Spring Boot实践——AOP实现

借鉴:http://www.cnblogs.com/xrq730/p/4919025.htmlhtml

      https://blog.csdn.net/zhaokejin521/article/details/50144753程序员

    http://www.importnew.com/24305.htmlspring

AOP介绍

1、AOP

  AOP(Aspect Oriented Programming),即面向切面编程,能够说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来创建一种对象层次结构,用于模拟公共行为的一个集合。不过OOP容许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码每每横向地散布在全部对象层次中,而与它对应的对象的核心功能毫无关系对于其余类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它致使了大量代码的重复,而不利于各个模块的重用。express

AOP技术偏偏相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减小系统的重复代码,下降模块之间的耦合度,并有利于将来的可操做性和可维护性。编程

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特色是,他们常常发生在核心关注点的多处,而各处基本类似,好比权限认证、日志、事物。AOP的做用在于分离系统中的各类关注点,将核心关注点和横切关注点分离开来。浏览器

  AOP(Aspect Orient Programming),咱们通常称为面向方面(切面)编程,做为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,好比事务管理、日志、缓存等等。AOP实现的关键在于AOP框架自动建立的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的表明为AspectJ;而动态代理则以Spring AOP为表明。缓存

  与AspectJ的静态代理不一样,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的所有方法,而且在特定的切点作了加强处理,并回调原对象的方法。安全

  Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理经过反射来接收被代理的类,而且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。springboot

  若是目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,是利用asm开源包,能够在运行时动态的生成某个类的子类注意,CGLIB是经过继承的方式作的动态代理,所以若是某个类被标记为final,那么它是没法使用CGLIB作动态代理的。框架

2、AOP核心概念

一、横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

二、切面(aspect)

类是对物体特征的抽象,切面就是对横切关注点的抽象

三、链接点(joinpoint)

被拦截到的点,由于Spring只支持方法类型的链接点,因此在Spring中链接点指的就是被拦截到的方法,实际上链接点还能够是字段或者构造器

四、切入点(pointcut)

对链接点进行拦截的定义

五、通知(advice)

所谓通知指的就是指拦截到链接点以后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

六、目标对象

代理的目标对象

七、织入(weave)

将切面应用到目标对象并致使代理对象建立的过程

八、引入(introduction)

在不修改代码的前提下,引入能够在运行期为类动态地添加一些方法或字段

 

3、Spring对AOP的支持

  Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。所以,AOP代理能够直接使用容器中的其它bean实例做为目标,这种关系可由IOC容器的依赖注入提供。Spring建立代理的规则为:

一、默认使用Java动态代理来建立AOP代理,这样就能够为任何接口实例建立代理了

二、当须要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

AOP编程实际上是很简单的事情,纵观AOP编程,程序员只须要参与三个部分:

一、定义普通业务组件

二、定义切入点,一个切入点可能横切多个业务组件

三、定义加强处理,加强处理就是在AOP框架为普通业务组件织入的处理动做

因此进行AOP编程的关键就是定义切入点和定义加强处理,一旦定义了合适的切入点和加强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=加强处理+被代理对象的方法。

实现方式

Spring除了支持Schema方式配置AOP,还支持注解方式:使用@AspectJ风格的切面声明。

1、Aspectj介绍

@AspectJ 做为经过 Java 5 注释注释的普通的 Java 类,它指的是声明 aspects 的一种风格。

AspectJ是静态代理的加强,所谓的静态代理就是AOP框架会在编译阶段生成AOP代理类,所以也称为编译时加强。

AspectJ: 基于字节码操做(Bytecode Manipulation),经过编织阶段(Weaving Phase),对目标Java类型的字节码进行操做,将须要的Advice逻辑给编织进去,造成新的字节码。毕竟JVM执行的都是Java源代码编译后获得的字节码,因此AspectJ至关于在这个过程当中作了一点手脚,让Advice可以参与进来。

而编织阶段能够有两个选择,分别是加载时编织(也能够成为运行时编织)和编译时编织

  • 加载时编织(Load-Time Weaving):顾名思义,这种编织方式是在JVM加载类的时候完成的。
  • 编译时编织(Compile-Time Weaving):须要使用AspectJ的编译器来替换JDK的编译器。

  详情:AOP的两种实现-Spring AOP以及AspectJ

一、添加spirng aop支持和AspectJ依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.0.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
</parent>


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
    </dependency>
</dependencies>

二、启用对@AspectJ的支持

  Spring默认不支持@AspectJ风格的切面声明,为了支持须要使用以下配置:

<!-- 自动扫描使用了aspectj注解的类 -->
<aop:aspectj-autoproxy/>

或者在配置类上加注解

@Configuration
@ComponentScan("com.only.mate.springboot.aop")
@EnableAspectJAutoProxy//开启AspectJ注解
public class CustomAopConfigurer {
}

三、声明切面

@Aspect
@Component
public class CustomLogAspect {
}

或者

定一个普通类

public class CustomAuthorityAspect {
}

在配置文件中定义一个POJO

<bean id="customAuthorityAspect" class="com.only.mate.springboot.aop.CustomAuthorityAspect" />

而后在该切面中进行切入点及通知定义,接着往下看吧。

四、声明切入点

@AspectJ风格的命名切入点使用org.aspectj.lang.annotation包下的@Pointcut+方法(方法必须是返回void类型)实现。

@Pointcut(value="切入点表达式", argNames = "参数名列表")  
public void pointcutName(……) {}  

  value:指定切入点表达式;

       argNames:指定命名切入点方法参数列表参数名字,能够有多个用“,”分隔,这些参数将传递给通知方法同名的参数,同时好比切入点表达式“args(param)”将匹配参数类型为命名切入点方法同名参数指定的参数类型。

       pointcutName:切入点名字,可使用该名字进行引用该切入点表达式。

案例:

@Pointcut(value="execution(* com.only.mate.springboot.controller.*.sayAdvisorBefore(..)) && args(param)", argNames = "param")  
public void pointCut(String param) {} 

定义了一个切入点,名字为“pointCut”,该切入点将匹配目标方法的第一个参数类型为通知方法实现中参数名为“param”的参数类型。

五、声明通知

@AspectJ风格的声明通知也支持5种通知类型:

A、前置通知:使用org.aspectj.lang.annotation 包下的@Before注解声明。

@Before(value = "切入点表达式或命名切入点", argNames = "参数列表参数名")  

       value:指定切入点表达式或命名切入点。

       argNames:与Schema方式配置中的同义。

B、后置返回通知:使用org.aspectj.lang.annotation 包下的@AfterReturning注解声明。

@AfterReturning(  
value="切入点表达式或命名切入点",  
pointcut="切入点表达式或命名切入点",  
argNames="参数列表参数名",  
returning="返回值对应参数名")  

       value:指定切入点表达式或命名切入点。

       pointcut:一样是指定切入点表达式或命名切入点,若是指定了将覆盖value属性指定的,pointcut具备高优先级。

       argNames:与Schema方式配置中的同义。

       returning:与Schema方式配置中的同义。

C、后置异常通知:使用org.aspectj.lang.annotation 包下的@AfterThrowing注解声明。

@AfterThrowing (  
value="切入点表达式或命名切入点",  
pointcut="切入点表达式或命名切入点",  
argNames="参数列表参数名",  
throwing="异常对应参数名")

       value:指定切入点表达式或命名切入点。

       pointcut:一样是指定切入点表达式或命名切入点,若是指定了将覆盖value属性指定的,pointcut具备高优先级。

       argNames:与Schema方式配置中的同义。

       throwing:与Schema方式配置中的同义。

D、后置最终通知:使用org.aspectj.lang.annotation 包下的@After注解声明。

@After (  
value="切入点表达式或命名切入点",  
argNames="参数列表参数名") 

       value:指定切入点表达式或命名切入点。

       argNames:与Schema方式配置中的同义。

E、环绕通知:使用org.aspectj.lang.annotation 包下的@Around注解声明。

@Around (  
value="切入点表达式或命名切入点",  
argNames="参数列表参数名")  

       value:指定切入点表达式或命名切入点。

       argNames:与Schema方式配置中的同义。

2、实践

一、Schema方式配置AOP

A、定一个切入点

/**
 * 自定义一个切入点-权限校验
 * @ClassName: CustomAuthorityAspect 
 * @Description: TODO
 * @author OnlyMate
 * @Date 2018年9月7日 下午2:24:24  
 *
 */
public class CustomAuthorityAspect {
    private Logger logger = LoggerFactory.getLogger(CustomLogAspect.class);
    /**
     * 加密
     * @Title: encode 
     * @Description: TODO
     * @Date 2018年9月7日 下午2:30:05 
     * @author OnlyMate
     */
    public void encode() {
        logger.info("CustomAuthorityAspect ==> encode method: encode data");
    }
    
    /**
     * 解密
     * @Title: decode 
     * @Description: TODO
     * @Date 2018年9月7日 下午2:30:11 
     * @author OnlyMate
     */
    public void decode() {
        logger.info("CustomAuthorityAspect ==> decode method: decode data");
    }
}

B、经过Schema方式配置AOP

<bean id="customAuthorityAspect" class="com.only.mate.springboot.aop.CustomAuthorityAspect" />

<aop:config proxy-target-class="false">
    <!-- AOP实现 -->
    <aop:aspect id="customAuthority" ref="customAuthorityAspect">
        <aop:pointcut id="addAllMethod" expression="execution(* com.only.mate.springboot.controller.*.*(..))" />
        <aop:before method="encode" pointcut-ref="addAllMethod" />
        <aop:after method="decode" pointcut-ref="addAllMethod" />
    </aop:aspect>
</aop:config>

  前面说过Spring使用动态代理或是CGLIB生成代理是有规则的,高版本的Spring会自动选择是使用动态代理仍是CGLIB生成代理内容,固然咱们也能够强制使用CGLIB生成代理,那就是<aop:config>里面有一个"proxy-target-class"属性,这个属性值若是被设置为true,那么基于类的代理将起做用,若是proxy-target-class被设置为false或者这个属性被省略,那么基于接口的代理将起做用

二、使用@AspectJ风格的切面声明

A、定一个切入点

/**
 * @Description: 自定义切面
 * @ClassName: CustomLogAspect 
 * @author OnlyMate
 * @Date 2018年9月10日 下午3:51:32  
 *
 */
@Aspect
@Component
public class CustomLogAspect {
    private Logger logger = LoggerFactory.getLogger(CustomLogAspect.class);

    /**
     * @Description: 定义切入点
     * @Title: pointCut 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:17
     */
    //被注解CustomAopAnnotation表示的方法
    //@Pointcut("@annotation(com.only.mate.springboot.annotation.CustomAopAnnotation")
    @Pointcut("execution(public * com.only.mate.springboot.controller.*.*(..))")
    public void pointCut(){
        
    }

    /**
     * @Description: 定义前置通知
     * @Title: before 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:23 
     * @param joinPoint
     * @throws Throwable
     */
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        logger.info("【注解:Before】------------------切面  before");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        logger.info("【注解:Before】浏览器输入的网址=URL : " + request.getRequestURL().toString());
        logger.info("【注解:Before】HTTP_METHOD : " + request.getMethod());
        logger.info("【注解:Before】IP : " + request.getRemoteAddr());
        logger.info("【注解:Before】执行的业务方法名=CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("【注解:Before】业务方法得到的参数=ARGS : " + Arrays.toString(joinPoint.getArgs()));

    }

    /**
     * @Description: 后置返回通知
     * @Title: afterReturning 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:30 
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(returning = "ret", pointcut = "pointCut()")
    public void afterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        logger.info("【注解:AfterReturning】这个会在切面最后的最后打印,方法的返回值 : " + ret);
    }

    /**
     * @Description: 后置异常通知
     * @Title: afterThrowing 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:37 
     * @param jp
     */
    @AfterThrowing("pointCut()")
    public void afterThrowing(JoinPoint jp){
        logger.info("【注解:AfterThrowing】方法异常时执行.....");
    }

    /**
     * @Description: 后置最终通知,final加强,不论是抛出异常或者正常退出都会执行
     * @Title: after 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:48 
     * @param jp
     */
    @After("pointCut()")
    public void after(JoinPoint jp){
        logger.info("【注解:After】方法最后执行.....");
    }

    /**
     * @Description: 环绕通知,环绕加强,至关于MethodInterceptor
     * @Title: around 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:56 
     * @param pjp
     * @return
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) {
        logger.info("【注解:Around . 环绕前】方法环绕start.....");
        try {
            //若是不执行这句,会不执行切面的Before方法及controller的业务方法
            Object o =  pjp.proceed();
            logger.info("【注解:Around. 环绕后】方法环绕proceed,结果是 :" + o);
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }

}

 

B、使用@AspectJ风格的切面声明

/**
 * 自定义AOP配置类
 * @ClassName: CustomAopConfigurer 
 * @Description: TODO
 * @author OnlyMate
 * @Date 2018年9月7日 下午3:43:21  
 *
 */
@Configuration
@ComponentScan("com.only.mate.springboot.aop")
@EnableAspectJAutoProxy//开启AspectJ注解
public class CustomAopConfigurer {
}

效果图

总结

AspectJ在编译时就加强了目标对象,Spring AOP的动态代理则是在每次运行时动态的加强,生成AOP代理对象,区别在于生成AOP代理对象的时机不一样,相对来讲AspectJ的静态代理方式具备更好的性能,可是AspectJ须要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

详情:http://www.importnew.com/24305.html

 

想知道更多@AspectJ的使用,请前往 Spring Boot实践——三种拦截器的建立 查看。

相关文章
相关标签/搜索