0
点赞
收藏
分享

微信扫一扫

【JAVA】Spring Boot中实现 AOP

waaagh 2022-01-08 阅读 87

一、前言

AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 动态代理,与 CGLIB的动态代理

二、两种动态代理的区别

主要来说是JDK动态代理需要借助接口才可实现,最终的代理类原类实现同一个接口,属于兄弟关系
CGLIB动态代理使用的是继承的方式,代理类继承于原类,属于父子关系
在Spring实现AOP时会根据类有无接口自动选择代理方式,也可以手动指定使用CGLIB动态代理。实现方式是在Spring配置文件中加入如下。
下面展示一些 内联代码片

<!-- 强制使用cglib生动态代理 -->
	<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

在Spring Boot 2.0之后默认使用CGLIB动态代理

三、相关注解介绍

在Spring Boot中通常使用AspectJ实现AOP,AspectJ 中常用的通知有五种类型:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知

下面介绍AspectJ的常用注解:

  1. @Before:前置增强,目标方法执行之前执行。
  2. @AfterReturning:在目标方法执行之后执行可以获取到目标方法的返回值,该注解的 returning 属性就是用于指定接收方法返回值的变量名的。
  3. @Around:在目标方法执行之前之后执行。方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
  4. @AfterThrowing:异常通知,在目标方法抛出异常后执行。方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。
  5. @After:无论目标方法是否抛出异常,该增强均会被执行
  6. @Pointcut:定义切入点。
  7. @Aspect:把当前类标识为一个切面。

四、Spring Boot中实现 AOP主要步骤

在pom文件中加入如下依赖。

<!--aspect面向切面编程依赖-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

下面为写项目时实现AOP的一个demo

@Aspect
@Component
public class LogAspect {

	//定义日志对象
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

	//设置切面,该方法没有实际意义
    @Pointcut("execution(* com.yyy.blog.web.*.*(..))")
    public void log(){}

	//前置通知
    @Before("log()")
    public void doBefore(JoinPoint joinPoint){
    
    	//获取HttpServletRequest
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        
        String url = request.getRequestURL().toString();
        String ip = request.getRemoteAddr();
        
		//获取请求方法名
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        //获取请求参数
        Object[] args = joinPoint.getArgs();
        RequestLog requestLog = new RequestLog(url,ip,classMethod,args);
        logger.info("Request : {}",requestLog);
    }

    @After("log()")
    public void doAfter(){
        logger.info("doAfter");
    }

    @AfterReturning(returning = "result",pointcut = "log()")
    public void doAfterReturn(Object result){
        logger.info("Result : {}" + result);
    }

    private class RequestLog{
        private String url;
        private String ip;
        private String classMethod;
        private Object[] args;

        public RequestLog(String url, String ip, String classMethod, Object[] args) {
            this.url = url;
            this.ip = ip;
            this.classMethod = classMethod;
            this.args = args;
        }

        @Override
        public String toString() {
            return "{" +
                    "url='" + url + '\'' +
                    ", ip='" + ip + '\'' +
                    ", classMethod='" + classMethod + '\'' +
                    ", args=" + Arrays.toString(args) +
                    '}';
        }
    }
}

五、补充

在实际操作中,定义切点的方法有两种:

一是在某个通知注解上加入value属性来指定切点

@Before(value = "execution(* com.yyy.*.*(..))")
public void doBefore(JoinPoint joinPoint){
}
@AfterReturning(returning = "result",value = "execution(* com.yyy.*.*(..))")
public void doAfterReturn(Object result){
	//result为切点方法的返回值
}
@Around(value = "execution(* com.yyy.*.*(..))"public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
	//用于接收切点方法返回值
	Object obj;
	//执行切点方法
	obj = pjp.proceed();
}

第二种方法就是将@Pointcut 注解在一个方法之上,以后所有的 executionvalue 属性值均可使用该方法名作为切入点。代表的就是@Pointcut定义的入点。这个使用@Pointcut注解的方法一般使用 private 的标识方法,即没有实际作用的方法。详情见“四、”。

举报

相关推荐

0 条评论