spring3.x第六章 SpringAOP基础

6.1 AOP概述
6.1.1 AOP到底是什么

  AOP是Aspect Oriented Programing的简称,面向切面编程。Horse、Pig、Camel这些对象都有run()、eat()的方法,通过引入一个包含这两个方法抽象的Animal父类,就可以通过继承来复用run()和eat()方法。这是纵向继承体系。

public void removeTopic(int topicId){
    pnonitor.start();
    transManager.beginTransaction();
    topicDao.removeTopic(topicId);
    transManager.commit();
    pmonitor.end();
}
public void CreateForum(Forum forum){
    pnonitor.start();
    transManager.beginTransaction();
    forumDao.create(forum);
    transManager.commit();
    pmonitor.end();
}

  pnonitor是性能监视代码,而业务代码事务开始和业务提交被淹没在重复化的非业务性的代码之中。

6.1.2 AOP术语
  连接点(Joinpoint)

  程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。

  切点(Pointcut)

  每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点。通过数据库查询的概念来理解切点和连接点的关系:连接点相当于数据库中的记录,而切点相当于查询条件。一个切点可以匹配多个连接点。所以,如果希望定位到具体连接点上,还需要提供方位信息。如一个切点查询到某个方法连接点,方法可以指定before或者after。

  增强(Advice)

  增强是植入到目标类连接点上的一段程序代码。以及拥有另一个和连接点相关的信息,就是执行的方位(类似与TCP数据包的头信息)。

  目标对象(Target)

  增强逻辑的植入目标类。即需要植入的类。

  引介(Introduction)

  引介是一种特殊的增强,它为类添加一些属性和方法。这样即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类称为这个接口的实现类,但是编程人员在业务实际编写时不用手动实现这个接口。

  织入(Weaving)

  织入是将增强添加到目标类具体连接点上的过程,AOP像一台织布机,将目标类、增强或引介通过AOP这台织布机天衣无缝地编织到一起。AOP有三种织入的方式:
  1)编译期织入,这要求使用特殊的Java编译期;
  2)类装载期织入,这要求使用特殊的类装载器;
  3)动态代理织入,在运行期为目标类添加增强生成子类的方式;
  Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

  代理(Proxy)

  一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,业可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。
  切面(Aspect)
  切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架。
  AOP的重心在于:第一,如何通过切点和增强定位到连接上;第二如何在增强中编写切面的代码。

6.1.3 AOP的实现者

  AOP工具的设计目标是把横切的问题(如性能监视、事务管理)模块化。使用类似于OOP的方式进行切面的编程工作。位于AOP工具核心的是连接点模型,它提供了一种机制,可以识别出在哪里发生了横切。

  AspectJ

  AspectJ是语言级的AOP实现,能够在编译期提供横切代码的织入,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

  AspectWerkz

  支持运行期或类装载器织入横切代码,所以它拥有一个特殊的类装载器。已经和AspectJ合并。

  JBossAOP
  SpringAOP

  SpringAOP使用纯Java实现,不需要专门的编译过程,不需要特殊的类装载器。它在运行期通过代理方式向目标类织入增强代码。

6.2 基础知识

  Spring AOP使用了两种代理机制:一种是基于JDK的动态代理;另一种是基于CGLib的动态代理。之所以需要两种代理机制,因为JDK本身只提供接口的代理,而不支持类的代理。

6.2.1 带有横切逻辑的实例
package com.baobaotao.proxy;

public interface ForumService {
    public void removeTopic(int id);
    public void removeForum(int id);
}
package com.baobaotao.proxy;

public class MethodPerformance {
    private long begin;
    private long end;
    private String serviceMethod;
    public MethodPerformance(String serviceMethod){
        this.serviceMethod = serviceMethod;
        this.begin = System.currentTimeMillis();
    }
    public void printPerformance(){
        end = System.currentTimeMillis();
        long elapse = end - begin;
        System.out.println(serviceMethod+"花费"+elapse+"毫秒。");
    }
}
package com.baobaotao.proxy;

public class PerformanceMonitor {
    //通过一个ThreadLocal保存调用线程相关的性能监视信息
    private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();
    //启动对某一目标方法的性能监视
    public static void begin(String method){
        System.out.println("begin monitor...");
        MethodPerformance mp = new MethodPerformance(method);
        performanceRecord.set(mp);
    }
    public static void end(){
        System.out.println("end monitor...");
        MethodPerformance mp = performanceRecord.get();
        //打印出方法性能监视的结果信息
        mp.printPerformance();
    }
}
package com.baobaotao.proxy;

public class ForumServiceImpl implements ForumService{
    public void removeTopic(int topicId){
        //开始对该方法进行性能监视
        PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeTopic");
        System.out.println("模拟删除Topic记录:"+topicId);
        try {
            Thread.currentThread().sleep(20);
        } catch (Exception e) {
            // TODO: handle exception
            throw new RuntimeException();
        }
        //结束对该方法进行性能
        PerformanceMonitor.end();
    }
    public void removeForum(int forumId){
        //开始对该方法进行性能监视
        PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeForum");
        System.out.println("模拟删除Forum记录:"+forumId);
        try {
            Thread.currentThread().sleep(40);
        } catch (Exception e) {
            // TODO: handle exception
            throw new RuntimeException();
        }
        //结束对该方法进行性能
        PerformanceMonitor.end();
    }
}
package com.baobaotao.proxy;

public class TestForumService {
    public static void main(String[] args) {
        ForumServiceImpl forumService = new ForumServiceImpl();
        forumService.removeForum(10);
        forumService.removeTopic(1012);
    }
}

  非业务逻辑的性能监视代码破坏了业务逻辑的纯粹性。我们希望通过代理的方式,将业务类方法中开启和结束性能监视的这些横切代码从业务类中完全移除。并通过JDK动态代理技术或CGLib动态代理技术将横切代码动态织入到目标方法的相应位置。

6.2.2 JDK动态代理

  JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。

package com.baobaotao.proxy_JDK;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PerformanceHandler implements InvocationHandler{

    private Object target;
    public PerformanceHandler(Object target){
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName());
        Object object = method.invoke(target, args);
        PerformanceMonitor.end();
        return object;
    }
}
package com.baobaotao.proxy_JDK;

import java.lang.reflect.Proxy;

public class TestForumService {

    public static void main(String[] args) {
        ForumService target = new ForumServiceImpl();
        PerformanceHandler handler = new PerformanceHandler(target);
        ForumService proxy = (ForumService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler);
        proxy.removeForum(10);
        proxy.removeTopic(1012);
    }
}
6.2.3 CGLib动态代理

  使用JDK创建代理有一个限制,即它只能为接口创建代理实例,这一点我们可以从Proxy的接口newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)的方法签名中,第二个入参interfaces就是需要代理实例实现接口列表。如果没有通过接口定义业务方法的类,如何动态创建代理实例呢?
  CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。

package com.baobaotao.Proxy_Cglib;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import com.baobaotao.proxy.PerformanceMonitor;

public class CglibProxy implements MethodInterceptor{

    private Enhancer enhancer = new Enhancer();
    public Object getProxy(Class clazz){
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        PerformanceMonitor.begin(obj.getClass().getName()+"."+method.getName());
        Object result = proxy.invokeSuper(obj, args);
        PerformanceMonitor.end();
        return result;
    }
}
package com.baobaotao.Proxy_Cglib;

import com.baobaotao.proxy.ForumServiceImpl;

public class TestForumService {
    public static void main(String[] args){
        CglibProxy proxy = new CglibProxy();
        ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class);
        forumService.removeForum(10);
        forumService.removeTopic(1023);
    }
}

  实际输出没有出现特殊的子类。

6.2.4 AOP联盟
6.2.5 代理知识小结

  Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术为目标Bean织入横切逻辑。小结,虽然通过PerformanceHandler或CglibProxy实现了性能监视横切逻辑的动态织入,但这种实现方式存在三个明显需要改进的地方:
  1)目标类的所有方法都添加了性能监视横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定方法添加横切逻辑;
  2)我们通过硬编码的方式指定了织入横切逻辑的织入点,即在目标类业务方法的开始和结束前织入代码;
  3)我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的程序代码,无法做到通用。
  有研究表明,CGLib所创建额动态代理对象的性能依旧比JDK的所创建的代理对象的性能高不少(大概10倍)。但CGLib在创建代理对象时所花费的时间却比JDK动态代理多(大概8倍),所以对于singleton的代理对象或者具有实例池的代理,因为无须频繁创建代理对象,所以比较适合用CGLib动态代理技术。由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final,private等方法进行代理。

6.3 创建增强类

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

6.3.1 增强类型
  前置增强
  后置增强
  环绕增强
  异常抛出增强
  引介增强

  这些增强接口都有一些方法,通过实现这些接口方法,在接口方法中就可以将它们织入到目标类方法的相应接口点的位置。

6.3.2 前置增强
package com.baobaotao.advices.advice;

public interface Waiter {
    void greetTo(String name);
    void serveTo(String name);
}
package com.baobaotao.advices.advice;

public class NaiveWaiter implements Waiter{
    public void greetTo(String name) {
        System.out.println("greet to"+name+"...");
    }
    public void serveTo(String name) {
        System.out.println("serving"+name+"...");
    }
}
package com.baobaotao.advices.advice;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class GreetingBeforeAdvice implements MethodBeforeAdvice{

    public void before(Method method, Object[] args, Object obj)
            throws Throwable {
        String clientName = (String) args[0];
        System.out.println("How are you!Mr."+clientName+".");
    }
}
package com.baobaotao.advices.advice;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;

public class TestBeforeAdvice {

    public static void main(String[] args) {
        Waiter target = new NaiveWaiter();
        BeforeAdvice advice = new GreetingBeforeAdvice();
        //1.Spring提供的代理工厂
        ProxyFactory pf = new ProxyFactory();
        //2.设置代理目标
        pf.setTarget(target);
        //3.为代理目标添加增强
        pf.addAdvice(advice);
        //4.生成代理实例
        Waiter proxy = (Waiter) pf.getProxy();
        proxy.greetTo("John");
        proxy.serveTo("Tom");
    }
}
  解剖ProxyFactory

  如果通过ProxyFactory的setInterfaces(Class[] interfaces)指定针对接口进行代理,则ProxyFactory就使用JdkDynamicAopProxy;如果针对类的代理,则使用Cglib2AopProxy。也可以是ProxyFactory的setOptimize(true)方法,让其启动优化代理方式,这样,针对接口的代理也会使用Cglib2AopProxy。
  也可以通过addAdvice(Advice)添加多个增强。

  在Spring中配置
<bean id="greetingAdvice" class="com.baobaotao.advices.advice.GreetingBeforeAdvice"/>
    <bean id="target" class="com.baobaotao.advices.advice.NaiveWaiter"/>
    <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:proxyInterfaces="com.baobaotao.advices.advice.Waiter"
        p:interceptorNames="greetingAdvice"
        p:target-ref="target"
        //使用的JDK代理技术,p:proxyTargetClass="true"就变成了CGLib代理技术
    />
6.3.3 后置增强
package com.baobaotao.advices.advice;

import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;

public class GreetingAfterAdvice implements AfterReturningAdvice{

    public void afterReturning(Object returnObj, Method method, Object[] args,
            Object obj) throws Throwable {
        System.out.println("Please enjoy yourself!");
    }
}
<bean id="greetingBefore" class="com.baobaotao.advices.advice.GreetingBeforeAdvice"/>
    <bean id="greetingAfter" class="com.baobaotao.advices.advice.GreetingAfterAdvice"/>
    <bean id="target" class="com.baobaotao.advices.advice.NaiveWaiter"/>
    <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:proxyInterfaces="com.baobaotao.advices.advice.Waiter"
        p:target-ref="target"
        p:interceptorNames="greetingBefore, greetingAfter"
        />
6.3.4 环绕增强

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

6.3.5 异常抛出增强

  异常抛出增强最适合的应用场景是事务管理,当参与事务的某个Dao发生异常事务管理器就必须回滚事务。

package com.baobaotao.advices.throwad;

import java.lang.reflect.Method;

import org.springframework.aop.ThrowsAdvice;

public class TransactionManager implements ThrowsAdvice{
    //定义增强逻辑
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex)throws Throwable{
        System.out.println("--------------");
        System.out.println("method:"+method.getName());
        System.out.println("抛出异常:"+ex.getMessage());
        System.out.println("成功回滚事务。");
    }
}
<bean id="transactionManager" class="com.baobaotao.advices.throwad.TransactionManager"/>
    <bean id="forumServiceTarget" class="com.baobaotao.advices.throwad.ForumService"/>
    <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:proxyTargetClass="true"
        p:target-ref="forumServiceTarget"
        p:interceptorNames="transactionManager"
        />
package com.baobaotao.advices.throwad;

import java.sql.SQLException;

public class ForumService {
    public void removeForum(int forumId){
        throw new RuntimeException("运行异常。");
    }
    public void updateForum(int forumId) throws Exception{
        throw new SQLException("数据更新操作异常。");
    }
}

  ForumService的两个方法所抛出的异常都被TransactionManager这个异常抛出,增强捕获并成功处理。注意ThrowsAdvice只是一个标志,并没有强制的实现方法,但是JVM通过反射进行得知有这个标志的方法进行特殊的处理,其中的方法名必须是afterThrowing,但是前三个参数可以省略。

6.3.6 引介增强

  引介增强是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强,我们可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介增强可以为目标类创建实现某接口的代理。

package com.baobaotao.advices.introduce;

public interface Monitorable {
    void setMonitorActive(boolean active);
}
package com.baobaotao.advices.introduce;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;

import com.baobaotao.proxys.proxy.PerformanceMonitor;

public class ControllablePerformanceMonitor extends DelegatingIntroductionInterceptor implements Monitorable{

    private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>();
    public void setMonitorActive(boolean active) {
        MonitorStatusMap.set(active);
    }
    //拦截方法
    public Object invoke(MethodInvocation mi) throws Throwable{
        Object obj = null;
        //对于支持性能监视可控代理,通过判断其状态决定是否开启性能监控功能
        if (MonitorStatusMap.get()!=null && MonitorStatusMap.get()) {
            PerformanceMonitor.begin(mi.getClass().getName()+"."+mi.getMethod().getName());
            obj = super.invoke(mi);
            PerformanceMonitor.end();
        }else{
            obj = super.invoke(mi);
        }
        return obj;
    }
}
package com.baobaotao.advices.introduce;

public class ForumService{
    public void removeTopic(int topicId){
        System.out.println("模拟删除Topic记录:"+topicId);
        try {
            Thread.currentThread().sleep(20);
        } catch (Exception e) {
            // TODO: handle exception
            throw new RuntimeException();
        }
    }
    public void removeForum(int forumId){
        System.out.println("模拟删除Forum记录:"+forumId);
        try {
            Thread.currentThread().sleep(40);
        } catch (Exception e) {
            // TODO: handle exception
            throw new RuntimeException();
        }
    }
}
<bean id="pmonitor" class="com.baobaotao.advices.introduce.ControllablePerformanceMonitor"/>
    <bean id="forumServiceTarget" class="com.baobaotao.advices.introduce.ForumService"/>
    <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interfaces="com.baobaotao.advices.introduce.Monitorable"
        p:target-ref="forumServiceTarget"
        p:interceptorNames="pmonitor"
        p:proxyTargetClass="true"
        />
package com.baobaotao.advices.introduce;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class TestBeforeAdviceUseXml {
    public static void main(String[] args) {
        String configPath = "com/baobaotao/advices/introduce/ProxyBean.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        ForumService forumService = (ForumService) ctx.getBean("forumService");
        forumService.removeForum(10);
        forumService.removeTopic(1022);
        System.out.println("--------------------");
        Monitorable monitorable = (Monitorable) forumService;
        monitorable.setMonitorActive(true);
        forumService.removeForum(10);
        forumService.removeTopic(1022);
    }

}
6.4 创建切面

  增强提供了连接点方位信息:织入到方法前面、后面等。而切点进一步描述植入到哪些类的哪些方法上。
  Spring支持两种方法匹配器:静态方法匹配器和动态方法匹配器。所谓静态方法匹配器,它仅对方法名签名(包括方法名和入参类型及顺序)进行匹配;而动态方法匹配器,会在运行期检查方法入参的值。静态匹配仅会判别一次;而动态匹配因为每次调用方法的入参都可能不一样,所以每次调用方法都必须判断,因为,动态匹配对性能的影响很大。方法匹配器的类型由isRuntime()返回值决定,返回false表示是静态方法匹配器,返回true表示是动态方法匹配器。

6.4.1 切点类型

  Spring提供了六种类型切点。

  静态方法切点
  动态方法切点
  注解切点
  表达式切点
  流程切点
  复合切点
6.4.2 切面类型

  由于增强既包含横切代码,又包含部分的连接点信息,所以我们可以仅通过增强类生成一个切面。但切点仅代表目标类连接点的部分信息。所有仅有切点,我们无法制作出一个切面,必须结合增强才能制作出切面。
  一个切面同时包含横切代码和连接点信息。切面可以分为三类:一般切面、切点切面和引介切面。

  Advisor

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

  PointcutAdvisor

  代表具有切点的切面,它包含Advice和Pointcut两个类。通过类、方法名、以及方位等信息灵活地定义切面的连接点,提供更具适用性的切面;

  IntroductionAdvisor

  代表引介切面。引介切面是对应引介增强的特殊的切面,它应用于类层面上。

6.4.3 静态普通方法名匹配切面
package com.baobaotao.advisor;

public class Waiter {
    public void greetTo(String name){
        System.out.println("waiter greet to"+name+"...");
    }
    public void serveTo(String name){
        System.out.println("waiter serving"+name+"...");
    }
}
package com.baobaotao.advisor;

public class Seller {
    public void greetTo(String name){
        System.out.println("seller greet to "+name+"...");
    }
}
package com.baobaotao.advisor;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class GreetingBeforeAdvice implements MethodBeforeAdvice{

    public void before(Method method, Object[] args, Object obj)
            throws Throwable {
        System.out.println(obj.getClass().getName()+"."+method.getName());
        String clientName = (String) args[0];
        System.out.println("How are you!Mr."+clientName+".");
    }
}
package com.baobaotao.advisor;

import java.lang.reflect.Method;

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;

public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor{

    //切点方法匹配规则:方法名为greetTo
    public boolean matches(Method method, Class<?> clazz) {
        return "greetTo".equals(method.getName());
    }
    //切点类匹配规则:为Waiter的类或子类
    public ClassFilter getClassFilter(){
        return new ClassFilter() {
            public boolean matches(Class<?> clazz) {
                return Waiter.class.isAssignableFrom(clazz);
            }
        };
    }

}
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引用Spring的多个Schema空间的格式定义文件 -->
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="waiterTarget" class="com.baobaotao.advisor.Waiter"/>
    <bean id="sellerTarget" class="com.baobaotao.advisor.Seller"/>
    <bean id="greetingAdviceBefore" class="com.baobaotao.advisor.GreetingBeforeAdvice"/>
    <!-- 将增强装配到切面中 -->
    <bean id="greetingAdvisor" class="com.baobaotao.advisor.GreetingAdvisor"
        p:advice-ref="greetingAdviceBefore"/>
    <bean id="parent" abstract="true"
        class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interceptorNames="greetingAdvisor"
        p:proxyTargetClass="true"/>
    <bean id="waiter" parent="parent" p:target-ref="waiterTarget" />
    <bean id="seller" parent="parent" p:target-ref="sellerTarget" />
</beans>
package com.baobaotao.advisor;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAdvisor {

    public static void main(String[] args) {
        String configPath = "com/baobaotao/advisor/AdvisorBean.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        Waiter waiter = (Waiter) ctx.getBean("waiter");
        Seller seller = (Seller) ctx.getBean("seller");
        waiter.greetTo("John");
        waiter.serveTo("John");
        seller.greetTo("John");
    }
}
6.4.4 静态正则表达式方法匹配切面

  在静态普通方法名的切面中,我们仅能通过方法名定义切点,这种描述方法不够灵活,假设目标类中有多个方法,且它们都满足一定的命名规范,使用正则表达式进行匹配就灵活多了。RegexpMethodPointcutAdvisor是正则表达式方法匹配的切面实现类。

6.4.5 动态切面

  很少会用到,动态通过入参进行匹配。耗费性能。

6.4.6 流程切面

  如果我们希望所有由WaiterDelegate#service()方法发起调用的其他方法都织入增强。

package com.baobaotao.advisor.defaultPointcut;

import com.baobaotao.advisor.staticMethod.Waiter;

public class WaiterDelegate {
    private Waiter waiter;
    public void service(String clientName){
        waiter.greetTo(clientName);
        waiter.serveTo(clientName);
    }
    public void setWaiter(Waiter waiter){
        this.waiter = waiter;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引用Spring的多个Schema空间的格式定义文件 -->
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="greetingAdvice" class="com.baobaotao.advisor.staticMethod.GreetingBeforeAdvice"/>
    <bean id="waiterTarget" class="com.baobaotao.advisor.staticMethod.Waiter"/>
    <!-- 对service连接点 -->
    <bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
        <constructor-arg type="java.lang.Class" value="com.baobaotao.advisor.defaultPointcut.WaiterDelegate" />
        <constructor-arg type="java.lang.String" value="service" />
    </bean>
    <bean id="controlFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
        p:pointcut-ref="controlFlowPointcut"
        p:advice-ref="greetingAdvice" />

    <bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interceptorNames="controlFlowAdvisor"
        p:target-ref="waiterTarget"
        p:proxyTargetClass="true"/>
</beans>
6.4.7 复合切点切面

  如果我们希望所有由WaiterDelegate#service()方法发起调用的其他方法的某些方法都织入增强。即只有通过WaiterDelegate#service()方法调用Waiter#greetTo()方法时才织入增强。

6.4.8 引介切面
6.5 自动创建代理
6.5.1 实现类介绍

  基于Bean配置名规则的自动代理创建器
  允许为一组特定配置名的Bean自动创建代理实例的代理创建器,实现类为BeanNameAutoProxyCreator;
  基于Advisor匹配机制的自动代理创建器
  它会对容器中所有的Advisor进行扫描,自动将这些切面应用到匹配的Bean中,实现类为DefaultAdvisorAutoProxyCreator;
  基于Bean中AspjectJ注解标签的自动代理创建器

6.5.2 BeanNameAutoProxyCreator
<bean id="waiter" class=""/>
<bean id="seller" class=""/>
<bean id="greetingAdvice" class=""/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
    //由于只有一个Bean名称,所有直接使用属性进行配置,也可以使用<list>、逗号等设置多个Bean名称
    p:beanNames="*er"
    p:interceptorNames="greetingAdvice"
    p:optimize="true"/>
6.5.3 DefaultAdvisorAutoProxyCreator

  我们直到切面Advisor是切点和增强的复合体,Advisor本身已经包含了足够的信息:横切逻辑(要织入什么)以及连接点(织入到哪里)。
  DefaultAdvisorAutoProxyCreator能够扫描容器中的Advisor,并将Advisor自动植入到匹配的目标Bean。

<bean id="waiter" class=""/>
<bean id="seller" class=""/>
<bean id="greetingAdvice" class=""/>
<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
    p:patterns=".*greet."
    p:advice-ref="greetingAdvice"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
6.6 小结

  AOP是OOP的有益补充,将重复性的横切逻辑抽取到统一的模块中。
  Spring采用JDK动态代理和CGLib动态代理的技术在运行期织入增强,所以我们不需要特殊的编译器或类装载器就可以使用AOP的功能。要使用JDK动态代理,目标类必须实现接口,而CGLib不对目标类做任何限制。JDK在创建代理对象时性能高于CGLib,而生成的代理对象的运行性能却比CGLib的低,如果是singleton的代理,推荐使用CGLib动态代理。
  Spring只能在方法级别上织入增强,提供了4种类型的方法增强,前置增强,后置增强,环绕增强和异常抛出增强,此外还有一种特殊的引介增强,是类级别的。增强其实就是一种最简单的切面,它既包括横切代码也包括切点信息。
  Spring中,普通的切点通过目标类名和方法名描述连接点的信息。流程切点是比较特殊的切点,它通过方法调用堆栈的运行环境信息来决定连接点。
  切面是增强和切点的联合体,我们可以通过ProxyBeanFactory将切面织入到不同的目标类中。当然,为每一个目标类手工配置一个切面是比较繁琐的,Spring利用BeanPostProcessor可干涉Bean声明周期的机制,提供了一些可以自动创建代理,织入切面的自动代理创建器,其中DefaultAdvisorAutoProxyCreator是功能强大的自动代理创建器,它可以将容器中所有Advisor自动织入到目标Bean中。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值