Spring AOP详解

来源:互联网 发布:域名如何买卖 编辑:程序博客网 时间:2024/06/05 17:44

学习spring aop首先要有一定的代理知识,代理就相当于显示生活中的中介,通过代理对象来完成一些真实对象想完成的事。

一、AOP基础知识
1、AOP横向抽取机制
* AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码

Spring AOP 是Spring框架内部自带AOP实现
AspectJ 第三方开源AOP框架,Spring2.0开始 已经支持AspectJ

相关术语
JoinPoint 连接点 ,可以被动态代理拦截目标类的方法
PointCut 切入点, 被拦截的连接点
Advice 通知、增强, 拦截目标后,增强新的代码功能
Introduction 引介,是一种特殊Advice ,对目标增加新的方法和属性
Target 被拦截的目标对象
Weaving 织入 将增强代码应用到目标上,生成代理对象过程
Proxy 生成的代理对象
Aspect 切面, 切入点和通知总和

2、AOP基础代理
代理模式 ,是23种设计模式之一,为目标真实对象,提供代理对象,由代理对象控制目录的访问 (静态代理 )
* 静态代理,代理类 是用户自己编写的真实存在的
* 动态代理,代理类 是由类加载器 动态生成的 ,不是一个用户编写好的类

静态代理模式由三个部分组成: 业务接口、真实业务类、代理类

动态代理
1) JDK自带动态代理
面向接口生成代理,原理就是类加载器根据接口,在虚拟机内部创建接口实现类
Proxy.newProxyInstance(classloader,interfaces[], invocationhandler );

2) CGlib提供动态代理
一个开源技术,不需要针对接口就可以生成动态代理类
使用CGLib 需要导入CGLib的开发包 (最新版本 Spring core包中 已经集成cglib )

原理:创建需要代理对象类一个子类

二、Spring AOP
1、AOP联盟 org.aopalliance.aop.Interface.Advice
Spring AOP 针对Advice 提供五种实现
前置通知 org.springframework.aop.MethodBeforeAdvice 在目标方法执行前实施增强
后置通知 org.springframework.aop.AfterReturningAdvice 在目标方法执行后实施增强
环绕通知 org.aopalliance.intercept.MethodInterceptor 在目标方法执行前后实施增强
异常抛出通知 org.springframework.aop.ThrowsAdvice 在方法抛出异常后实施增强
引介通知 org.springframework.aop.IntroductionInterceptor 在目标类中添加一些新的方法和属性
* 引入jar包
spring-aop-3.2.0.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
AOP即面向切面编程,业界认为AOP是对OOP面向对象编程的一种延伸,改变了传统的纵向继承模式。AOP采用横向抽取模式,动态横向织入代码,对代码进行增强,例如性能监控,日志技术,异常处理,事物管理等。而对AOP进行底层实现的就是动态代理,AOP有两种动态代理,一种是jdk自带的动态代理,另一种就是第三方提供的CGlib的动态代理。下面会有详细介绍,首先我先说一下静态代理。

静态代理:

由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
例子:

提供接口

/**  * 代理接口。处理给定名字的任务。 */public interface Subject {  /**   * 执行给定名字的任务。    * @param taskName 任务名   */   public void dealTask(String taskName); }

真实类

/** * 真正执行任务的类,实现了代理接口。 */public class RealSubject implements Subject { /**  * 执行给定名字的任务。这里打印出任务名,并休眠500ms模拟任务执行了很长时间  * @param taskName   */   @Override   public void dealTask(String taskName) {      System.out.println("正在执行任务:"+taskName);      try {         Thread.sleep(500);      } catch (InterruptedException e) {         e.printStackTrace();      }   }}

静态代理类

/** * 代理类,实现了代理接口。 */public class ProxySubject implements Subject { //代理类持有一个委托类的对象引用 private Subject delegate; public ProxySubject(Subject delegate) {  this.delegate = delegate; } /**  * 将请求分派给委托类执行,记录任务执行前后的时间,时间差即为任务的处理时间  *   * @param taskName  */ @Override public void dealTask(String taskName) {  long stime = System.currentTimeMillis();   //将请求分派给委托类处理  delegate.dealTask(taskName);  long ftime = System.currentTimeMillis();   System.out.println("执行任务耗时"+(ftime - stime)+"毫秒"); }}

生成静态代理工厂

public class SubjectStaticFactory { //客户类调用此工厂方法获得代理对象。 //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。 public static Subject getInstance(){   return new ProxySubject(new RealSubject()); }}

客户类

public class Client1 { public static void main(String[] args) {  Subject proxy = SubjectStaticFactory.getInstance();  proxy.dealTask("DBQueryTask"); } }

静态代理类优缺点
优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

通过静态代理我们熟悉了代理模式的思想,接下来我就说一下AOP的两种动态代理。
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

JDK的动态代理:

Spring AOP默认是使用JDK自带的代理,这种代理是面向接口的,底层是通过类加载器创建接口的实现类实现代理的。

例子:

定义业务接口:

package cn.dynamicproxy;/** * 业务接口 * @author  * */public interface Fly {    // 业务方法    public void gotofly();}

定义真实对象实现接口:

package cn.dynamicproxy;public class Bird implements Fly{    @Override    public void gotofly() {        System.out.println("鸟飞走了....");    }}
package cn.dynamicproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import org.junit.Test;import org.springframework.cglib.proxy.Enhancer;import org.springframework.cglib.proxy.MethodInterceptor;import org.springframework.cglib.proxy.MethodProxy;// 动态代理操作public class ProxyTest {    @Test    public void demo(){        // JDK自带动态代理          // 原理 根据类加载器和接口 创建 接口实现类 (必须使用接口)        // 1 真实业务对象        final Fly fly = new Bird();        // 2 使用真实业务对象 类加载器和 实现接口,在内存中创建代理对象        Fly proxy = (Fly) Proxy.newProxyInstance(fly.getClass().getClassLoader(), fly.getClass().getInterfaces(), new InvocationHandler() {            @Override            public Object invoke(Object proxy, Method method, Object[] args)                    throws Throwable {                // 拦截gotofly方法                if(method.getName().equals("gotofly")){                    System.out.println("鸟飞不走了... 被拦截了");                    return null;                }                // 如果不想拦截                return method.invoke(fly, args);            }        });        // 3 调用代理对象业务方法        proxy.gotofly();    }}

下面这种是CGlib的动态代理,它是采用传统的字节码的方式创建代理,是面向类的,即创建该类的子类来实现代理。
例子:

创建真实类:

 package cn.dynamicproxy;/** * 业务类 没有实现任何接口 * @author  * */public class Cat {    public void run(){        System.out.println("猫抓老鼠....");    }}
package cn.dynamicproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import org.junit.Test;import org.springframework.cglib.proxy.Enhancer;import org.springframework.cglib.proxy.MethodInterceptor;import org.springframework.cglib.proxy.MethodProxy;// 动态代理操作public class ProxyTest {    @Test    public void demo(){        // CGlib 为Cat类生成代理 (无接口代理)        // 1、创建真实业务对象        final Cat cat = new Cat();        // 2、根据真实业务对象生成代理对象         Enhancer enhancer = new Enhancer();        // 为真实业务对象类 创建子类        enhancer.setSuperclass(cat.getClass());        // 设置回调函数  类似InvocationHandler        enhancer.setCallback(new MethodInterceptor() {            @Override            // 相比 InvocationHandler 多了一个 方法代理对象 methodProxy             public Object intercept(Object proxy, Method method, Object[] args,                    MethodProxy methodProxy) throws Throwable {                // 需要拦截run方法                if(method.getName().equals("run")){                    System.out.println("猫被拦截了....");//                  Object result = method.invoke(cat, args);                    // 用代理方法也可以调用真实方法                    Object result = methodProxy.invoke(cat, args);                    System.out.println("真实方法操作后拦截...");                    return result;                }                // 不需要拦截                return method.invoke(cat, args);            }        });        // 生成代理对象        Cat proxy = (Cat) enhancer.create();        // 3、调用代理对象方法        proxy.run();    }}

各位从以上不难看出,这种代理的繁琐性,当然,spring AOP 当然为大家提供了更为方便的自动代理。
1) BeanNameAutoProxyCreator 根据Bean名称自动代理
案例四: 对所有以DAO 结尾Bean 自动生成代理

    <!-- 真实业务对象 -->    <bean id="userDAO" class="cn.aop.service.UserDAOImpl"></bean>    <bean id="customerDAO" class="cn.aop.service.CustomerDAO"></bean>    <!-- 通知 -->    <bean id="myMethodBeforeAdvice" class="cn.aop.advice.MyMethodBeforeAdvice"></bean>    <!-- 配置自动代理 bean不需要写id -->    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">        <!-- 配置自动代理Bean名称规则 -->        <property name="beanNames" value="*DAO"></property>        <!-- 增强Advice -->        <property name="interceptorNames" value="myMethodBeforeAdvice"></property>        <!-- 强制使用CGlib -->        <property name="optimize" value="true"></property>    </bean>

** 对目标Bean所有方法进行拦截

2) DefaultAdvisorAutoProxyCreator 默认切面自动代理
配置切面Advisor时候,切面中如果定义切点 ,切点中本身含有 需要拦截的信息

案例五: 只需要配置切点切面,使用DefaultAdvisorAutoProxyCreator

    <!-- 配置切点切面 -->    <bean id="regexAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">        <!-- 配置环绕通知 -->        <property name="advice" ref="myMethodIntercepor"></property>        <!-- 拦截规则 -->        <property name="patterns" value=".*delele,.*exportOrder"></property>    </bean>    <!-- 自动代理 -->    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>

AspectJ 实现AOP功能
导入jar : spring-aop.jar aopalliance.jar 、 spring-aspects.jar 、 aspects-weaver.jar
com.springsource.org.aspectj tools 携带编译器完整工具包 ,weaver 提供AOP切面编程最基本的类 (tools 包含 weaver )

1、采用注解 @Aspectj 进行AOP开发
导入aop schema
开启自动代理

@AspecJ 提供六种 Advice @Before 前置通知,相当于BeforeAdvice@AfterReturning 后置通知,相当于AfterReturningAdvice@Around 环绕通知,相当于MethodInterceptor@AfterThrowing抛出通知,相当于ThrowAdvice@After 最终final通知,不管是否异常,该通知都会执行 ------------------------ 新增 @DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)

* 每种通知都提供value属性,在该属性中定义切点信息 execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)

匹配所有类public方法 execution(public * *(..))
匹配指定包下所有类方法 execution(* cn.dao.(..)) 不包含子包 execution( cn.dao..(..)) ..表示包、子孙包下所有类
匹配指定类所有方法 execution(* cn.service.UserService.*(..))
匹配实现特定接口所有类方法 execution(* cn.dao.GenericDAO+.*(..))
匹配所有save开头的方法 execution(* save*(..))

advisor 和 aspect 区别 ?
Advisor 和 Aspect 都是切面 ,Advisor是Spring中定义切面 ,通常只有一个Advice , Aspect是真正意义上切面,允许有多个advice

编程步骤
第一步: 创建真实业务对象 UserDAO
第二步: 创建切面 MyAspect
第三步: 配置applicationContext.xml 业务类和切面

案例一: @Before前置增强 ,可以在方法中传入一个参数 JoinPoint 对象 被连接点 (获得当前连接点信息)

@Before("execution(* cn.service.UserDAO.s*(..))")    public void before2(JoinPoint joinpoint){        System.out.println(joinpoint);        System.out.println("前置增强2");    }

案例二: @AfterRetuning 后置增强 ,涉及到返回值操作

@AfterReturning(value="execution(* cn.service.UserDAO.save(..))",returning="val")    public void afterReturning2(Object val){ // 这里参数名 必须和returning 配置一致        System.out.println(val);        System.out.println("后置增强2...");

案例三:@Around 环绕增强 控制目标业务方法是否执行,在方法中传入ProceedingJoinPoint 可执行连接点

@Around("execution(* cn.service.UserDAO.*(..))")    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ // 传入一个参数,通过该参数,控制目标业务方法是否执行        System.out.println("环绕前操作....");        Object result = proceedingJoinPoint.proceed();// 目标业务方法执行        System.out.println("环绕后操作....");        return result;    }

案例四: @AfterThrowing 抛出增强,当目标方法发生异常,该增强获得执行 ,获得异常对象(包含异常信息 )

@AfterThrowing(value="execution(* cn.service.UserDAO.*(..))",throwing="e")    public void afterEhrowing(Throwable e){ // 参数e名称 必须和 throwing的值一致        System.out.println("目标方法出现异常,异常信息:" +e.getMessage() );    }

案例五: @After 最终增强,无论目标方法是否发生异常,该增强代码都会执行

@After("execution(* cn.service.UserDAO.*(..))")    public void after(){        System.out.println("最终增强......");    }

2、 如果切点信息完全相同,可以@PointCut 将切点信息提取出来
切点方法:private void 无参数方法,方法名为切点名

// 作为切点方法// 方法名就是切点名@Pointcut(value="execution(* cn.service.UserDAO.*(..))")private void mypointcut(){}@AfterThrowing(value="MyAspect.mypointcut()",throwing="e")public void afterEhrowing(Throwable e){ // 参数e名称 必须和 throwing的值一致System.out.println("目标方法出现异常,异常信息:" +e.getMessage() );}@After("MyAspect.mypointcut()")public void after(){System.out.println("最终增强......");}

** 当通知多个切点时,可以使用|| 进行连接

@Aspect @Before @AfterRetuning @AfterThrowing @Around @After @Pointcut

AspectJ 通过XML配置切面

<!-- AOP配置 -->    <!-- proxy-target-class="false" 对接口进行代理 proxy-target-class="true" 对目标类进行代理  -->    <aop:config proxy-target-class="true">        <!-- 定义切面 , 在切面中定义切点和通知 -->        <aop:aspect ref="myaspect">            <!-- 定义切点 -->            <aop:pointcut expression="execution(* cn.service.UserDAO.*(..))" id="mypointcut"/>            <!-- 定义通知 -->            <aop:before method="before" pointcut-ref="mypointcut"/>            <aop:after-returning method="afterReturning" pointcut-ref="mypointcut" returning="val"/>            <aop:around method="around" pointcut-ref="mypointcut"/>            <aop:after-throwing method="afterThrowing" pointcut-ref="mypointcut" throwing="ex"/>            <aop:after method="after" pointcut-ref="mypointcut"/>        </aop:aspect>    </aop:config>

小结:
1、JDK动态代理和CGlib 动态代理
2、传统Spring AOP
一个Advice 就是一个Advisor , ProxyFactoryBean 生成代理时,拦截目标类所有方法 (前置增强、引介增强 )
PointcutAdvisor 正则表达式切点 生成代理时,对指定方法进行拦截 (环绕增强 )
自动代理 :Bean名称代理 (所有方法拦截)、根据切面Advisor配置自动代理 (拦截指定方法 )
3、AspectJ (重点)
@Aspect @Before @AfterRetuning @AfterThrowing @Around @After @Pointcut
XML配置

原创粉丝点击