Spring核心技术详解(四)

Spring AOP详解

  AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。面向切面编程这个概念一直被很多人诟病,因为它和IoC一样晦涩,不太容易理解。


1、AOP到底是什么

  我们先来看看以前的OOP(面向对象编程)解决的问题。按照软件重构思想的理念,如果多个类中出现了相同的代码,应该考虑定义一个共同的抽象类,将这些代码提取到抽象类中。

  例如dog,cat这些动物对象都应该有eat()、run()等功能,所以我们通过引入包含这两个方法抽象的Animal父类,然后dog、cat这些类通过继承复用eat()、run()等功能。我们大多数情况通过引入父类来消除多个类中重复的代码,但有的时候并没这么简单,我们看下面的例子。

    public class Dog implements Animal {

        public void run() {
            System.out.println("主人发出命令");
            System.out.println("跑");
            System.out.println("主人给予奖励");
        }

        public void eat() {
            System.out.println("主人发出命令");
            System.out.println("吃");
            System.out.println("主人给予奖励");

        }

    }

  仔细看上面的代码,我们会发现run()和eat()方法中有很大一部分重复代码,这些代码无法完全提取出来,这些情况会发生在很多地方,如性能监测访问控制事务管理日志记录等。

  假设我们将Dog类看成一颗圆木,eat()和run()方法堪称圆木上的一截,我们会发现那些重复代码(事务开启和关闭、主人发出命令和给予奖励)就像一圈年轮,真正的业务代码是树心,这就是这些重复代码被称为横切代码概念的由来。

  我们无法通过抽象父类的方式消除以上所述的重复性横切代码,因为这些横切代码依附在具体的业务方法中。AOP独辟蹊径通过横向抽取机制为这类无法通过纵向继承的重复性代码提供了解决方案,AOP希望将这些分散在业务逻辑中的相同代码,通过横向切割的方式抽取到一个独立模块中,我们知道将这些重复性的横切代码提取出来是很容易的,但如何将这些独立的模块融合到业务逻辑中完成和以前一样的操作,这才是关键,也是AOP解决的主要问题。



2、AOP术语

  • 连接点(Joinpoint):程序执行的某个特定位置:如类初始化前,类初始化后,某个方法调用前,方法调用后,方法抛出异常后。一个类或一段代码拥有一些具有边界性质的特定点,就被称为 “连接点”。Spring仅支持方法的连接点,仅能在方法调用前后、方法抛出异常时这些连接点织入增强。

  • 切点(Pointcut):每个程序都拥有很多连接点,如有一个两个方法的类,这两个方法都是连接点。如何定位到某个连接点上呢?AOP通过“切点”定位特定的连接点。例如:数据库记录是连接点,切点就是查询条件,用来定位特定的连接点。在Spring中,切点通过Pointcut接口描述,使用类和方法作为连接点的查询条件,Spring AOP的解析引擎负责解析切点设定的条件,找到对应的连接点。连接点是方法执行前后等具体程序执行点,而切点只定位到某个方法上,所以如果定位到具体的连接点,还需要提供方位信息,即方法执行前还是执行后。

  • 增强/通知(Advice):增强是织入到目标类连接点上的一段程序代码。在Spring中,增强除了描述一段程序代码外,还拥有另一个和连接点相关的信息,就是执行点的方位。结合方位信息和切点信息,就可以找到特定的连接点。Spring提供的增强接口都是带方位名的:BeforeAdvice,AfterRetuningAdvice,ThrowsAdvice等。所以只有结合切点和增强,才能确定特定的连接点并织入增强代码。

  • 目标对象(Target):需要织入增强代码的目标类。

  • 引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使某个类没有实现某个接口,通过AOP的引介功能,我们可以动态的为该类添加接口的实现逻辑,让该类成为这个接口的实现类。

  • 织入(Weaving):织入是将增强添加到目标类上具体连接点的过程。

  • 代理(Proxy):一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。

  • 切面(Aspect):切面由切点和增强组成,既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面,将定义的横切逻辑织入到切面指定的连接点中。



3、AOP基础知识

AOP的工作重心在于如何将增强应用于目标对象的连接点上,包括两个工作。

  • 第一:如何通过切点和增强定位到连接点

  • 第二,如何在增强中编写切面的代码。

  Spring AOP使用动态代理技术在运行期织入增强的代码。Spring AOP使用了两种代理机制,一种是基于JDK的动态代理;另一种是基于CGLib的动态代理,之所以使用两种代理机制,很大程度上是因为JDK本身只提供接口的代理,不支持类的代理。



4、动态代理

JDK动态代理

我们以前学习过JDK动态代理,这里我们来演示一个前面的例子。

    //动物接口
    public interface Animal {
        public void run();
        public void eat();

    }
    //狗的实现类
    public class Dog implements Animal {

        public void run() {
            System.out.println("跑");
        }

        public void eat() {
            System.out.println("吃");
        }

    }
    //现在我们需要在run()和eat()方法前后加一些动作,这些动作是相同的,所以我们提取出来 
    public class MyAspect {
        public void before(){
            System.out.println("主人发出命令");
        }

        public void after(){
            System.out.println("主人给予奖励");
        }
    }
    //现在我们需要使用动态代理将增强逻辑和方法编织在一起 
    public class MyProxyFactory {
        public  static Animal createDog(){
            final Animal dog=new Dog();
            final MyAspect aspect=new MyAspect(); 
            //生成代理对象
            Animal dogproxy=(Animal)  Proxy.newProxyInstance(MyProxyFactory.class.getClassLoader(), dog.getClass().getInterfaces(), new InvocationHandler() {

                @Override
                public Object invoke(Object proxy, Method method, Object[] arg2) throws Throwable {
                    aspect.before();
                    Object obj=method.invoke(dog, arg2); 
                    aspect.after();
                    return obj;
                }
            });
            return dogproxy;
        }

    }
    //我们来进行测试
    public class TestJDK {
        @Test
        public void test(){
            Animal animal=MyProxyFactory.createDog();
            animal.eat();
        }

    }

运行结果:
这里写图片描述

我们发现程序的运行结果和直接在方法里面编写重复代码的效果是一样的,但是重复代码已经被我们提取到了某个类中。


CGLib动态代理

我们以前讲过,使用JDK动态代理有一个很大的限制,就是只能为接口创建代理实例。 对于没有通过接口定义的类,如何动态创建代理实例呢?CGLib填补了这么空缺。

CGLib采用非常底层的字节码技术可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并织入横切逻辑。但是由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的private、final等方法进行代理

CGLib使用需要导入cglib.jar核心包和asm.jar依赖包,但是Spring 的Core.jar核心包中已经包含了这两个jar包,所以我们直接使用即可。

我们还使用上面的小例子,不过要稍作一些修改。

    //我们还实现一个Dog类,但是不需要实现任何接口
    public class Dog {

        public void run() {
            System.out.println("跑");
        }

        public void eat() {
            System.out.println("吃");
        }

    }
    //把横切逻辑代码提取出来的类
    public class MyAspect {
        public void before(){
            System.out.println("主人发出命令");
        }

        public void after(){
            System.out.println("主人给予奖励");
        }
    }
//我们使用CGLib生成代理对象
public class CglibProxy {

    public static Dog createDog(){  
        //CGLib核心类
        Enhancer enhancer=new Enhancer();
        Dog dog=new Dog();
        MyAspect aspect=new MyAspect();

        //设置父类,因为CGLib底层是生成需要被代理类的子类
        enhancer.setSuperclass(dog.getClass()); 
        //设置回调方法,和InvocationHandler起到的效果一样
        enhancer.setCallback(new org.springframework.cglib.proxy.MethodInterceptor() {

            //intercept方法会拦截所有目标类方法的调用
            //proxy表示目标类的实例,method为目标类方法的反射对象,arg2是方法的参数,arg3是代理类实例
            @Override
            public Object intercept(Object proxy, Method method, Object[] arg2, MethodProxy arg3) throws Throwable {
                aspect.before();
                Object obj=method.invoke(dog, arg2);
                aspect.after();
                return obj;
            }
        });
        //创建代理对象
        Dog dogproxy=(Dog) enhancer.create();
        return dogproxy;
    }

}
    //我们进行测试一下 

    public class TestCGLib {
        @Test
        public void test(){
            Dog dog=CglibProxy.createDog();
            dog.run();
        }

    }

运行结果:
这里写图片描述



5、动态代理存在的问题

Spring AOP的底层就是通过使用JDK动态代理或者CGLib动态代理技术为目标Bean织入横切逻辑。

我们虽然通过JDK动态代理和CGLib完成了横切逻辑的动态织入,但我们的方式存在很多需要改进的地方:

  • 目标类的所有方法都添加了横切逻辑,而有时,我们只想对某些方法添加横切逻辑。

  • 我们通过硬编码的方式指定了织入横切逻辑的连接点,显然不利于修改和维护。

  • 我们纯手工的编写创建代理的过程,过程繁琐,为不同类创建代理时,需要分别编写不同的代码,无法做到通用。

Spring AOP为我们完全解决了上述的问题,Spring AOP通过切点指定在哪些类的哪些方法上织入横切逻辑,通过增强描述横切逻辑代码和方法的具体织入点。Spring通过切面将切点和增强组装起来,通过切面的信息,就可以利用JDK或者CGLib的动态代理技术采用统一通用的方式为Bean创建代理对象。



6、Spring中增强类型

Spring使用增强类定义横切逻辑,同时由于Spring只支持方法连接点,增强还包括了在方法的哪一点织入横切代码的方位信息,所以增强既包括横切逻辑,还包括连接点的部分信息。

AOP联盟为增强定义了Advice接口,Spring支持五种类型的增强:

  • 前置增强:org.springframework.aop.BeforeAdvice 代表前置增强,因为Spring目前只支持方法级的增强,所以MethodBeforeAdvice是目前可用的前置增强,表示在目标方法执行前实施置增强,而BeforeAdvice是为了将来扩展而定义的。

  • 后置增强:org.springframework.aop.AfterReturningAdvice 代表后置增强,表示在目标方法执行后实施增强。

  • 环绕增强:org.aopalliance.intercept.MethodInterceptor 代表环绕增强,表示在目标方法的执行前后实施增强。

  • 异常抛出增强:org.springframework.aop.ThrowsAdvice 代表抛出异常增强,表示在目标方法抛出异常后实施增强。

  • 引介增强:org.springframework.aop.IntroductionInterceptor 代表引介增强,表示在目标类中添加一些新的方法和属性。

这些增强接口都有一些方法,通过实现这些接口的方法,在接口方法中定义横切逻辑,就可以将他们织入到目标类方法的特定连接点

前置增强

我们还使用我们的狗的例子,即简单又生动。我们的狗只有很单一的功能,我们需要在功能执行前增加一些功能。

    //定义动物接口 
    public interface Animal {
        public void run();
        public void eat();

    }
    //狗的实现类 
    public class Dog implements Animal{

        public void run() {
            System.out.println("跑");
        }

        public void eat() {
            System.out.println("吃");
        }

    }
    //我们使实现方法前置增强接口,并写入我们的横切逻辑代码
    public class MyBeforeAdvice implements MethodBeforeAdvice {

        //method为目标类的方法,args为目标类方法的所需参数,obj为目标类实例
        public void before(Method method, Object[] args, Object obj) throws Throwable {
            System.out.println("主人发出命令");

        }

    }
    //我们测试一下
    public class Test {
        @Test
        public void test(){
            Animal dog=new Dog(); 
            BeforeAdvice advice=new MyBeforeAdvice();  
            //Spring提供的代理工厂
            ProxyFactory pf=new ProxyFactory(); 
            //设置代理目标 
            pf.setTarget(dog);
            //为代理目标添加增强
            pf.addAdvice(advice);

            //生成代理对象
            Animal proxy=(Animal)pf.getProxy();
            proxy.run();
        }

    }

运行结果:
这里写图片描述
上述测试中包含有两个关键的步骤:解析ProxyFactory、使用配置文件声明代理。

  • 解析ProxyFactory

在上面的测试中,我们使用ProxyFactory代理工厂将增强Advice织入到目标类Dog中,ProxyFactory内部就是使用JDK动态代理或者CGLib动态代理技术。

  • Spring中定义了AopProxy接口,并且提供了两个final类型的实现类Cglib2AopProxy和JdkDynamicAopProxy。

  • Cglib2AopProxy使用CGLib技术创建代理,JdkDynamicAopProxy通过JDK动态代理技术创建代理。

如果通过ProxyFactory的setInterfaces(Classs[] interfaces)方法指定针对接口进行代理,ProxyFactory就使用JdkDynamicAopProxy,如果是针对类的代理,则使用Cglib2AopProxy。此外还可以通过ProxyFactory的setOptimize(true)方法,让ProxyFactory启动优化处理方式,这样,针对接口的代理也会使用Cglib2AopProxy。

  • 使用配置文件声明代理

虽然通过ProxyFactory已经简化了很多操作,但是Spring给我们提供了通过配置文件来声明一个代理来让免去硬编码

<!--实例化目标对象-->
<bean id="target" class="com.cad.demo.Dog"></bean>  
<!--实例化增强对象-->
<bean id="advice" class="com.cad.demo.MyBeforeAdvice"></bean> 
<!--实例化代理对象-->
<bean id="proxydog" class="org.springframework.aop.framework.ProxyFactoryBean"> 
    <!--指定代理的接口,有多个的话可以使用数组、list等标签-->
    <property name="proxyInterfaces" value="com.cad.demo.Animal"></property> 
    <!--指定使用的增强-->
    <property name="interceptorNames" value="advice"></property> 
    <!--指定代理的目标类-->
    <property name="target" ref="target"></property>
</bean>

ProxyFactoryBean是FactoryBean接口的实现类,我们前一节中介绍了FactoryBean的功能,它负责实例化一个Bean。ProxyFactoryBean负责为其他Bean创建代理对象,内部使用ProxyFactory来完成。我们来了解一下可配置的属性:

  • target:指定要代理的目标对象。

  • proxyInterfaces:代理要实现的接口,可以是多个接口,使用数组、list标签来指定。

  • interceptorNames:需要织入的增强类。 是String[]类型,接受增强Bean的id而不是实例。

  • singleton:返回的代理是否是单实例,默认是单实例。

  • optimize:当设置为true时,强制使用CGLib代理。

  • proxyTargetClass:是否对类进行代理,设置为true,使用CGLib代理。

    //我们测试一下
    public class Test {
        @Test
        public void test(){
            ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml"); 
            Animal dogproxy=(Animal) ac.getBean("proxydog");
            dogproxy.eat();
        }

    }

运行结果:
这里写图片描述
后置增强和前置增强一模一样,不需要过多说明。


环绕增强

环绕增强允许在目标方法调用前后织入增强,综合实现了前置、后置增强两者的功能。

我们还是使用上面小狗的例子。

    //创建环绕增强实现类
    public class MyInterceptor implements MethodInterceptor {

        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("主人发出命令");  
            //使用proceed()方法反射调用目标类相应的方法
            Object obj=invocation.proceed();
            System.out.println("主人给予奖励"); 

            return obj;
        }

    }
        <!--修改配置文件 -->
<bean id="target" class="com.cad.demo.Dog"></bean> 
<bean id="advice" class="com.cad.demo.MyInterceptor"></bean> 

<bean id="proxydog" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.cad.demo.Animal"></property>
    <property name="interceptorNames" value="advice"></property> 
    <property name="target" ref="target"></property>
</bean>
//我们测试一下
public class Test {
    @Test
    public void test(){
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml"); 
        Animal dogproxy=(Animal) ac.getBean("proxydog");
        dogproxy.eat();
    }

}

这里写图片描述


异常抛出增强

异常抛出增强最适合的应用场景是事务管理,当执行事务发生异常时,就必须回滚事务。

我们给出一个模拟的例子,来说明异常抛出增强的使用。

    //模拟业务类中的转账操作,抛出异常
    public class UserService {
        public void addMoney(){
            throw new RuntimeException("转账异常");
        }
    }
    //我们实现异常抛出增强接口,对转账业务方法进行增强处理
    public class TransactionManager implements ThrowsAdvice { 
        public void afterThrowing(Method method,Object[] args,Object target,Exception e)throws Throwable{
            System.out.println(method.getName());
            System.out.println(e.getMessage());
            System.out.println("回滚事务");
        }
    }

ThrowsAdvice异常抛出增强接口没有定义任何方法,它是一个标识接口,在运行期Spring使用反射机制自行判断,我们必须使用以下签名形式定义异常抛出的增强方法。

  • void afterThrowing(Method method,Object[] args,Object target,Throwable)

方法名必须为afterThrowing,前三个参数是可选的,最后一个参数是Throwable或者其子类。可以在同一个增强中定义多个afterThrowing()方法,当目标类方法抛出异常时,Spring会自动选用最匹配的增强方法,主要是根据异常类的匹配程度。

<!--编写配置文件-->
<bean id="target" class="com.cad.demo.UserService"></bean> 
<bean id="advice" class="com.cad.demo.TransactionManager"></bean> 

<bean id="proxyservice" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="interceptorNames" value="advice"></property> 
    <property name="target" ref="target"></property> 
    <!--因为我们的业务类没有实现接口,因此使用CGLib代理-->
    <property name="proxyTargetClass" value="true"></property>
</bean>
//我们进行测试一下 
public class Test {
    @Test
    public void test(){
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml"); 
        UserService service=(UserService) ac.getBean("proxyservice");
        service.addMoney();
    }
}

运行结果:
这里写图片描述



7、Spring中切点类型

  在学习增强时,我们可能注意到一个问题:增强被织入到目标类的所有方法中。我们希望有选择的织入到某些特定的方法中,就需要使用切点来进行目标连接点的定位。

  Spring通过Pointcut接口描述切点,Pointcut由ClassFilterMethodMatcher构成,通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就拥有了描述某些类的特定方法的能力。

  Spring支持两种方法匹配器:静态方法匹配器动态方法匹配器。静态方法匹配器仅对方法名签名(包括方法名和入参类型和顺序)进行匹配。动态方法匹配器会在运行期检查方法入参的值。静态匹配仅匹配一次,而动态匹配因为每次调用方法的参数都可能不一样,所以每次调用都需要判断。所以动态匹配影响性能,不常使用。


切点类型
  • 静态方法切点:org.springframework.aop.support.StaticMethodMatcherPointcut是静态方法切点的抽象基类,默认情况下它匹配所有的类。StaticMethodMatcherPointcut包括两个主要的子类,分别是NameMatchMethodPointcut和AbstractRegexpMethodPointcut,前者提供简单字符串匹配方法签名,而后者使用正则表达式匹配方法签名。

  • 动态方法切点:org.springframework.aop.support.DynamicMethodMatcherPointcut 是动态方法切点的抽象基类,默认情况下它匹配所有的类。DynamicMethodMatcherPointcut类已经过时,可以使用DefaultPointcutAdvisor 和DynamicMethodMatcherPointcut动态方法匹配器替代之。

  • 注解切点:org.springframework.aop.support.AnnotationMatchingPointcut实现类表示注解切点。使用AnnotationMatchingPointcut支持在Bean中直接通过JDK5.0注解标签定义的切点。

  • 表达式切点:org.springframework.aop.support.ExpressionPointcut接口主要是为了支持AspectJ切点表达式语法而定义的接口。

  • 流程切点:org.springframework.aop.support.ControlFlowPointcut实现类表示控制流程切点。ControlFlowPointcut是一种特殊的切点,它根据程序执行堆栈的信息查看目标方法是否由某一个方法直接或间接发起调用,以此判断是否为匹配的连接点。

  • 复合切点:org.springframework.aop.suppot.ComposablePointcut实现类是为创建多个切点而提供的方便操作类。它所有的方法都返回ComposablePointcut类,这样,我们就可以使用链接表达式对切点进行操作。



8、Spring中切面类型

  由于增强既包含横切代码,又包含部分的连接点信息,所以我们可以仅通过增强类生成一个切面。 Spring使用org.springframework.aop.Advisor接口表示切面的概念,一个切面同时包含了增强和切点的信息。切面可以分为三类:一般切面切点切面引介切面

  • Advisor:代表一般切面,它仅包含一个Advice。我们说过,因为Advice包含了横切代码和连接点的部分信息,所以Advice本身就是一个简单的切面,但是它代表的连接点是目标类的所有方法,横切面太宽泛,不会直接使用。

  • PointcutAdvisor:代表具有切点的切面,它包含Pointcut和Advice两个可u,这样,我们就可以通过类名、方法名、方法方位等信息灵活的定义切面的连接点。

  • IntroductionAdvisor:代表引介切面

我们主要学习PointcutAdvisor,PointAdvisor主要有六个具体的实现类

  • DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意Pointcut和Advice定义一个切面,不支持引介切面类型,可以通过扩展该类实现自定义的切面。

  • NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面。

  • RegexpMethodPointcutAdvisor:对于按照正则表达式匹配方法名定义切点的切面,通过该类进行操作。

  • StaticMethodMatcherPointcutAdvisor:静态方法匹配器切面定义的切面,默认情况匹配所有目标类。

  • AspectJExpressionPointcutAdvisor:用于AspectJ切点表达式定义切点的切面。

  • AspectJPointcutAdvisor:用于AspectJ语法定义切点的切面。

演示静态方法匹配切面

StaticMethodMatcherPointcutAdvisor代表一个静态方法匹配切面,通过类过滤和方法名匹配定义切点。

    我们创建两个类 ,Dog和Cat 

    public class Dog {
        public void eat(){
            System.out.println("狗在吃饭");
        }

        public void run(){
            System.out.println("狗在跑步");
        }
    } 

    public class Cat {
        public void eat(){
            System.out.println("猫在吃饭");
        }

        public void run(){
            System.out.println("猫在跑步");
        }
    }
    //现在我们定义一个切面,在Dog的eat()方法调用前织入一个增强 
    //StaticMethodMatcherPointcutAdvisor抽象类唯一需要实现的是matches()方法
    //但是默认情况,该切面匹配所有类,我们通过覆盖getClassFilter方法,仅匹配Dog及其子类
    public class MyAdvisor extends StaticMethodMatcherPointcutAdvisor {

        //匹配方法名称为eat的方法
        public boolean matches(Method method, Class<?> clazz) {
            return method.getName().equals("eat");
        }

        //我们通过覆盖getClassFilter方法,仅匹配Dog及其子类
        public ClassFilter getClassFilter() {
            return new ClassFilter(){
                public boolean matches(Class<?> clazz) {        
                    return Dog.class.isAssignableFrom(clazz);
                }

            };
        }
    }
    //我们还需要定义一个增强,我们使用前置增强
    public class MyBeforeAdvice implements MethodBeforeAdvice {

        public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
            System.out.println("主人发出命令");
        }
    }
    <!--配置文件-->
    <?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:context="http://www.springframework.org/schema/context" 
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--Dog目标类和Cat目标类-->
    <bean id="targetDog" class="com.cad.spring.Dog"></bean>
    <bean id="targetCat" class="com.cad.spring.Cat"></bean> 
    <!--增强类--> 
    <bean id="advice" class="com.cad.spring.MyBeforeAdvice"></bean>  
    <!--切面,里面包含增强,还有一个classFilter属性,可以指定类匹配过滤器,但我们在类里面已经通过覆盖来实现类匹配过滤器,所以不用配置-->
    <bean id="MyAdvisor" class="com.cad.spring.MyAdvisor">
        <property  name="advice" ref="advice"></property>
    </bean>

    <!--小狗代理-->
    <bean id="dogproxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interceptorNames" value="MyAdvisor"></property>
        <property name="proxyTargetClass" value="true"></property> 
        <property name="target" ref="targetDog"></property>
    </bean>

    <!--小猫代理-->
    <bean id="catproxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interceptorNames" value="MyAdvisor"></property>
        <property name="proxyTargetClass" value="true"></property> 
        <property name="target" ref="targetCat"></property>
    </bean>
    </beans>   
    //我们进行测试一下
    public class TestDemo {
        @Test
        public void test(){
            ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
            Dog dog=(Dog)ac.getBean("dogproxy");
            Cat cat=(Cat)ac.getBean("catproxy");
            dog.eat();
            System.out.println("-------------------------");
            cat.eat();
        }
    }

运行结果:
这里写图片描述
可见切面只织入到Dog的eat方法前的连接点上,并且Dog的run()方法没有织入切面。

我们只演示这些,还有动态切面,复合切点切面等都可以自行找相关资料,大概流程都差不多。用的也不多



9、Spring自动创建代理

  在前面所有的例子中,对于每个代理对象,我们都需要进行配置,需要代理的对象很多的时候就会很麻烦。
Spring为我们提供了自动代理机制,让容器为我们自动生成代理,免去我们繁琐的配置功能,内部原理是使用BeanPostProcessor后处理Bean自动地完成这项工作,当我们获取对象时,后处理Bean会获取我们对象的代理,然后最后返回的是我们获取Bean的代理。

  这些基于BeanPostProcessor的自动代理创建器的实现类,根据一些规则自动在容器实例化Bean时为匹配的Bean生成代理,这些代理创建器可以分为以下三类。

  • 基于Bean配置名规则的自动代理创建器:允许为一组特定配置名的Bean自动创建代理。实现类为BeanNameAutoProxyCreator

  • 基于Advisor匹配机制的自动代理创建器:它会对容器中所有的Advisor进行扫描,自动将这些切面应用到匹配的Bean中,实现类为DefaultAdvisorAutoProxyCreator

  • 基于Bean中AspectJ注解标签的自动创建代理器:为包含AspectJ注解的Bean自动创建代理。实现类是AnnotationAwareAspectJAutoProxyCreator

演示BeanNameAutoProxyCreator
    //我们还使用前面猫和狗的例子,不过我们需要修改配置文件
    <?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:context="http://www.springframework.org/schema/context" 
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    //猫和狗的目标类
    <bean id="targetDog" class="com.cad.spring.Dog"></bean>
    <bean id="targetCat" class="com.cad.spring.Cat"></bean>  
    //增强类
    <bean id="advice" class="com.cad.spring.MyBeforeAdvice"></bean>  

    //配置BeanNameAutoProxyCreator实现类
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        //beanNames属性允许用户指定需要自动代理的Bean名称,可以使用*通配符,例如*t,就会匹配cat
        <property name="beanNames" value="targetDog,targetCat"></property>
        <property name="interceptorNames" value="advice"></property>
        //指定使用CGLib
        <property name="optimize" value="true"></property>
    </bean> 

    </beans>
    //我们进行测试
    public class TestDemo {
        @Test
        public void test(){
            ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
            Dog dog=(Dog)ac.getBean("targetDog");
            Cat cat=(Cat)ac.getBean("targetCat");
            dog.eat();
            System.out.println("-------------------------");
            cat.eat();
        }
    }

运行结果:
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值