Spring Boot1.5.4 AOP实例

  1. 还是首先添加依赖(使用当前springboot的默认版本)
  
    org.springframework.boot  
    spring-boot-starter-aop  
  

  • 参考下面官方文档的部分配置说明,可见aop是默认开启的,自动添加了@EnableAspectJAutoProxy注解
# AOP
spring.aop.auto=true # Add @EnableAspectJAutoProxy.
spring.aop.proxy-target-class=false # Whether subclass-based (CGLIB) proxies are to be created (true) as opposed to standard Java interface-based proxies (false).
  1. 编写一个切面类,AspectAdviceConfig.java,里面定义了一个切点指示器和各种通知(Advice,也译作 增强)
package com.ansel.testall.aop;

import java.lang.reflect.Method;
import java.util.Arrays;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Component
public class AspectAdviceConfig {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 定义切点指示器:com.ansel.testall.aop包下,带@RestController注解的类。
     */
    @Pointcut("execution(* com.ansel.testall.aop..*(..)) and @annotation(org.springframework.web.bind.annotation.RestController)")
    public void myPointcut() {
    }

    /**
     * 前置通知,在目标方法完成之后调用通知,此时不会关 心方法的输出是什么
     */
    @Before("myPointcut()")
    public void beforeAdvice() {
        System.out.println("Before--通知方法会在目标方法调用之前执行");
    }

    /**
     * 后置通知,在目标方法完成之后调用通知,此时不会关 心方法的输出是什么
     */
    @After("myPointcut()")
    public void afterAdvice() {
        System.out.println("After--通知方法会在目标方法返回或抛出异常后调用");
    }

    /**
     * 返回通知,在目标方法成功执行之后调用,可以获得目标方法的返回值,但不能修改(修改也不影响方法的返回值)
     * 
     * @param jp
     *            JoinPoint接口,可以获得连接点的一些信息
     * 
     * @param retVal
     *            目标方法返回值,和jp一样会由spring自动传入
     */
    @AfterReturning(returning = "retVal", pointcut = "myPointcut()")
    public void afterReturningAdvice(JoinPoint jp, Object retVal) {
        retVal = retVal + " (@AfterReturning can read the return value, but it can't change the value!)";
        System.out.println("AfterReturning--通知方法会在目标方法返回后调用; retVal = " + retVal);
        System.out.println(jp.toLongString());
    }

    /**
     * 异常通知,在目标方法抛出异常后调用通知
     */

    @AfterThrowing("myPointcut()")
    public void afterThrowingAdvice() {
        System.out.println("AfterThrowing--通知方法会在目标方法抛出异常后调用");
    }

    /**
     * 环绕通知,可以在目标方法调用前后,自定义执行内容。可以修改目标方法的返回值
     * 
     * @param pjp
     */
    @Around("myPointcut()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) {
        Object retVal = null;
        try {
            System.out.println("Around--目标方法调用之前执行");

            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();

            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod(); // 获取被拦截的方法
            String methodName = method.getName(); // 获取被拦截的方法名

            logger.info("requset method name is: " + methodName);
            logger.info("request URL is: " + request.getRequestURL().toString());
            logger.info("request http method: " + request.getMethod());
            logger.info("request arguments are: " + Arrays.toString(pjp.getArgs()));

            retVal = pjp.proceed();
            retVal = retVal + " (@Around can change the return value!)";

            System.out.println("Around--目标方法返回后调用");
        } catch (Throwable e) {
            System.out.println("Around--目标方法抛出异常后调用");
        }
        return retVal;
    }
}

  1. 通过Advice可以某些方法增加一些功能,若要为某个对象增加新的方法,则要用到Introduction,编写另外一个切面AspectIntroductionConfig.java
package com.ansel.testall.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AspectIntroductionConfig {

    /**
     * DeclareParents注解所标注的静态属性指明了要引入的接口;
     * value属性指定了哪种类型的bean要引入该接口;
     * defaultImpl属性指定了为引入功能提供实现的类。
     */
    @DeclareParents(value = "com.ansel.testall.aop.AopService+", defaultImpl = IntroductionServiceImpl.class)
    public static IntroductionService introductionService;
}

  1. 编写两个接口AopService、IntroductionService,和2个实现AopServiceImpl、IntroductionServiceImpl
package com.ansel.testall.aop;

public interface AopService {
    String myOwnMethod();
}
/***********************************/
package com.ansel.testall.aop;

public interface IntroductionService {
    String IntroductionMethod();
}
/***********************************/
package com.ansel.testall.aop;

import org.springframework.stereotype.Service;

@Service
public class AopServiceImpl implements AopService {

    @Override
    public String myOwnMethod() {
        return "this method is from AopService";
    }

}
/***********************************/
package com.ansel.testall.aop;

import org.springframework.stereotype.Service;

@Service
public class IntroductionServiceImpl implements IntroductionService {

    @Override
    public String IntroductionMethod() {
        return "this method from Introduction.";
    }

}
  1. 编写一个切点实例AopController.java (其实就是个普通的controller)
package com.ansel.testall.aop;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AopController {

    /**
     * 只注入AopService
     */
    @Autowired
    AopService aopService;

    /**
     * 测试AOP通知(Advice,也译作 增强)
     * 
     * @return
     */
    @RequestMapping(value = "/aop", method = RequestMethod.GET)
    public String testAop() {
        return "this is a AOP Advice test.";
    }

    /**
     * 测试AOP引入(Introduction)
     * 
     * @return
     */
    @RequestMapping(value = "/aop/introdution", method = RequestMethod.GET)
    public String testAopIntroduction() {
        System.out.println(aopService.myOwnMethod());
        //接口类型转换
        IntroductionService introductionService = (IntroductionService) aopService;
        System.out.println(introductionService.IntroductionMethod());
        return "this is a AOP Introduction test.";
    }
}

  1. 启动项目,触发 localhost:8080/aop 请求,控制台输出结果如下:
Around--目标方法调用之前执行
2017-07-03 17:33:15.494  INFO 8548 --- [nio-8443-exec-7] tConfig$$EnhancerBySpringCGLIB$$5f4562fb : requset method name is: testAOP
2017-07-03 17:33:15.495  INFO 8548 --- [nio-8443-exec-7] tConfig$$EnhancerBySpringCGLIB$$5f4562fb : request URL is: https://localhost:8443/aop
2017-07-03 17:33:15.495  INFO 8548 --- [nio-8443-exec-7] tConfig$$EnhancerBySpringCGLIB$$5f4562fb : request http method: GET
2017-07-03 17:33:15.495  INFO 8548 --- [nio-8443-exec-7] tConfig$$EnhancerBySpringCGLIB$$5f4562fb : request arguments are: []
Before--通知方法会在目标方法调用之前执行
Around--目标方法返回后调用
After--通知方法会在目标方法返回或抛出异常后调用
AfterReturning--通知方法会在目标方法返回后调用; retVal = this is a AOP test. (@Around can change the return value!) (@AfterReturning can read the return value, but it can't change the value!)
execution(public java.lang.String com.ansel.testall.aop.AOPController.testAOP())

页面返回结果如下:
this is a AOP test. (@Around can change the return value!)

触发 localhost:8080/aop/introdution 请求,控制台输出结果如下:

Spring Boot1.5.4 AOP实例_第1张图片
qq 20170704124306

注:

@Pointcut("execution(* com.ansel.testall.aop..*(..)) and @annotation(org.springframework.web.bind.annotation.RestController)")

当我使用and操作符连接上面两个切点指示器,没有任何问题(把注解RestController换成RequestMapping也没问题)
然而,当我使用&&操作符连接上面两个切点指示器时,完全没有触发AspectJ,切面没有织入任何连接点(把注解RestController换成RequestMapping就没问题)

   /**
    * OK
    */
   @Pointcut("execution(* com.ansel.testall.aop..*(..)) and @annotation(org.springframework.web.bind.annotation.RestController)")

   /**
    * not OK
    */
   @Pointcut("execution(* com.ansel.testall.aop..*(..)) && @annotation(org.springframework.web.bind.annotation.RestController)")

   /**
    * OK
    */
   @Pointcut("execution(* com.ansel.testall.aop..*(..)) and @annotation(org.springframework.web.bind.annotation.RequestMapping)")

   /**
    * OK
    */
   @Pointcut("execution(* com.ansel.testall.aop..*(..)) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")

你可能感兴趣的:(Spring Boot1.5.4 AOP实例)