Spring AOP(动态代理)

动态代理

    • Spring AOP概论
    • AOP的组成
    • Spring AOP实现步骤:
      • 添加依赖
      • 定义切面
      • 定义切点
      • 实现通知
    • Spring AOP 实现原理---》动态代理
      • AOP的实战环节
      • 用户登录拦截器
      • 统一异常处理
      • 统一数据格式的返回

Spring AOP概论

AOP(Aspect Oriented Programming):⾯向切⾯编程,它是⼀种思想,它是对某⼀类事情的集中处理。而Spring AOP是AOP具体的实现。

比如:写博客系统时,用户在进行观看和书写的时候,需要服务器去验证用户的登录状态,以前的写法是在每个执行程序的加入判断用户状态,现在用AOP就可以做到。集中处理这些。

之前的处理⽅式是每个 Controller 都要写⼀遍⽤户登录验证,然⽽当你的功能越来越多,那么你要写的登录验证也越来越多,⽽这些⽅法⼜是相同的,这么多的⽅法就会代码修改和维护的成本。

对于这种功能统⼀,且使⽤的地⽅较多的功能,就可以考虑 AOP
来统⼀处理了。

AOP可以实现。

  • 统⼀⽇志记录
  • 统⼀⽅法执⾏时间统计
  • 统⼀的返回格式设置
  • 统⼀的异常处理
  • 事务的开启和提交等

AOP的组成

1.切面(Aspect):定义的是事件(AOP是做啥的):比如用户登录校验(定义方向
2.切点(Pointcut):定义具体规则:比如定义用户登录拦截规则,那些接口判断用户登录权限?哪些不判断(定制具体方案
3.通知(Advice):AOP执行的具体方法:比如获取用户登录信息,如果获取到说明已经登录,否则未登录。(具体业务执行者
分类:前置通知,后置通知,环绕通知,异常通知,返回通知
4.连接点(join poit):有可能触发切点的所有点:比如所有的接口,链接所有可能触发条件 的接口

Spring AOP实现步骤:

(拦截)
1.添加Spring AOP依赖
2.定义切面
3.定义切点
4.实现通知

添加依赖

面板中不存在,不热门的插件,所以这个需要手动添加到pom.xml中


<dependency>
 <groupId>org.springframework.bootgroupId>
 <artifactId>spring-boot-starter-aopartifactId>
dependency>

Spring AOP(动态代理)_第1张图片

定义切面

使用注解:@Aspect
Spring AOP(动态代理)_第2张图片

定义切点

使用注解 @Pointcut
Spring AOP(动态代理)_第3张图片
AspectJ ⽀持三种通配符

  • ( * ) :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
  • (. .) :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。
  • (+) :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的所有⼦类包括本身

实现通知

  • 前置通知使⽤@Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
  • 后置通知使⽤@After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
  • 返回之后通知使⽤@AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
  • 抛异常后通知使⽤@AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
  • 环绕通知使⽤@Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执
    Spring AOP(动态代理)_第4张图片
    Spring AOP(动态代理)_第5张图片
    每一次在前端调用方法时都会,被AOP拦截,执行完AOP中的方法后,在返回给Controller
@Aspect
@Component
public class UserAspect {

    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void pointcut(){

    }

    @Before("pointcut()")
    public void doBefore(){
        System.out.println("执行了前置通知");

    }

    @After("pointcut()")
    public void doAfter(){
        System.out.println("执行了后置通知");

    }

    @AfterReturning("pointcut()")
    public void doAfterReturning(){
        System.out.println("执行了返回之后通知");
    }



    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知执行之前");
        //执行目标方法
        Object result=joinPoint.proceed();
        System.out.println("环绕通知执行之后");
        return result;

    }

}

环绕通知: AOP 统计 UserController 每个⽅法的执⾏时间。

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知执行之前");
        //执行目标方法
        Object result=joinPoint.proceed();
        System.out.println("环绕通知执行之后");
        return result;  
    }

Spring AOP 实现原理—》动态代理

动态代理组成:
1.JDK Proxy-------》代理对象必须实现接口,才能使用JDK Proxy。
2.CGLIB-------》通过是实现代理类的子类来实现动态代理(被final修饰的类是不能被代理)

JDK Proxy vs CGLIB 的区别
1.出身不同:
JDK Proxy出身于JDK
CGLIB出身第三方库
2.实现不同:

  • JDK Proxy 要求代理类实现接口才能实现dialing;
  • CGLIB 是通过实现代理类的子类完成动态代理。

3.性能不同:

  • JDK 7+ JDK Proxy性能是略高于 CGLIB;
  • JDK 7 之前CGLIB 性能远远高于JDK Proxy

AOP的实战环节

  • 统一的登录权限校验
  • 统一异常处理
  • 统一数据格式返回

Spring 拦截器:
HandlerInterceptor,拦截器的实现分为以下两个步骤:

  • 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的 preHandle(执⾏具体⽅法之前的预处理)⽅法。
  • 将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中

用户登录拦截器

Spring AOP(动态代理)_第6张图片

创建⾃定义拦截器
重写:preHandle

/*
* 自定义拦截器
* */
public class UserInterceptor implements HandlerInterceptor {
    //返回 true -》拦截器校验成功,继续执行后续的方法
    //返回false -> 拦截器校验失败,不会执行后续的目标方法
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {

        //业务方法
        HttpSession session=request.getSession();
        if (session != null&&session.getAttribute(AppVar.SESSION_KEY)!=null) {
            return true;
        }
        return false;
    }
}

将⾃定义拦截器加⼊ WebMvcConfigurer
全局配置文件

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private UserInterceptor userInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/user/reg")//拦截所有的请求,然后在选择放权限
                .excludePathPatterns("/user/login")
        ;
    }
}

控制层Controller

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/getuser")
    public String getUser(){
        System.out.println("do getUser");
        return "get user";
    }

    @RequestMapping("/getUser")
    public String delUser(){
        System.out.println("do getUser");
        return "del user";
    }

    @RequestMapping("/reg")
    public String doReg(){
        System.out.println("do reg");
        return "get reg";
    }

    @RequestMapping("/login")
    public String dologin(){
        System.out.println("do login");
        return "get login";
    }

}

设置的全局变量


//全局变量
public class AppVar {
    //session key值
    public static final String SESSION_KEY="SESSION_KEY";
}
  • addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意⽅法(也就是所有⽅法)。
  • excludePathPatterns:表示需要排除的 URL。

说明:以上拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件、JS 和 CSS 等⽂件)。

统一异常处理

1.@ControllerAdvice/@RestControllerAdvice
2.@ExceptionHandler(Exception.class)统一返回对象。

//一个特定的异常处理
@RestControllerAdvice
public class ExceptionAdvice {
    @ExceptionHandler(NullPointerException.class)
    public ResultAjax doNullPointerException(NullPointerException e){
        ResultAjax resultAjax=new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMsg("空指针异常:"+e.getMessage());
        resultAjax.setObject(null);
        return resultAjax;
    }
}
//全部的异常处理
@ExceptionHandler(Exception.class)
    public ResultAjax doException(Exception e){
        ResultAjax resultAjax=new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMsg("空指针异常:"+e.getMessage());
        resultAjax.setObject(null);
        return resultAjax;
    }

统一数据格式的返回

统⼀的数据返回格式可以使⽤ @ControllerAdvice +
@ResponseBodyAdvice 的⽅式实现.

后置执行,无论如何都得执行

  • @ControllerAdvice
  • 实现@ResponseBodyAdvice接口,并重写他的两个方法,supports
    必须返回true,beforeBodyWrite方法中进行重写判断和重写操作。

这个呢,也叫保底策略。

/*
* 实现返回值的保底实现类
*
* */
@ControllerAdvice
public class ResponseAdvice  implements ResponseBodyAdvice {
    /*
    * true-->才会调用beforBody方法
    * false-->不会会调用beforBody方法
    * */
    @Resource
    private ObjectMapper objectMapper;
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
        //已经包装好的对象
        if (body instanceof ResultAjax){
            return body;
        }
        //无法接收String类型
        //对字符串进行单独的判断和数据
        if (body instanceof String){
            ResultAjax resultAjax=ResultAjax.succ(body);
            try {
                return objectMapper.writeValueAsString(resultAjax);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }

        }
        return ResultAjax.succ(body);
    }
}

总结:

  • 统⼀⽤户登录权限的效验使⽤ WebMvcConfigurer+ HandlerInterceptor来实现
  • 统⼀异常 处理使⽤ @ControllerAdvice + @ExceptionHandler 来实现
  • 统⼀返回值处理使⽤ @ControllerAdvice + ResponseBodyAdvice 来处理

你可能感兴趣的:(java,spring,java,数据库)