zoukankan      html  css  js  c++  java
  • spring-framework-core-aop

    祝大家春节快乐!万事如意!福如东海!寿比南山!早生贵子!哈哈哈哈!

    AOP概述

    AOP中的名词

    Spring2.0版本前的AOP

    1. 小试牛刀AOP

    2. Spring中的通知

    3. RegexpMethodPointcutAdvisor切入点和通知类关联使用

    4. 给目标类新增接口

    5. JDK代理和CGLib代理都是动态代理

    6. 一次生成多个代理对象

    7. 根据切入点生成代理对象

    8. 编程方式创建代理对象

    9. TargetSource

      1. HotSwappableTargetSource

      2. PrototypeTargetSource

    AspectJ风格的SpringAOP

    1. Aspect风格和Aspect编译器

    2. 一个简单的入门

    3. 支持的切入点匹配器

    4. 切入点组合

    5. 注解方式的连接点

    6. 连接点的通用参数

    7. XML方式使用AOP

    8. 给目标对象增加接口

    真正的AspectJ编译器

    本类调用为什么会失效

    AspectJ编译器体验

    AOP概述

    AOP要做的是将业务代码和功能代码分开实现。例如,一个类中的一个方法要往数据库添加一条数据,删除一条数据。这两个业务操作,都用得上事务管理。那么将事务管理抽取出一个类,类里的方法就是开启事务,提交事务,和回滚事务。AOP负责将事务代码和业务代码无缝结合在一块。

    AOP中的名词

    通知(Advice):通常我们要对目标类的某个方法进行增强。可以增强的点有方法执行前,后,异常后,finally中,或者前后一块(环绕)。

    目标类(Target):业务类。通知就是要和目标类中的方法结合的。

    连接点(JoinPoint):一个类的每个方法都可以被增强,这些方法就是连接点。

    切入点(Pointcut):切入点匹配器,通常是execution表达式。被匹配到的具体方法,就是切入点。

    引入(Introduction):spring允许对一个bean动态的实现一个接口。

    代理(Proxy):实现AOP操作的方法就是通过代理,通过创建一个目标类的代理对象,在代理对象里对目标类增强。

    切面(Aspect):切入点和通知的类方法结合的过程就是切面。

    织入(Weaving):创建代理对象的这个过程就是织入。

    Spring2.0前的AOP

    1 小试牛刀AOP

    package springcode.aop.spring;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-25 18:54
     * @notify 通知类
     * @version 1.0
     */
    
    import org.springframework.aop.MethodBeforeAdvice;
    
    import java.lang.reflect.Method;
    
    public class BeforeAdvice implements MethodBeforeAdvice {
    
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println("我是BeforeAdvice");
        }
    }
    View Code
    package springcode.aop.spring;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-25 21:10
    * @notify 
    * @version 1.0
    */
    public interface TargetInte {
         void show();
         void speak();
         void exc();
    }
    View Code
    package springcode.aop.spring;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-25 18:56
     * @notify 被增强的目标类
     * @version 1.0
     */
    
    public class Target implements TargetInte {
    
        @Override
        public void show() {
            System.out.println("我是目标类的show方法");
        }
    
        @Override
        public void speak() {
            System.out.println("我是目标方法的speak方法");
        }
    
        @Override
        public void exc() {
            int i = 1 / 0;
        }
    }
    View Code
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.BeforeAdvice" id="beforeAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
            <property name="pattern" value=".*show"></property>
        </bean>
    
        <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
            <property name="pointcut" ref="pointcut1"></property>
            <property name="advice" ref="beforeAdvice"></property>
        </bean>
    
        <bean class="springcode.aop.spring.Target" id="targetBean"></bean>
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean"></property>
            <property name="interceptorNames">
                <list>
                    <value>advisor</value>
                </list>
            </property>
        </bean>
    </beans>
    View Code
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.BeforeAdvice" id="beforeAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
            <property name="pattern" value=".*show"></property>
        </bean>
    
        <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
            <property name="pointcut" ref="pointcut1"></property>
            <property name="advice" ref="beforeAdvice"></property>
        </bean>
    
        <bean class="springcode.aop.spring.Target" id="targetBean"></bean>
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean"></property>
            <property name="proxyInterfaces"  value="springcode.aop.spring.TargetInte"/>
            <property name="interceptorNames">
                <list>
                    <value>advisor</value>
                </list>
            </property>
        </bean>
    </beans>
    View Code

    在上边的代码片段中,先来看BeforeAdvice.class它继承了MethodBeforeAdvice。前边我们提到过连接点的概念,继承了MethodBeforeAdvice则代表该类是一个通知类,并且可以作为前置通知。TargetInte接口,定义的是业务接口它的作用就像是Service接口一样,并没有特殊的意义。Target类实现了业务接口TargetInte。在高版本的Spring中目标类有没有父类并不重要,但是一个目标类,有父接口和没有这是有区别的,我们稍后会提到。对于以上三个类其实对于AOP操作我们必须凑齐的条件已经够了,有目标类,有通知类。最后我们需要在xml配置文件中,将两个类结合使用就行。beforeAdvice将通知类注册成bean,targetBean将目标类注册成bean,JdkRegexpMethodPointcut是Spring提供的一个切入点,通过JDK正则表达式匹配这个切入点可切入的类中的方法。DefaultPointcutAdvisor这个bean当做一个中间层,它将切入点和通知类管理到一起。那么现在我们需要的只剩下对目标类做代理操作了。ProxyFactoryBean它内部必须的参数需要代理的bean(target),需要代理的bean的父接口(proxyInterfaces),最后该代理对象所需要的通知(往下成为拦截器),这是一个列表,意味着我们可以给一个代理对象指定多个通知,它们将依次执行(interceptorNames)

    JdkRegexpMethodPointcut切入点匹配了show方法,所以只有show方法有前置通知。

    2 Spring中的通知

    在spring中定义了4个接口供我们使用。我们可以使用一个Advice类实现4个接口,也可以单独使用。但是一个Advice实现4个接口,意味着该Advice拥有所有的连接点。

    1 MethodBeforeAdvice 前置通知

    package springcode.aop.spring;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-25 18:54
     * @notify 通知类
     * @version 1.0
     */
    
    import org.springframework.aop.MethodBeforeAdvice;
    
    import java.lang.reflect.Method;
    
    public class BeforeAdvice implements MethodBeforeAdvice {
    
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println("我是BeforeAdvice");
        }
    }
    View Code
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.BeforeAdvice" id="beforeAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
            <property name="pattern" value=".*show"></property>
        </bean>
    
        <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
            <property name="pointcut" ref="pointcut1"></property>
            <property name="advice" ref="beforeAdvice"></property>
        </bean>
    
        <bean class="springcode.aop.spring.Target" id="targetBean"></bean>
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean"></property>
            <property name="proxyInterfaces"  value="springcode.aop.spring.TargetInte"/>
            <property name="interceptorNames">
                <list>
                    <value>advisor</value>
                </list>
            </property>
        </bean>
    </beans>
    View Code

    2 AfterReturningAdvice 后置通知

    package springcode.aop.spring;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-25 18:54
    * @notify 通知类
    * @version 1.0
    */
    
    import org.springframework.aop.AfterReturningAdvice;
    
    import java.lang.reflect.Method;
    
    public class AfterAdvice implements AfterReturningAdvice {
    
        @Override
        public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
            System.out.println("我是AfterAdvice");
        }
    }
    View Code
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.AfterAdvice" id="afterAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
            <property name="pattern" value=".*show"></property>
        </bean>
    
        <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
            <property name="pointcut" ref="pointcut1"></property>
            <property name="advice" ref="afterAdvice"></property>
        </bean>
    
        <bean class="springcode.aop.spring.Target" id="targetBean"></bean>
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean"></property>
            <property name="proxyInterfaces"  value="springcode.aop.spring.TargetInte"/>
            <property name="interceptorNames">
                <list>
                    <value>advisor</value>
                </list>
            </property>
        </bean>
    </beans>
    View Code

    3 ThrowsAdvice异常通知

    这里需要注意了,ThrowsAdvice是一个标记接口,我们不需要覆盖任何方式。但是afterThrowing()方法名是固定的,我们能改变的是参数类型,异常通知会根据业务代码抛出的异常类型,选择合适的异常通知。

    package springcode.aop.spring;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-25 22:21
    * @notify 
    * @version 1.0
    */
    
    import org.springframework.aop.ThrowsAdvice;
    
    public class ExceptionAdvice implements ThrowsAdvice {
        public void afterThrowing(Exception ex){
            System.out.println("异常了");
        }
    
        public void afterThrowing(ArithmeticException ex){
            System.out.println("算数异常了");
        }
    }
    View Code
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.ExceptionAdvice" id="exceptionAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
            <property name="patterns">
                <list>
                    <value>.*show</value>
                    <value>.*speak</value>
                    <value>.*exc</value>
                </list>
            </property>
        </bean>
    
        <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
            <property name="pointcut" ref="pointcut1"></property>
            <property name="advice" ref="exceptionAdvice"></property>
        </bean>
    
        <bean class="springcode.aop.spring.Target" id="targetBean"></bean>
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean"></property>
            <property name="proxyInterfaces"  value="springcode.aop.spring.TargetInte"/>
            <property name="interceptorNames">
                <list>
                    <value>advisor</value>
                </list>
            </property>
        </bean>
    </beans>
    View Code

    4 MethodInterceptor 环绕通知和finally

    Spring将环绕通知和finally放到了同一个接口中,invoke(MethodInvocation methodInvocation)参数methodInvocation的proceed()方法,执行目标类的本身的代码。如果出现异常则catch代码块必定执行。看打印结果,show()和speak()方法没有抛出异常,则有前置和后置通知,exc()方法抛出了异常则只有前置和finally通知。

    package springcode.aop.spring;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-25 22:35
     * @notify
     * @version 1.0
     */
    
    
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    public class MethodAdvice implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            Object result = null;
            try {
                System.out.println("MethodAdvice1");
                result = methodInvocation.proceed();
                System.out.println("methodAdvice2");
                return result;
            } catch (Exception e) {
                System.out.println("methodAdvice3");
                return result;
            }
        }
    }
    View Code
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.MethodAdvice" id="methodAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
            <property name="patterns">
                <list>
                    <value>.*show</value>
                    <value>.*speak</value>
                    <value>.*exc</value>
                </list>
            </property>
        </bean>
    
        <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
            <property name="pointcut" ref="pointcut1"></property>
            <property name="advice" ref="methodAdvice"></property>
        </bean>
    
        <bean class="springcode.aop.spring.Target" id="targetBean"></bean>
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean"></property>
            <property name="proxyInterfaces"  value="springcode.aop.spring.TargetInte"/>
            <property name="interceptorNames">
                <list>
                    <value>advisor</value>
                </list>
            </property>
        </bean>
    </beans>
    View Code

    3 RegexpMethodPointcutAdvisor切入点和通知类关联使用

    回顾上述内容我们的xml配置文件总是需要做的内容1、将Advice类的注册成bean。2、创建一个JdkRegexpMethodPointcut匹配器,用来确定哪些类的哪些方法可以被代理。3、创建DefaultPointcutAdvisor用来连接Advice和JdkRegexpMethodPointcut。4、将Target目标类注册成bean。5、创建ProxyFactoryBean设置需要代理的bean和拦截器链。似乎是一气呵成,但是总是的第3步骤好像没有起到实际的意义。于是RegexpMethodPointcutAdvisor的出现就是直接在匹配器中注入Advice,然后在ProxyFactoryBean直接引用RegexpMethodPointcutAdvisor

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.MethodAdvice" id="methodAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="advice">
                <ref bean="methodAdvice"/>
            </property>
            <property name="patterns">
                <list>
                    <value>.*show</value>
                    <value>.*speak</value>
                    <value>.*exc</value>
                </list>
            </property>
        </bean>
    
    
    
        <bean class="springcode.aop.spring.Target" id="targetBean"></bean>
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean"></property>
            <property name="proxyInterfaces"  value="springcode.aop.spring.TargetInte"/>
            <property name="interceptorNames">
                <list>
                    <value>pointcut1</value>
                </list>
            </property>
        </bean>
    </beans>
    View Code

    4 给目标类新增接口

    在aop名词中提到了引入的概念,我们可以动态的给代理对象新增一个接口,并且实现它。目标类Target只实现了TargetInte接口,并且有3个方法。现在我们创建一个类并且实现IntroductionInterceptor接口和IOther接口,IOther接口是我们新定义的一个接口给目标类扩展的接口就是它。在IntroductionInterceptor实现类中实现方法implementsInterface()invoke()和IOther接口的doOther(),当方法执行时,所执行的方法属于IOther接口,则调用本类的doOther()。xml配置也做了修改,原来的RegexpMethodPointcutAdvisor换成DefaultIntroductionAdvisor,并且它也不需要方法过滤,DefaultIntroductionAdvisor需要两个参数1、Adivce通知,2、额外增加的接口。看测试结果,Target类本来没有doOther()方法,现在则有了。

    package springcode.aop.spring;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-25 18:56
     * @notify 被增强的目标类
     * @version 1.0
     */
    
    public class Target implements TargetInte {
    
        @Override
        public void show() {
            System.out.println("我是目标类的show方法");
        }
    
        @Override
        public void speak() {
            System.out.println("我是目标方法的speak方法");
        }
    
        @Override
        public void exc() {
            int i = 1 / 0;
        }
    }
    View Code
    package springcode.aop.spring;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-25 22:45
     * @notify
     * @version 1.0
     */
    
    import org.aopalliance.intercept.MethodInvocation;
    import org.springframework.aop.IntroductionInterceptor;
    
    public class IntroductionInterceptorAdvice implements IntroductionInterceptor, IOther {
    
        @Override
        public void doOther(){
            System.out.println("Other对象的功能");
        }
    
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            if(implementsInterface(methodInvocation.getMethod().getDeclaringClass())){
                return methodInvocation.getMethod().invoke(this, methodInvocation.getArguments());
            }
            else{
                return methodInvocation.proceed();
            }
        }
    
        @Override
        public boolean implementsInterface(Class arg0){
            boolean assignableFrom = arg0.isAssignableFrom(IOther.class);
            return assignableFrom;
        }
    }
    View Code
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.IntroductionInterceptorAdvice" id="introductionInterceptorAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
            <constructor-arg ref="introductionInterceptorAdvice"></constructor-arg>
            <constructor-arg value="springcode.aop.spring.IOther" />
        </bean>
    
    
        <bean class="springcode.aop.spring.Target" id="targetBean"></bean>
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean"></property>
            <property name="proxyInterfaces"  value="springcode.aop.spring.TargetInte"/>
            <property name="interceptorNames">
                <list>
                    <value>pointcut1</value>
                </list>
            </property>
        </bean>
    </beans>
    View Code
    package springcode.aop.spring;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-25 23:01
    * @notify 
    * @version 1.0
    */
    public interface IOther {
         void doOther();
    }
    View Code

    JDK代理和CGLib代理都是动态代理

    在上边我们的Target目标类总是有一个父接口,原因是Spring动态代理有两种实现方式,一种是使用JDK动态代理,JDK代理明确的要想给一个类生成代理对象,这个类必须要有父接口。如果没有父接口的类,则需要通过CGLib动态代理。他们两个都是动态代理也就是在运行时给类生成代理对象,注意是运行时!通俗讲JDK动态代理是通过接口,给目标类创建了一个新的兄弟类。而CGLib代理则是通过目标类创建了一个子类。类Target2并没有父接口,也可以成功生成代理对象。原因是Spring会根据目标类本身有没有父接口,自动的选择使用JDK动态代理还是CGLib动态代理。测试代码System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\class");设置CGLib动态生成的类存放位置。可以看出,对于Target目标类并没有使用CGLib动态代理,只有Target2才使用了。

    package springcode.aop.spring;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-26 11:39
    * @notify 
    * @version 1.0
    */
    public class Target2 {
        public void show() {
            System.out.println("我是目标类2的show方法");
        }
    }
    View Code
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.BeforeAdvice" id="beforeAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="advice">
                <ref bean="beforeAdvice"/>
            </property>
            <property name="patterns">
                <list>
                    <value>.*show</value>
                    <value>.*speak</value>
                    <value>.*exc</value>
                </list>
            </property>
        </bean>
    
    
    
        <bean class="springcode.aop.spring.Target" id="targetBean"></bean>
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean"></property>
            <property name="interceptorNames">
                <list>
                    <value>pointcut1</value>
                </list>
            </property>
        </bean>
    
        <bean class="springcode.aop.spring.Target2" id="targetBean2"></bean>
    
        <bean id="target2" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean2"></property>
            <property name="interceptorNames">
                <list>
                    <value>pointcut1</value>
                </list>
            </property>
        </bean>
    </beans>
    View Code
     @Test
        public void t8() {
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\class");
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:springresource/cglib.xml");
            TargetInte target = (TargetInte) context.getBean("target");
            target.show();
    
            Target2 target2 = (Target2) context.getBean("target2");
            target2.show();
        }
    View Code

    当然对于有父接口的目标类,我们也可以强制的让它使用CGLib代理。ProxyFactoryBeanproxyTargetClass属性设置为true则强制使用CGLib代理。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.BeforeAdvice" id="beforeAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="advice">
                <ref bean="beforeAdvice"/>
            </property>
            <property name="patterns">
                <list>
                    <value>.*show</value>
                    <value>.*speak</value>
                    <value>.*exc</value>
                </list>
            </property>
        </bean>
    
    
    
        <bean class="springcode.aop.spring.Target" id="targetBean"></bean>
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean"></property>
            <property name="proxyTargetClass" value="true"/>
            <property name="interceptorNames">
                <list>
                    <value>pointcut1</value>
                </list>
            </property>
        </bean>
    
        <bean class="springcode.aop.spring.Target2" id="targetBean2"></bean>
    
        <bean id="target2" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="targetBean2"></property>
            <property name="interceptorNames">
                <list>
                    <value>pointcut1</value>
                </list>
            </property>
        </bean>
    </beans>
    View Code

    6 一次生成多个代理对象

    有没有发现一个问题,ProxyFactoryBean虽然它可以指定多个拦截器,一次只能给我们生成一个代理对象。BeanNameAutoProxyCreator则完全的消除这个弊端,BeanNameAutoProxyCreator允许我们匹配bean的Name从而确定我们需要几个代理对象。属性beanNames的值设置为target*,则代表所有target开头的bean都会生成代理对象。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.BeforeAdvice" id="beforeAdvice"></bean>
    
        <bean id="pointcut1"
              class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="advice">
                <ref bean="beforeAdvice"/>
            </property>
            <property name="patterns">
                <list>
                    <value>.*show</value>
                </list>
            </property>
        </bean>
    
        <bean class="springcode.aop.spring.Target" id="target"></bean>
    
        <bean class="springcode.aop.spring.Target2" id="target2"></bean>
        <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
            <property name="beanNames" value="targe*"/>
            <property name="interceptorNames">
                <list>
                    <value>pointcut1</value>
                </list>
            </property>
        </bean>
    
    </beans>
    View Code

    7 根据切入点生成代理对象

    思考一个问题,我们配置的RegexpMethodPointcutAdvisor有了目标类和目标方法的匹配规则,然后使用ProxyFactoryBean或者BeanNameAutoProxyCreator确定给哪个类做代理对象,然后确定使用什么拦截器。如果我们换个思考方式,只要是RegexpMethodPointcutAdvisor匹配的目标类和目标方法我们就想生成代理类。类DefaultAdvisorAutoProxyCreator则会自动的收集RegexpMethodPointcutAdvisor匹配到的类,并生成代理对象。在配置文件中我们配置了两个RegexpMethodPointcutAdvisor,第一个是前置通知对show和speak方法进行匹配,第二个则是后置通知只对show方法进行匹配。DefaultAdvisorAutoProxyCreator我们不设置任何的属性。可以看到结果,show方法有前置和后置通知,而speak方法只有前置通知。不管是Target目标类还是Target2目标类,都生成了代理对象。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.BeforeAdvice" id="beforeAdvice"></bean>
    
        <bean id="pointcut1"
              class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="advice">
                <ref bean="beforeAdvice"/>
            </property>
            <property name="patterns">
                <list>
                    <value>.*show</value>
                    <value>.*speak</value>
                </list>
            </property>
        </bean>
    
        <bean class="springcode.aop.spring.AfterAdvice" id="afterAdvice"></bean>
    
        <bean id="pointcut2"
              class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="advice">
                <ref bean="afterAdvice"/>
            </property>
            <property name="patterns">
                <list>
                    <value>.*show</value>
                </list>
            </property>
        </bean>
    
        <bean class="springcode.aop.spring.Target" id="target"></bean>
        <bean class="springcode.aop.spring.Target2" id="target2"></bean>
    
        <bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
        </bean>
    
    </beans>
    View Code

    8 编程方式创建代理对象

    同样的SpringAOP允许我们使用java代码的方式创建代理对象。仔细观察你会发现xml配置方式使用的是ProxyFactoryBean类而java代码ProxyFactory类除此之外他们几乎没有任何的差别。重要的在于我们怎样理解SpringAop提供的组件,又怎么把组件连接起来。

    9 TargetSource

    TargetSource为了使代理对象有更多的原则操作而诞生的,简单说生成代理对象这一个操作,我们可以对他进行装饰。如果我们不设置TargetSource,默认的使用EmptyTargetSource。TargetSource的实现类有很多,这里我们只看文档上提及的几个。

    1 HotSwappableTargetSource

    作用可动态的切换目标源,注意!!!ProxyFactoryBean必须要是scope="prototype"否则会出问题的。在下面xml代码中,ProxyFactoryBean设置target,而是设置了一个targetSource,而在targetSource中设置了target。获取HotSwappableTargetSource的bean然后调用swap()方法,这里需要传递一个对象,需要替换的对象。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.BeforeAdvice" id="beforeAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="advice">
                <ref bean="beforeAdvice"/>
            </property>
            <property name="patterns">
                <list>
                    <value>.*show</value>
                    <value>.*speak</value>
                    <value>.*exc</value>
                </list>
            </property>
        </bean>
    
    
        <bean class="springcode.aop.spring.Target2" id="targetBean2"></bean>
    
        <bean id="hotSwappableTargetSource" class="org.springframework.aop.target.HotSwappableTargetSource">
            <constructor-arg ref="targetBean2"/>
        </bean>
    
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean" scope="prototype">
            <property name="targetSource" ref="hotSwappableTargetSource"/>
            <property name="interceptorNames">
                <list>
                    <value>pointcut1</value>
                </list>
            </property>
        </bean>
    
    </beans>
    View Code
    package springcode.aop.spring;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-25 18:56
     * @notify 被增强的目标类
     * @version 1.0
     */
    
    public class Target3  {
    
    
        public void show() {
            System.out.println("我是目标类3的show方法");
        }
    
    
        public void speak() {
            System.out.println("我是目标方法3的speak方法");
        }
    
        public void exc() {
            int i = 1 / 0;
        }
    }
    View Code
    package springcode.aop.spring;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-26 11:39
    * @notify 
    * @version 1.0
    */
    public class Target2 {
        public void show() {
            System.out.println("我是目标类2的show方法");
        }
    }
    View Code

    从源码中看,道理很简单。所以一定要把ProxyFactoryBean设置为原型,如果是单例bean被Spring存放到单例池里,那下次在getBean()还是同一个,这样根本无法切换目标源。

    PrototypeTargetSource

    一般的我们创建的代理对象都是单例的,PrototypeTargetSource帮我们创建不同实例的代理对象。Target目标类需要设置scope="prototype"

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="springcode.aop.spring.BeforeAdvice" id="beforeAdvice"></bean>
    
        <bean id="pointcut1" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="advice">
                <ref bean="beforeAdvice"/>
            </property>
            <property name="patterns">
                <list>
                    <value>.*show</value>
                    <value>.*speak</value>
                    <value>.*exc</value>
                </list>
            </property>
        </bean>
    
    
        <bean class="springcode.aop.spring.Target2" id="targetBean2" scope="prototype"></bean>
    
    <!--        <bean id="hotSwappableTargetSource" class="org.springframework.aop.target.HotSwappableTargetSource">-->
    <!--            <constructor-arg ref="targetBean2"/>-->
    <!--        </bean>-->
    
        <bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
            <property name="targetBeanName" value="targetBean2"/>
        </bean>
    
        <bean id="target" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="targetSource" ref="prototypeTargetSource"/>
            <property name="interceptorNames">
                <list>
                    <value>pointcut1</value>
                </list>
            </property>
        </bean>
    
    </beans>
    View Code

    AspectJ风格的SpringAOP

    1 Aspect风格和Aspect编译器

    Aspect风格的AOP还是SpringAOP使用JDK动态代理或者是CGLib动态代理,只是注解和xml方式和Aspect编译器类似,Aspect编译器是静态代理。动态代理在类运行时创建新的代理类,静态代理在编译时改变类的字节码,不会有新的Class产生。Spring支持使用Aspect编译器,下边会讲到。

    2 一个简单的入门

    SpringBoot项目导入AOP包,创建一个目标类,这个目标类没有父接口,意味着使用的CGLib动态代理。定义一个Advice类,@Componnet和@Aspect注解不可少。方法pointcut()是一个空方法,方法上使用@Pointcut注解声明目标类和切入点。方法before()上注解@Before值为任意一个被@Pointcut注解的方法。这样就完成了AOP的代理对象的操作,有点简单哦。

        <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    View Code
    package springcode.aop.aspectj;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 14:39
    * @notify 
    * @version 1.0
    */
    
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    
    @Component
    public class Person {
        public void show()throws Exception {
            System.out.println("开始我的表演");
        }
    }
    View Code
    package springcode.aop.aspectj;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 14:27
    * @notify 
    * @version 1.0
    */
    
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    import org.aspectj.lang.annotation.Aspect;
    
    @Component
    @Aspect
    public class Aspectj1 {
    
        //返回类型模式、方法名模式和参数模式
        @Pointcut(value = "execution(public * springcode.aop..show(..)throws Exception)")
        public void pointcut(){}
    
    
        @Before(value = "pointcut()")
        public void before(){
            System.out.println("准备表演");
        }
    }
    View Code

    3 支持的切入点匹配器

    @Pointcut注解的value值,Spring最多的支持9中匹配模式。execution、within、this、target、args、@target、@args、@within、@annotation最常用的还是execution

    execution:用于匹配方法执行连接点。这是使用Spring AOP时要使用的主要切入点指示器。格式为 方法修饰符 返回值类型 包名类名(参数列表)异常类型。其中返回类型模式、方法名模式和参数模式是不可少的,其他的都可以省略。@Pointcut(value = "execution( * *show(..))") 代表任意方法修饰符,任意返回值类型,任意包下以show结尾方法参数任意,异常类型任意的都匹配。

    4 切入点组合

    && || !组合多个切入点匹配器使用 !的表达式不知道怎么用Q_Q

    package springcode.aop.aspectj;/*
    * @auther 顶风少年
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 14:27
    * @notify
    * @version 1.0
    */
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class Aspectj2 {
    
    
        @Pointcut(value = "execution(* show(..))")
        public void pointcut(){}
    
    
        @Pointcut(value = "execution(* rrrshow(..))")
        public void pointcut2(){}
    
        @Pointcut(value = "pointcut()&&pointcut2()")
        public void pointcut3(){}
    
        @Pointcut(value = "pointcut()||pointcut2()")
        public void pointcut4(){}
        
        @Before(value = "pointcut4()")
        public void before(){
            System.out.println("准备表演");
        }
    }
    View Code

    5 注解方式的连接点

    @Before

    前置通知@Before,连接点的value可以给切入点的方法名,也可以直接写匹配器。

    package springcode.aop.aspectj;/*
    * @auther 顶风少年
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 14:27
    * @notify
    * @version 1.0
    */
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class Aspectj3 {
        @Before(value = "execution(* show(..))")
        public void before(){
            System.out.println("准备表演");
        }
    }
    View Code

    @After

    @After后置通知,无论目标方法是否有异常,该通知都会执行。可以当做finally使用。

    package springcode.aop.aspectj;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-27 14:27
     * @notify
     * @version 1.0
     */
    
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class Aspectj6 {
        @After(value = "execution(* show2(..))")
        public void before() {
            System.out.println("after");
        }
    }
    View Code
    package springcode.aop.aspectj;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 14:39
    * @notify 
    * @version 1.0
    */
    
    import org.springframework.stereotype.Component;
    
    
    @Component
    public class Person2 {
        public int show()throws Exception {
            System.out.println("person2.show()");
            return 100;
        }
    
        public void show2(){
            int i = 1/0;
        }
    }
    View Code

    @AfterReturning

    后置通知@AfterRetruning可以获取目标对象方法执行后的返回值。需要注意的是returning的值必须和连接点的参数名一样。和@After不同的是,@AfterReturning只能在没有异常时才会执行。

    package springcode.aop.aspectj;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 14:39
    * @notify 
    * @version 1.0
    */
    
    import org.springframework.stereotype.Component;
    
    
    @Component
    public class Person2 {
        public int show()throws Exception {
            System.out.println("person2.show()");
            return 100;
        }
    }
    View Code
    package springcode.aop.aspectj;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-27 14:27
     * @notify
     * @version 1.0
     */
    
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class Aspectj4 {
        @AfterReturning(value = "execution(* show(..))", returning = "r")
        public void before(Object r) {
            System.out.println("after--返回值是:"+r);
        }
    }
    View Code

    @AfterThrowing

    @AfterThrowing发生在异常后,如果要指定异常类型,则可以在参数中声明要拦截的异常,抛出的异常是拦截异常或其子类则可以拦截。

    package springcode.aop.aspectj;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-27 14:27
     * @notify
     * @version 1.0
     */
    
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    
    @Component
    @Aspect
    public class Aspectj5 {
        @AfterThrowing(value = "execution(* show2(..))",throwing = "e")
        public void before(Exception e) {
            System.out.println("throwing");
        }
    }
    View Code
    package springcode.aop.aspectj;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 14:39
    * @notify 
    * @version 1.0
    */
    
    import org.springframework.stereotype.Component;
    
    
    @Component
    public class Person2 {
        public int show()throws Exception {
            System.out.println("person2.show()");
            return 100;
        }
    
        public void show2(){
            int i = 1/0;
        }
    }
    View Code

    @Around

    在目标类目标方法的开始和结束处增强,当然也可以使用catch将异常捕获,当做finally使用。

    package springcode.aop.aspectj;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-27 14:27
     * @notify
     * @version 1.0
     */
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class Aspectj7 {
        @Around(value = "execution(* show*(..))")
        public Object around(ProceedingJoinPoint pjp) {
            Object proceed = null;
            try {
                System.out.println("around1");
                proceed = pjp.proceed();
                System.out.println("around2");
                return proceed;
            } catch (Throwable e) {
                System.out.println("异常了");
                return proceed;
            }
    
        }
    }
    View Code
    package springcode.aop.aspectj;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 14:39
    * @notify 
    * @version 1.0
    */
    
    import org.springframework.stereotype.Component;
    
    
    @Component
    public class Person2 {
        public int show()throws Exception {
            System.out.println("person2.show()");
            return 100;
        }
    
        public void show2(){
            int i = 1/0;
        }
    }
    View Code

    6 连接点的通用参数

    所有的注解连接点都可以带一个参数,JoinPoint,除了@Around注解除外,其他的都需要将该参数放到参数列表的第一个位置。通过该参数,可以获取到目标对象,和代理对象的信息。

    package springcode.aop.aspectj;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-27 21:05
     * @notify
     * @version 1.0
     */
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Person3 {
    
        public void add(Integer arg1, Integer arg2) {
            System.out.println(arg1 + arg2);
        }
    }
    View Code
    package springcode.aop.aspectj;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 21:03
    * @notify 
    * @version 1.0
    */
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    
    @Component
    @Aspect
    public class Aspectj8 {
    
        @Before(value = "execution(* add(..))")
        public void before(JoinPoint joinPoint){
            //获取原对象的参数
            Object[] args = joinPoint.getArgs();
            for (Object o:args){
                System.out.println(o);
            }
            //获取原对象
            Object target = joinPoint.getTarget();
            System.out.println(target);
            System.out.println("before");
        }
    }
    View Code

    7 XML方式使用AOP

    xml配置方式aop配置都在<aop:config>标签内<aop:pointcut expression="execution(* *(..))" id="anyMethod"/> 声明切入点匹配器。<aop:aspect>引用一个Advice的bean在内部可配置连接点。连接点既可以直接使用pointcut匹配器的id,也可以自己配置pointcut。同样的,Advice方法都可以添加JoinPoint作为参数。

    package springcode.aop.xmlaspectj;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-27 22:01
     * @notify
     * @version 1.0
     */
    
    public class User {
        public String name = "张三";
        public void add() {
            System.out.println("添加用户");
        }
    
        public void update() {
            System.out.println("修改用户");
        }
    
        public void delete() {
            System.out.println("删除用户");
        }
    
        public User getUser() {
            return this;
        }
    }
    View Code
    package springcode.aop.xmlaspectj;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-27 22:03
     * @notify
     * @version 1.0
     */
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    
    public class UserAdvice {
    
        public void before(JoinPoint joinPoint) {
            User target = (User)joinPoint.getTarget();
            System.out.println(target.name);
            System.out.println("before");
        }
    
        public void after() {
            System.out.println("after");
        }
    
        public void afterReturn(User user) {
            System.out.println(user.name);
            System.out.println("afterReturn");
        }
    
        public void thro() {
            System.out.println("throws");
        }
    
        public Object around(ProceedingJoinPoint proceedingJoinPoint) {
            Object object = null;
            try {
                System.out.println("around1");
                object = proceedingJoinPoint.proceed();
                System.out.println("around2");
                return object;
            } catch (Throwable e) {
                System.out.println("around3");
                return object;
            }
        }
    }
    View Code
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 配置切面类 bean -->
        <bean id="advice" class="springcode.aop.xmlaspectj.UserAdvice"></bean>
        <!-- 配置目标类 bean -->
        <bean id="user" class="springcode.aop.xmlaspectj.User"></bean>
    
        <!-- aop 配置 -->
        <aop:config>
            <!-- 自定义切入点 -->
            <aop:pointcut expression="execution(* *(..))" id="anyMethod"/>
    
            <aop:aspect ref="advice">
                <!-- 前置通知 -->
                <aop:before method="before" pointcut-ref="anyMethod"/>
                <!-- 最终通知 -->
                <aop:after method="after" pointcut-ref="anyMethod"/>
                <!-- 后置通知 -->
                <aop:after-returning method="afterReturn" pointcut="execution(* getUser(..))" returning="user"/>
                <!-- 环绕通知 -->
                <aop:around method="around" pointcut-ref="anyMethod"/>
                <!-- 异常抛出通知 -->
                <aop:after-throwing method="thro" pointcut-ref="anyMethod"/>
            </aop:aspect>
        </aop:config>
    
    </beans>
    View Code

    8 给目标对象增加接口

    注解方式使用注解@DeclareParents被注解的必须是一个接口,value指定代理对象类型匹配器,defaultImpl必须是接口的子类。xml配置方式类似。

    注解方式

    package springcode.aop.addInte;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 22:49
    * @notify 
    * @version 1.0
    */
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Phone {
        public void call(){
            System.out.println("喂喂喂打电话");
        }
    }
    View Code
    package springcode.aop.addInte;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 22:50
    * @notify 
    * @version 1.0
    */
    
    import org.springframework.stereotype.Component;
    
    
    public interface Computer {
        public void play();
    }
    View Code
    package springcode.aop.addInte;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-27 22:50
    * @notify 
    * @version 1.0
    */
    
    import org.springframework.stereotype.Component;
    
    
    public class DEComputer implements Computer{
        public void play(){
            System.out.println("啪啪啪,我在写代码。。");
        }
    }
    View Code
    package springcode.aop.addInte;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-27 22:51
     * @notify
     * @version 1.0
     */
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.DeclareParents;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class AdviceInte {
        @DeclareParents(value = "springcode.aop.addInte.Phone", defaultImpl = DEComputer.class)
        public  Computer computer;
    }
    View Code

    XML配置方式

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <aop:config>
            <aop:aspect >
                <aop:declare-parents
                        types-matching="springcode.aop.addInte.Phone"
                        implement-interface="springcode.aop.addInte.Computer"
                        default-impl="springcode.aop.addInte.DEComputer"
                />
            </aop:aspect>
        </aop:config>
        <!-- 目标类 -->
        <bean id="phone" class="springcode.aop.addInte.Phone"></bean>
    </beans>
    View Code

    真正的AspectJ编译器

    本类调用为什么会失效

    类DB封装了一个List集合,DBTransaction是一个普通的注解类。通知类DBTransactionAspect中声明连接点匹配规则是@Pointcut("@annotation(springcode.aop.transactiontest.DBTransaction)")这意味着,任何使用@DBTransaction的方法都将会被拦截。around()是一个环绕通知,当被拦截的方法内部抛出异常,则删掉List中最后一条数据。Service类中的add方法,是业务类,它模拟向数据库中添加数据,当参数是true是则会抛出异常。

    结果是我们想要看到的,这其实就是模拟一个事务。

    package springcode.aop.transactiontest;
    
    import java.lang.annotation.*;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DBTransaction {
    }
    View Code
    package springcode.aop.transactiontest;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-28 14:53
    * @notify 
    * @version 1.0
    */
    
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    
    @Component
    public class DB {
        public ArrayList<String> mysql = new ArrayList<>();
    }
    View Code
    package springcode.aop.transactiontest;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-28 15:26
     * @notify
     * @version 1.0
     */
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class DBTransactionAspect {
        @Autowired
        private DB db;
    
        @Pointcut("@annotation(springcode.aop.transactiontest.DBTransaction)")
        public void Pointcut() {
        }
    
        @Around("Pointcut()")
        public void around(ProceedingJoinPoint pjp) {
            Object[] args = pjp.getArgs();
            for (Object s : args) {
                String o = (String) s;
                System.out.println("目标对象方法的参数--"+o);
            }
            Object object = null;
            try {
                System.out.println("事务开始");
                object = pjp.proceed();
                System.out.println("事务提交");
            } catch (Throwable throwable) {
                db.mysql.remove(db.mysql.size() - 1);
                System.out.println("事务回滚");
            }
    
        }
    }
    View Code
    package springcode.aop.transactiontest;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-28 14:56
     * @notify
     * @version 1.0
     */
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Service {
        @Autowired
        private DB db;
    
        @DBTransaction
        public void add(String flag, String s) {
            System.out.println("啊我是一个平凡的service,但是我的使命就是要添加一个字符串到db中");
            db.mysql.add("哈哈哈哈");
            if (flag.equals("true")) {
                int i = 1 / 0;
            }
        }
    }
    View Code

    现在业务变动了,在Service中新增了一个方法client()所有的客户端只能通过它访问add()方法。此时我们发现,无论抛不抛异常add()方法都没有被拦截。这个问题很关键,所以我首先贴出来官方的解释,然后在拼凑自己的理解。

    The key thing to understand here is that the client code inside the main(..) method of the Main class has a reference to the proxy. This means that method calls on that object reference are calls on the proxy. As a result, the proxy can delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object (the SimplePojo, reference in this case), any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.

    package springcode.aop.transactiontest;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-28 14:56
     * @notify
     * @version 1.0
     */
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Service {
        @Autowired
        private DB db;
    
        public void client(String flag, String s) {
            add(flag, "add");
        }
    
        @DBTransaction
        public void add(String flag, String s) {
            System.out.println("啊我是一个平凡的service,但是我的使命就是要添加一个字符串到db中");
            db.mysql.add("哈哈哈哈");
            if (flag.equals("true")) {
                int i = 1 / 0;
            }
        }
    }
    View Code

    当Service调用client时,它发现,没有任何一个拦截器是拦截clinet(),所以他没有走代理对象,走的只是Service.class。然后Service对象调用自己的add()当然也不会走代理对象!那么原因就是client()方法使用的不是代理对象的client()。

    接下来我们试试这种。

    package springcode.aop.transactiontest;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-28 14:56
     * @notify
     * @version 1.0
     */
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Service {
        @Autowired
        private DB db;
    
        @DBTransaction
        public void client(String flag, String s) {
            add(flag, "add");
        }
    
        @DBTransaction
        public void add(String flag, String s) {
            System.out.println("啊我是一个平凡的service,但是我的使命就是要添加一个字符串到db中");
            db.mysql.add("哈哈哈哈");
            if (flag.equals("true")) {
                int i = 1 / 0;
            }
        }
    }
    View Code

    此时事务虽然生效了,但是并不是add()方法的事务,真正被拦截的是client(),也就是说,在代理类的client中走的是目标类自己的add()。这也就是官方说明的一点,关于所有的this引用都指向的是目标类本身,而非代理对象。对于这种问题的方式官方给出了解决办法。

    首先将AopContext上下文暴露出来。@EnableAspectJAutoProxy(exposeProxy=true)然后目标对象中创建代理对象。这种方式粗暴简单说,我要用的就是你的代理对象,没毛病。但是一旦我们换了AOP不用SpringAop了,这种方式肯定是报错的。

    package springcode.aop.transactiontest;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-28 14:56
     * @notify
     * @version 1.0
     */
    
    import org.springframework.aop.framework.AopContext;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Service {
        @Autowired
        private DB db;
    
        public void client(String flag, String s) {
            ((Service) AopContext.currentProxy()).add(flag, "add");
        }
    
    
        @DBTransaction
        public void add(String flag, String s) {
            System.out.println("啊我是一个平凡的service,但是我的使命就是要添加一个字符串到db中");
            db.mysql.add("哈哈哈哈");
            if (flag.equals("true")) {
                int i = 1 / 0;
            }
        }
    }
    View Code

    然后,还有一种方式。既然this是本类对象的方法,那么我不用this。手动注入一个本类,通过Spring容器,那么你就要乖乖走代理对象。

    package springcode.aop.transactiontest;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-28 14:56
     * @notify
     * @version 1.0
     */
    
    import org.springframework.aop.framework.AopContext;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Service {
        @Autowired
        private DB db;
    
        @Autowired
        private Service service;
    
        public void client(String flag, String s) {
           // ((Service) AopContext.currentProxy()).add(flag, "add");
            service.add(flag, "add");
        }
    
    
        @DBTransaction
        public void add(String flag, String s) {
            System.out.println("啊我是一个平凡的service,但是我的使命就是要添加一个字符串到db中");
            db.mysql.add("哈哈哈哈");
            if (flag.equals("true")) {
                int i = 1 / 0;
            }
        }
    }
    View Code

    AspectJ编译器体验

    首先我要先吐槽下Spring文档,它对于AspectJ编译器真的一点都不友好!!!这一个Demo是找了好多资料拼拼凑凑完成的。只为了填本类调用的坑!下面代码片段是重新开的项目SpringBoot2.2.4。JDK8。编译器使用的IDEA。这里依然有两个问题我没有解决1、生成的代理Class在哪放?2、为什么@Component注解会失效!

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.4.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.datang</groupId>
        <artifactId>demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>aspectj</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <argLine>
                            -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                            -javaagent:"${settings.localRepository}/org/springframework/spring-instrument/5.2.3.RELEASE/spring-instrument-5.2.3.RELEASE.jar"
                            <!-- -Dspring.profiles.active=test-->
                        </argLine>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <agent>
                            ${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar
                        </agent>
                        <agent>
                            ${settings.localRepository}/org/springframework/spring-instrument/5.2.3.RELEASE/spring-instrument-5.2.3.RELEASE.jar
                        </agent>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    View Code

    pom文件需要注意的是aspectjweaver和spring-instrument这两个jar。位置不要照着写。

    package com.datang.demo.aspectj;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-28 15:26
     * @notify
     * @version 1.0
     */
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.beans.factory.annotation.Autowired;
    
    @Aspect
    public class DBTransactionAspect {
    
        @Autowired
        private DB db;
    
        @Pointcut("@annotation(com.datang.demo.aspectj.DBTransaction)&&execution(* *(..)) ")
        public void Pointcut() {
        }
    
        @Around("Pointcut()")
        public void around(ProceedingJoinPoint pjp) {
            Object[] args = pjp.getArgs();
            for (Object s : args) {
                String o = (String) s;
                System.out.println("目标对象方法的参数--"+o);
            }
            Object object = null;
            try {
                System.out.println("事务开始");
                object = pjp.proceed();
                System.out.println("事务提交");
            } catch (Throwable throwable) {
                System.out.println(db==null);
                db.mysql.remove(db.mysql.size() - 1);
                System.out.println("事务回滚");
            }
    
        }
    }
    View Code

    通知类此处还有坑@Component失效!所以我没有加。其次@Pointcut后边也加入了一段这个是为了应对AspectJ的BUG,如果不加在本类调用会被AOP两次!

    package com.datang.demo.aspectj;/*
     * @auther 顶风少年
     * @mail dfsn19970313@foxmail.com
     * @date 2020-01-28 14:56
     * @notify
     * @version 1.0
     */
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Service {
        @Autowired
        private DB db;
    
    
        public void client(String flag, String s) {
            this.add(flag, "add....");
        }
    
    
        @DBTransaction
        public void add(String flag, String s) {
            System.out.println("啊我是一个平凡的service,但是我的使命就是要添加一个字符串到db中");
            db.mysql.add("哈哈哈哈");
            if (flag.equals("true")) {
                int i = 1 / 0;
            }
        }
    }
    View Code
    package com.datang.demo.aspectj;
    
    import java.lang.annotation.*;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DBTransaction {
    }
    View Code

    Service和注解类不变

    package com.datang.demo.aspectj;/*
    * @auther 顶风少年 
    * @mail dfsn19970313@foxmail.com
    * @date 2020-01-29 14:37
    * @notify 
    * @version 1.0
    */
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableLoadTimeWeaving;
    
    import static org.springframework.context.annotation.EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT;
    
    @Configuration
    @ComponentScan("com.datang.demo.aspectj")
    @EnableLoadTimeWeaving(aspectjWeaving=AUTODETECT)
    public class CustomLtwConfig {
    }
    View Code

    此处用了个Spring配置类,主要为了配置@EnableLoadTimeWeaving(aspectjWeaving=AUTODETECT)这个玩意,启动AspectJ的XML配置。

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
    <aspectj>
    
        <!--要织入切面的目标类-->
        <weaver>
            <include within="com.datang.demo.aspectj..*" />
        </weaver>
        <!--切面类-->
        <aspects>
            <aspect name="com.datang.demo.aspectj.DBTransactionAspect" />
        </aspects>
    </aspectj>
    View Code

    这个文件的位置不要放错

     

     

  • 相关阅读:
    E: 无法获得锁 /var/lib/dpkg/lock-frontend
    Ubuntu 18.04 更换apt源
    ubuntu18.04
    小a与“204”------数列、排序
    星际穿越
    合唱团---DP
    分苹果---暴力
    地牢逃脱----DFS搜索最优解
    下厨房---map/字符串查询
    hdu 2654 Be a hero
  • 原文地址:https://www.cnblogs.com/zumengjie/p/12233331.html
Copyright © 2011-2022 走看看