ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【Spring基础系列5】Spring AOP基础(下)

【Spring基础系列5】Spring AOP基础(下) 主要讲解AOP的实现姿势包括AspectJ和Spring以及自己对AOP内部实现的理解。前言看这篇文章前请大家先看《【Spring基础系列5】Spring AOP基础上》因为这两篇文章是自成体系对于Spring AOP的基础知识本来打算只写一篇因为担心大家不愿意看长文就拆成了上-下两篇文章。Java中的AOP主要有4种实现方式对于AspectJ基于XML的声明式上一篇文章已经给出非常详细的示例这篇文章主要针对AspectJ基于Annotation的声明式、基于Spring的JDK动态代理和CGLib动态代理这3种AOP实现方式进行讲解。Spring AOPSpring JDK动态代理JDK 动态代理是通过 JDK 中的 java.lang.reflect.Proxy 类实现的。下面通过具体的案例演示 JDK 动态代理的使用我们先新建一个接口并给出具体实现类public interface CustomerDao { public void add(); // 添加 public void update(); // 修改 public void delete(); // 删除 public void find(); // 查询 }public class CustomerDaoImpl implements CustomerDao { Override public void add() { System.out.println(添加客户...); } Override public void update() { System.out.println(修改客户...); } Override public void delete() { System.out.println(删除客户...); } Override public void find() { System.out.println(修改客户...); } }新建一个切面类:public class MyAspect { public void myBefore() { System.out.println(方法执行之前); } public void myAfter() { System.out.println(方法执行之后); } }再新建一个通过代理实现的工厂类public class MyBeanFactory { public static CustomerDao getBean() { // 准备目标类 final CustomerDao customerDao new CustomerDaoImpl(); // 创建切面类实例 final MyAspect myAspect new MyAspect(); // 使用代理类进行增强 return (CustomerDao) Proxy.newProxyInstance( // MyBeanFactory.class.getClassLoader(), // 这个也可以 CustomerDao.class.getClassLoader(), new Class[] { CustomerDao.class }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { myAspect.myBefore(); // 前增强 Object obj method.invoke(customerDao, args); myAspect.myAfter(); // 后增强 return obj; } }); } }我使用MyBeanFactory.class.getClassLoader()发现也不影响功能后面有空再研究一下。最后是测试示例public class JDKProxyTest { Test public void test() { // 从工厂获得指定的内容相当于spring获得但此内容时代理对象 CustomerDao customerDao MyBeanFactory.getBean(); // 执行方法 customerDao.add(); customerDao.update(); //customerDao.delete(); //customerDao.find(); } } // 输出 // 方法执行之前 // 添加客户... // 方法执行之后 // 方法执行之前 // 修改客户... // 方法执行之后从输出结果中可以看出在调用目标类的方法前后成功调用了增强的代码由此说明JDK 动态代理已经实现。Spring CGLlB动态代理JDK 动态代理使用起来非常简单但是它也有一定的局限性这是因为 JDK 动态代理必须要实现一个或多个接口如果不希望实现接口则可以使用 CGLIB 代理。CGLIBCode Generation Library是一个高性能开源的代码生成包它被许多 AOP 框架所使用其底层是通过使用一个小而快的字节码处理框架 ASMJava 字节码操控框架转换字节码并生成新的类因此 CGLIB 要依赖于 ASM 的包。下面看一下CGLlB动态代理的实现姿势先定义一个实现类public class GoodsDao { public void add() { System.out.println(添加商品...); } public void update() { System.out.println(修改商品...); } public void delete() { System.out.println(删除商品...); } public void find() { System.out.println(修改商品...); } }新建一个切面类:public class MyAspect { public void myBefore() { System.out.println(方法执行之前); } public void myAfter() { System.out.println(方法执行之后); } }再新建一个通过代理实现工厂类public class MyBeanFactory { public static GoodsDao getBean() { // 准备目标类 final GoodsDao goodsDao new GoodsDao(); // 创建切面类实例 final MyAspect myAspect new MyAspect(); // 生成代理类CGLIB在运行时生成指定对象的子类增强 Enhancer enhancer new Enhancer(); // 确定需要增强的类 enhancer.setSuperclass(goodsDao.getClass()); // 添加回调函数 enhancer.setCallback(new MethodInterceptor() { // intercept 相当于 jdk invoke前三个参数与 jdk invoke—致 Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { myAspect.myBefore(); // 前增强 Object obj method.invoke(goodsDao, args); // 目标方法执行 myAspect.myAfter(); // 后增强 return obj; } }); // 创建代理类 GoodsDao goodsDaoProxy (GoodsDao) enhancer.create(); return goodsDaoProxy; } }最后是测试示例public class JDKProxyTest { Test public void test() { // 从工厂获得指定的内容相当于spring获得但此内容时代理对象 GoodsDao goodsDao MyBeanFactory.getBean(); // 执行方法 goodsDao.add(); goodsDao.update(); // goodsDao.delete(); // goodsDao.find(); } } // 输出 // 方法执行之前 // 添加商品... // 方法执行之后 // 方法执行之前 // 修改商品... // 方法执行之后从输出结果中可以看出在调用目标类的方法前后也成功调用了增强的代码由此说明使用 CGLIB 代理的方式同样实现了手动代理。两者比较可以直接参考文章《【Spring基础系列5】Spring AOP基础上》再盗用上一篇文章的图回顾一下重点JDK动态代理是基于接口CGLIB动态代理是基于类上面的示例也能看出两者的区别如果你在CGLIB代理的示例用接口替换肯定会报错的。使用ProxyFactoryBean创建AOP代理这种方式我没有在项目中遇到过仅作为扩展知识了解即可。基础知识上述已经讲解了 AOP 手动代理的两种方式下面介绍 Spring 是如何创建 AOP 代理的。Spring 创建一个 AOP 代理的基本方法是使用 org.springframework.aop.framework.ProxyFactoryBean这个类对应的切入点和通知提供了完整的控制能力并可以生成指定的内容。ProxyFactoryBean 类中的常用可配置属性如表所示具体实例我们还是先定义一个接口和一个实现类CustomerDao、CustomerDaoImpl同“Spring JDK动态代理“示例内容一样只是需要对CustomerDaoImpl加上注解Component(customerDao)主要是为了减少配置。Component(customerDao) public class CustomerDaoImpl implements CustomerDao { // 方法同“Spring JDK动态代理“示例内容 }然后定义一个切面类Component public class MyAspect implements MethodInterceptor { public Object invoke(MethodInvocation mi) throws Throwable { System.out.println(方法执行之前); Object obj mi.proceed(); System.out.println(方法执行之后); return obj; } }下面这个非常重要就是在applicationContext.xml文件中增加相应配置context:component-scan base-packagecom.java.spring.aop.xml / !--生成代理对象 -- bean idcustomerDaoProxy classorg.springframework.aop.framework.ProxyFactoryBean !--代理实现的接口 -- property nameproxyInterfaces valuecom.java.spring.aop.xml.CustomerDao / !--代理的目标对象 -- property nametarget refcustomerDao / !--用通知增强目标 -- property nameinterceptorNames valuemyAspect / !-- 如何生成代理true:使用cglib; false :使用jdk动态代理 -- property nameproxyTargetClass valuetrue / /bean最后看一下测试用例public class FactoryBeanTest { Test public void test() { ApplicationContext applicationContext new ClassPathXmlApplicationContext(classpath:applicationContext.xml); CustomerDao customerDao (CustomerDao) applicationContext.getBean(customerDaoProxy); customerDao.add(); customerDao.update(); // customerDao.delete(); // customerDao.find(); } } // 输出 // 方法执行之前 // 添加客户... // 方法执行之后 // 方法执行之前 // 修改客户... // 方法执行之后这个和“Spring JDK动态代理”、“Spring CGLlB动态代理”的区别在于前两者是手动方式这个是自动方式然后结合了“通知的分类”可以参考《【Spring基础系列5】Spring AOP基础上》。AspectJ AOPAspectJ 基于XML方式直接参考文章《【Spring基础系列5】Spring AOP基础上》中的示例。AspectJ 基于Annotation方式基础知识在 Spring 中尽管使用 XML 配置文件可以实现 AOP 开发但是如果所有的相关的配置都集中在配置文件中势必会导致 XML 配置文件过于臃肿从而给维护和升级带来一定的困难。为此AspectJ 框架为 AOP 开发提供了另一种开发方式——基于 Annotation 的声明式。AspectJ 允许使用注解定义切面、切入点和增强处理而 Spring 框架则可以识别并根据这些注解生成 AOP 代理。关于 Annotation 注解的介绍如表所示具体实例先定义一个具体的实现类Component(customerDao) public class CustomerDaoImpl { public void add() throws Exception { System.out.println(添加客户...); //throw new Exception(抛出异常测试); } public void update() { System.out.println(修改客户...); } public void delete() { System.out.println(删除客户...); } public void find() { System.out.println(修改客户...); } }然后定义一个切面类Aspect Component public class MyAspect { // 用于取代aop:pointcut expressionexecution(* com.java.spring.aop.customer.*.*(..)) idmyPointCut/ Pointcut(execution(* com.java.spring.aop.customer.*.*(..))) private void myPointCut() { } // 前置通知 Before(myPointCut()) public void myBefore(JoinPoint joinPoint) { System.out.println(前置通知方法名称: joinPoint.getSignature().getName()); } // 后置通知 AfterReturning(value myPointCut()) public void myAfterReturning(JoinPoint joinPoint) { System.out.println(后置通知方法名称 joinPoint.getSignature().getName()); } // 环绕通知 Around(myPointCut()) public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println(环绕开始); // 开始 Object obj proceedingJoinPoint.proceed(); // 执行当前目标方法 System.out.println(环绕结束); // 结束 return obj; } // 异常通知 AfterThrowing(value myPointCut(), throwing e) public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println(异常通知出错了); } // 最终通知 After(myPointCut()) public void myAfter() { System.out.println(最终通知); } }这里和“AspectJ 基于XML方式”一样需要在applicationContext.xml文件中增加相应配置context:component-scan base-packagecom.java.spring.aop.customer / context:component-scan base-packagecom.java.spring.aop.annotation / aop:aspectj-autoproxy proxy-target-classtrue/前面两行是自动注入注解的包第三行是需要开启AOP下面是测试用例public class AnnotationTest { Test public void test() throws Exception { ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml); // 从spring容器获取实例 CustomerDaoImpl customerDao (CustomerDaoImpl) applicationContext.getBean(customerDao); // 执行方法 customerDao.add(); } } // 输出 // 环绕开始 // 前置通知方法名称:add // 添加客户... // 环绕结束 // 最终通知 // 后置通知方法名称addAOP实现方式探讨开始看“Spring JDK动态代理”的示例时感觉这个示例非常熟悉大家可以看看《【设计模式系列6】代理模式》这篇文章其实就是通过代理模式进行前后增强唯一的区别是“Spring JDK动态代理”中对代理的对象封装成一个工厂所以是工厂模式 代理模式感觉也没啥技术含量。但是看到“AspectJ 基于XML方式”示例时我们回顾一下它的测试用例ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml); CustomerDaoImpl customerDao (CustomerDaoImpl) applicationContext.getBean(customerDao); // 执行方法 customerDao.add();大家有没有发现疑问我们直接获取customerDao这个Bean调用里面的add()方法就能实现增强这个是怎么实现的呢。我理解通过Spring拿到customerDao这个Bean其实不是customerDao本身而是customerDao的装饰类M也就是这个装设类M对customerDao的方法进行了修饰怎么修饰呢就是通过切面方法进行修饰上面听得可能有点蒙大家可以看文章《【设计模式系列7】装饰器模式》我还是直接盗用里面的图这里实体类是Chicken装饰器是RoastFood如果没有装饰器调用cook()输出的是“鸡肉”加上RoastFood装饰器修饰之后调用cook()输出的是“烤鸡肉”那么这个“烤”就是对“鸡肉”的装饰。同理CustomerDaoImpl对比ChickenMyAspect对比“烤”最后通过装饰器装饰后将MyAspect中的方法装饰到CustomerDaoImpl中。再回到我们上面的那个测试用例应该就不难理解了Spring先拿到CustomerDaoImpl类这个类其实是通过MyAspect进行装饰所以拿到的不是CustomerDaoImpl类本身而是它的装饰器最后将这个装饰器转成CustomerDaoImpl至于为什么能转是因为CustomerDaoImpl类和装饰器共同继承了CustomerDao接口。总结对于Spirng AOP获取到的实体类其实不是实体类本身而是这个实体类的装饰器这个装饰器里应该是将实体类作为了装饰器的成员变量然后通过切面MyAspect的方法进行增强这里其实就是用到了装饰器模式。这个装饰器的获取是通过工厂的封装然后装饰器中实体类方法的调用应该是采用的代理模式所以Spirng AOP至少用到了装饰器、代理模式和工厂模式然后完成整个功能的封装。
返回列表