spring_aop

主业务经常需要调用系统级业务(交叉业务),如果在主业务代 码中大量的调用系统级业务代码,会使系统级业务与主业务深度耦合在一 起,大大影响了主业务逻辑的可读性,降低了代码的可维护性,同时也增 加了开发难度。         所以,可以采用动态代理方式。动态代理是 OCP 开发原则的一 个重要体现:在不修改主业务逻辑的前提下,扩展和增强其功能。  1 package com.tongji.test;   2    3 import java.lang.reflect.InvocationHandler;   4 import java.lang.reflect.Method;  5 import java.lang.reflect.Proxy;   6    7 import org.junit.Test;   8    9 import com.tongji.service.ISomeService;  10 import com.tongji.service.SomeServiceImpl;  11 import com.tongji.utils.SomeUtils;  12   13 public class MyTest {  14     @Test  15     public void test01() {  16         final ISomeService target = new  SomeServiceImpl();  17         ISomeService proxy = (ISomeService)  Proxy.newProxyInstance(  18                          target.getClass().getClassLoader(),  
19                          target.getClass().getInterfaces(),  20                         new InvocationHandler() {  21                               22                             //织入:交叉业务逻辑切入到 主业务中,就在这里完成的  23                             @Override  24                             public Object  invoke(Object proxy, Method method, Object[] args)  25                                     throws  Throwable {  26                                  SomeUtils.doTransaction();  27                                 Object result =  method.invoke(target, args);  28                                 SomeUtils.doLog();  29                                 return result;  30                             }  31                         });  32         proxy.doSome();  33         proxy.doSecond();  34     }  35 }         AOP简介:         AOP(Aspect Orient Programming),面向切面编程,是面向对 象编程 OOP 的一种补充。
        面向对象编程是从静态角度考虑程序的结构,而面向切面编程是 从动态角度考虑程序运行过程。AOP 底层,就是采用动态代理模式(代理 模式见此链接)实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的 动态代理。          面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容 器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、 与主业务逻辑无关的代码,如安全检查、事务、日志等。          若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻 辑混合在一起。这样,会使主业务逻辑变的混杂不清。          例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记 录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑 间并无直接关系。但,它们的代码量所占比重能达到总代码量的一半甚至 还多。它们的存在,不仅产生了大量的“冗余”代码, 还大大干扰了主业务逻辑­­­转账。         AOP编程术语:             (1)切面(Aspect)                  切面泛指交叉业务逻辑。上例中的事务处理、日志处理 就可以理解为切面。常用的切面有通知与顾问。实际就是对主业务逻辑的 一种增强。              (2)织入(Weaving)                  织入是指将切面代码插入到目标对象的过程。上例中  invoke()方法完成的工作,就可以称为织入。              (3)切入点(Pointcut)                  切入点指切面具体织入的位置。在 StudentServiceImpl  类中,若 doSome()将被增强,而doOther()不被增强,则 doSome()为切 入点,而 doOther()仅为连接点。被标记为 final 的方法是不能作为连接点 与切入点的。因为最终的是不能被修的,不能被增强的。             (4)目标对象(Target) 
                目标对象指将要被增强的对象。即包含主业务逻辑的类 的对象。上例中的StudentServiceImpl 的对象若被增强,则该类称为目标 类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标 了。              (5)通知(Advice)                  通知是切面的一种实现,可以完成简单织入功能(织入 功能就是在这里完成的)。换个角度来说,通知定义了增强代码切入到目 标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型 不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。               (6)顾问(Advisor)                  顾问是切面的另一种实现,能够将通知以更为复杂的方 式织入到目标对象中,是将通知包装为更复杂切面的装配器。 
    通知(Advice):         通知(Advice),切面的一种实现,可以完成简单织入功能(织 入功能就是在这里完成的)。常用通知有:前置通知、后置通知、环绕通 知、异常处理通知。          通知的用法步骤:             (1)定义目标类                    定义目标类,就是定义之前的普通 Bean 类,也就是即 将被增强的 Bean 类。              (2)定义通知类                    通知类是指,实现了相应通知类型接口的类。当前,实 现了这些接口,就要实现这些接口中的方法,而这些方法的执行,则是根 据不同类型的通知,其执行时机不同。                    前置通知:在目标方法执行之前执行                    后置通知:在目标方法执行之后执行                    环绕通知:在目标方法执行之前与之后均执行 
                  异常处理通知:在目标方法执行过程中,若发生指定异 常,则执行通知中的方法              (3)注册目标类                    即在 Spring 配置文件中注册目标对象 Bean。             (4)注册通知切面                    即在 Spring 配置文件中注册定义的通知对象 Bean。             (5)注册代理工厂 Bean 类对象 ProxyFactoryBean             (6)客户端访问动态代理对象                    客户端访问的是动态代理对象,而非原目标对象。因为 代理对象可以将交叉业务逻辑按照通知类型,动态的织入到目标对象的执 行中。           通知详解:             下面详解的共同代码:             主业务接口(底层使用JDK的动态代理时使用,使用CGLIB 动态代理时不用): 1 package com.tongji.aop05;  2   3 //主业务接口  4 public interface ISomeService {  5     public void doSome();  6     public String doSecond();  7     public void doThird();  8 }             目标类:  1 package com.tongji.aop05;   2  
 3 //目标类   4 public class SomeServiceImpl implements  ISomeService {   5    6     @Override   7     public void doSome() {   8         System.out.println(“执行doSome()方法”);   9     }  10   11     @Override  12     public String doSecond() {  13         System.out.println(“执行doSecond()方法”);  14         return “China”;  15     }  16   17     @Override  18     public void doThird() {  19         System.out.println(“执行doThird()方法”);  20     }  21   22 }           测试类:  1 package com.tongji.aop06;   2    3 import org.junit.Test;   4 import  org.springframework.context.ApplicationContext; 
 5 import  org.springframework.context.support.ClassPathXmlApplicationCo
 6    7 public class MyTest {   8        9     @Test  10     public void test01() {  11         String resource =  “com/tongji/aop06/applicationContext.xml”;  12         @SuppressWarnings(“resource”)  13         ApplicationContext ac = new  ClassPathXmlApplicationContext(resource);  14         ISomeService service = (ISomeService)  ac.getBean(“serviceProxy”);  15         service.doSome();  16         System.out.println(“­­­­­­­­­­­­­­­­­­­”);  17         String second = service.doSecond();  18         System.out.println(second);  19         System.out.println(“­­­­­­­­­­­­­­­­­­­”);  20         service.doThird();  21     }  22       23 }           (1) 前置通知 MethodBeforeAdvice                  定义前置通知,需要实现 MethodBeforeAdvice 接口。 该接口中有一个方法 before(),会在目标方法执行之前执行。                 通知类:
 1 package com.tongji.aop01;   2    3 import java.lang.reflect.Method;  4    5 import org.springframework.aop.MethodBeforeAdvice;   6    7 //前置通知   8 public class MyMethodBeforeAdvice implements  MethodBeforeAdvice {   9       10     //该方法在目标方法执行之前执行  11     //method:目标方法  12     //args:目标方法的参数列表  13     //target:目标对象  14     @Override  15     public void before(Method method, Object[]  args, Object target)  16             throws Throwable {  17         System.out.println(“目标方法执行之前:对目标对象 的增强代码就是写在这里的”);  18           19     }  20   21 }                 Spring配置文件:  1 <?xml version="1.0" encoding="UTF­8"?>   2 <beans 
xmlns=“http://www.springframework.org/schema/beans”   3     xmlns:xsi=“http://www.w3.org/2001/XMLSchemainstance”   4     xsi:schemaLocation="   5         http://www.springframework.org/schema/beans     6          http://www.springframework.org/schema/beans/springbeans.xsd">   7    8     <!­­ 目标对象 ­­>       9       10     <!­­ 切面:前置通知 ­­>      11       12       13     <!­­ 代理对象的生成:注意这里的ProxyFactoryBefan不是 代理类,而是代理工厂 ­­>  14     
15           16         <!­­    ­­>  17         <!­­ 可以不写接口,因为默认会自动检测到目标类所实现 的所有接口 ­­>  18         <!­­    ­­> 
19           20       21              (2) 后置通知 AfterRunningAdvice:                 定义后置通知,需要实现接口 AfterReturningAdvice。 该接口中有一个方法 afterReturning ()。                 通知类:  1 package com.tongji.aop02;   2    3 import java.lang.reflect.Method;  4    5 import  org.springframework.aop.AfterReturningAdvice;   6    7 //后置通知   8 public class MyAfterReturningAdvice implements  AfterReturningAdvice {   9   10     //returnValue:目标方法返回值,后置通知只能获取返回值不 能修改返回值  11     @Override  12     public void afterReturning(Object returnValue,  Method method,  13             Object[] args, Object target) throws  Throwable {  14         System.out.println(“目标方法执行之后,目标方法返
回值为” + returnValue);  15           16     }  17   18 }                 Spring配置文件:  1 <?xml version="1.0" encoding="UTF­8"?>   2 <beans  xmlns=“http://www.springframework.org/schema/beans”   3     xmlns:xsi=“http://www.w3.org/2001/XMLSchemainstance”   4     xsi:schemaLocation="   5         http://www.springframework.org/schema/beans     6          http://www.springframework.org/schema/beans/springbeans.xsd">   7    8     <!­­ 目标对象 ­­>       9       10     <!­­ 切面:后置通知 ­­>      11       12       13     <!­­ 代理对象的生成 ­­>  14     
15           16           17       18              (3) 环绕通知 MethodInterceptor:                 定义环绕通知,需要实现 MethodInterceptor 接口。环 绕通知,也叫方法拦截器,可以在目标方法调用之前及之后做处理,可以 改变目标方法的返回值,也可以改变程序执行流程。                 通知类:  1 package com.tongji.aop03;   2    3 import org.aopalliance.intercept.MethodInterceptor;   4 import org.aopalliance.intercept.MethodInvocation;   5    6 public class MyMethodInterceptor implements  MethodInterceptor {   7    8     @Override   9     public Object invoke(MethodInvocation  invocation) throws Throwable {  10         System.out.println(“目标方法执行之前”);  11         //调用目标方法  12         Object result = invocation.proceed();  13         System.out.println(“目标方法执行之后”);  14         if (result != null) { 
15             result =  ((String)result).toUpperCase();  16         }  17         return result;  18     }  19   20 }                 Spring配置文件:  1 <?xml version="1.0" encoding="UTF­8"?>   2 <beans  xmlns=“http://www.springframework.org/schema/beans”   3     xmlns:xsi=“http://www.w3.org/2001/XMLSchemainstance”   4     xsi:schemaLocation="   5         http://www.springframework.org/schema/beans     6          http://www.springframework.org/schema/beans/springbeans.xsd">   7    8     <!­­ 目标对象 ­­>       9       10     <!­­ 切面:环绕通知 ­­>      11       12       13     <!­­ 代理对象的生成 ­­> 
14     
15           16           17       18              (4) 异常通知 ThrowsAdvice:                 定义异常通知,需要实现 ThrowsAdvice 接口。该接口 的主要作用是,在目标方法抛出异常后,根据异常的不同做出相应的处 理。当该接口处理完异常后,会简单地将异常再次抛出给目标方法。                  不过,这个接口较为特殊,从形式上看,该接口中没有 必须要实现的方法。但,这个接口却确实有必须要实现的方法  afterThrowing()。这个方法重载了四种形式。由于使用时,一般只使用其 中一种,若要都定义到接口中,则势必要使程序员在使用时必须要实现这 四个方法。这是很麻烦的。所以就将该接口定义为了标识接口(没有方法 的接口)。使用方法时要参考接口源代码中的提示。                 自定义异常类(略)                 主业务接口: 1 package com.tongji.aop04;  2   3 //主业务接口  4 public interface ISomeService {  5     boolean check(String username, String password)  throws UserException;  6 }                 目标类:
 1 package com.tongji.aop04;   2    3 //目标类   4 public class SomeServiceImpl implements  ISomeService {   5    6     //目标方法   7     @Override   8     public boolean check(String username, String  password) throws UserException {   9         if (!“qjj”.equals(username)) {  10             throw new UsernameException(“用户名不正 确”);  11         }  12           13         if (!“248xiaohai”.equals(password)) {  14             throw new PasswordException(“密码不正 确”);  15         }  16           17         return true;  18     }  19       20 }                 通知类:  1 package com.tongji.aop04;   2  
 3 import org.springframework.aop.ThrowsAdvice;   4    5 //异常处理通知   6 public class MyThrowsAdvice implements ThrowsAdvice  {   7        8     //若发生UsernameException异常,则该方法会自动被调用执 行   9     public void afterThrowing(UsernameException ex)  {  10         System.out.println(“发生用户名异常,ex=” +  ex);  11     }  12       13     //若发生PasswordException异常,则该方法会自动被调用执 行  14     public void afterThrowing(PasswordException ex)  {  15         System.out.println(“发生密码异常,ex=” + ex);  16     }  17 }               这里的参数 ex 为,与具体业务相关的用户自定义的异常 类对象。容器会根据异常类型的不同,自动选择不同的该方法执行。这些 方法的执行是在目标方法执行结束后执行的。               测试类:  1 package com.tongji.aop04;   2  
 3 import org.junit.Test;   4 import  org.springframework.context.ApplicationContext;   5 import  org.springframework.context.support.ClassPathXmlApplicationCo
 6    7 public class MyTest {   8        9     @Test  10     public void test01() {  11         String resource =  “com/tongji/aop04/applicationContext.xml”;  12         @SuppressWarnings(“resource”)  13         ApplicationContext ac = new  ClassPathXmlApplicationContext(resource);  14         ISomeService service = (ISomeService)  ac.getBean(“serviceProxy”);  15         try {  16             service.check(“qjj1”, “248”);  17         } catch (UserException e) {  18             e.printStackTrace(); 19         }  20     }  21       22 }             (5) 给目标方法织入多个切面:
 1 <?xml version="1.0" encoding="UTF­8"?>   2 <beans  xmlns=“http://www.springframework.org/schema/beans”   3     xmlns:xsi=“http://www.w3.org/2001/XMLSchemainstance”   4     xsi:schemaLocation="   5         http://www.springframework.org/schema/beans     6          http://www.springframework.org/schema/beans/springbeans.xsd">   7    8     <!­­ 目标对象 ­­>       9       10     <!­­ 切面:通知 ­­>      11       12       13       14     <!­­ 代理对象的生成 ­­>  15     
16           17          
18       19              (6) 无主业务接口时,AOP底层使用CGLIB动态代理:                 目标类(无接口),其他代码均相同:  1 package com.tongji.aop09;   2    3 //目标类   4 public class SomeService {   5    6     public void doSome() {   7         System.out.println(“执行doSome()方法”);   8     }   9   10     public String doSecond() {  11         System.out.println(“执行doSecond()方法”);  12         return “China”;  13     }  14   15 }           (7) 有主业务接口时,迫使AOP底层使用CGLIB动态代理:                 Spring配置文件,其他代码都不需要改变:  1 <?xml version="1.0" encoding="UTF­8"?>   2 <beans  xmlns=“http://www.springframework.org/schema/beans”   3     xmlns:xsi=“http://www.w3.org/2001/XMLSchemainstance” 
 4     xsi:schemaLocation="   5         http://www.springframework.org/schema/beans     6          http://www.springframework.org/schema/beans/springbeans.xsd">   7    8     <!­­ 目标对象 ­­>       9       10     <!­­ 切面:后置通知 ­­>      11       12       13     <!­­ 代理对象的生成 ­­>  14     
15           16           17         <!­­  ­­>  18           19       20            顾问(Advisor):
            通知(Advice)是 Spring 提供的一种切面(Aspect)织入方 法。但其功能过于简单:只能将切面织入到目标类的所有目标方法中,无 法完成将切面织入到指定目标方法中。               顾问(Advisor)是 Spring 提供的另一种切面织入方法。其 可以完成更为复杂的切面织入功能。PointcutAdvisor 是顾问的一种,可以 指定具体的切入点。顾问将通知进行了包装,会根据不同的通知类型,在 不同的时间点,将切面织入到不同的切入点。             PointcutAdvisor 接口有两个较为常用的实现类:                  NameMatchMethodPointcutAdvisor 名称匹配方法切入 点顾问                  RegexpMethodPointcutAdvisor 正则表达式匹配方法切 入点顾问              1. 名称匹配方法切入点顾问                  NameMatchMethodPointcutAdvisor,即名称匹配方法 切入点顾问。容器可根据配置文件中指定的方法名来设置切入点。                  Spring配置文件:  1 <?xml version="1.0" encoding="UTF­8"?>   2 <beans  xmlns=“http://www.springframework.org/schema/beans”   3     xmlns:xsi=“http://www.w3.org/2001/XMLSchemainstance”   4     xsi:schemaLocation="   5         http://www.springframework.org/schema/beans     6          http://www.springframework.org/schema/beans/springbeans.xsd">   7    8     <!­­ 目标对象 ­­>     
 9       10     <!­­ 切面:后置通知 ­­>      11       12     <!­­ 切面:名称匹配方法切入点顾问 ­­>      13     <bean id=“afterAdvisor”  class=“org.springframework.aop.support.NameMatchMethodPointcu
14           15         <!­­    ­­>  16           17         <!­­    18               19                 doSome  20                 doThird  21               22             23         ­­>  24       25       26     <!­­ 代理对象的生成 ­­>  27     
28          
29           30       31              2. 正则表达式方法切入点顾问                  RegexpMethodPointcutAdvisor,即正则表达式方法顾 问。容器可根据正则表达式来设置切入点。注意,与正则表达式进行匹配 的对象是接口中的方法名,而非目标类(接口的实现类)的方法名。                 Spring配置文件:  1 <?xml version="1.0" encoding="UTF­8"?>   2 <beans  xmlns=“http://www.springframework.org/schema/beans”   3     xmlns:xsi=“http://www.w3.org/2001/XMLSchemainstance”   4     xsi:schemaLocation=”   5         http://www.springframework.org/schema/beans     6          http://www.springframework.org/schema/beans/springbeans.xsd">   7    8     <!­­ 目标对象 ­­>       9       10     <!­­ 切面:后置通知 ­­>      11       12     <!­­ 切面:正则表达式方法切入点顾问 ­­>     
13     <bean id=“afterAdvisor”  class=“org.springframework.aop.support.RegexpMethodPointcutAd
14           15         <!­­ 正则表达式匹配的对象为:全限定性方法名,而不是 简单方法名 ­­>  16         <!­­  ­­>  17         <!­­  ­­>  18           19       20       21     <!­­ 代理对象的生成 ­­>  22     
23           24           25       26            自动代理生成器:             前面代码中所使用的代理对象,均是由 ProxyFactoryBean  代理工具类生成的。而该代理工具类存在着如下缺点: 
                (1)一个代理对象只能代理一个 Bean,即如果有两个  Bean 同时都要织入同一个切面,这时,不仅要配置这两个 Bean,即两个 目标对象,同时还要配置两个代理对象。                  (2)在客户类中获取 Bean 时,使用的是代理类的 id, 而非我们定义的目标对象 Bean 的 id。我们真正想要执行的应该是目标对 象。从形式上看,不符合正常的逻辑。              Spring 提供了自动代理生成器,用于解决  ProxyFactoryBean 的问题。常用的自动代理生成器有两个:                   默认 advisor 自动代理生成器                  Bean 名称自动代理生成器              需要注意的是,自动代理生成器均继承自 Bean 后处理器  BeanPostProcessor。容器中所有 Bean 在初始化时均会自动执行 Bean  后处理器中的方法,故其无需 id 属性。所以自动代理生成器的 Bean 也没 有 id 属性,客户类直接使用目标对象 bean 的 id。           1. 默认 advisor 自动代理生成器                DefaultAdvisorAutoProxyCreator 代理的生成方式是,将 所有的目标对象与 Advisor 自动结合,生成代理对象。无需给生成器做任 何的注入配置。注意,只能与 Advisor 配合使用。                Spring配置文件:  1 <?xml version="1.0" encoding="UTF­8"?>   2 <beans  xmlns=“http://www.springframework.org/schema/beans”   3     xmlns:xsi=“http://www.w3.org/2001/XMLSchemainstance”   4     xsi:schemaLocation=”   5         http://www.springframework.org/schema/beans     6          http://www.springframework.org/schema/beans/spring
beans.xsd">   7    8     <!­­ 目标对象 ­­>       9       10       11     <!­­ 切面:通知 ­­>      12       13       14     <!­­ 切面:名称匹配方法切入点顾问 ­­>      15     <bean id=“afterAdvisor”  class="org.springframework.aop.support.NameMatchMethodPointcu
16           17           18       19     <bean id=“beforeAdvisor”  class=“org.springframework.aop.support.NameMatchMethodPointcu
20           21           22      
23       24     <!­­ 自动代理生成器 ­­>  25     <bean  class=“org.springframework.aop.framework.autoproxy.DefaultAdv
26                测试类:  1 package com.tongji.aop07;   2    3 import org.junit.Test;   4 import  org.springframework.context.ApplicationContext;   5 import  org.springframework.context.support.ClassPathXmlApplicationCo
 6    7 public class MyTest {   8        9     @Test  10     public void test01() {  11         String resource =  “com/tongji/aop07/applicationContext.xml”;  12         @SuppressWarnings(“resource”)  13         ApplicationContext ac = new  ClassPathXmlApplicationContext(resource);  14         ISomeService service1 = (ISomeService)  ac.getBean(“someService1”); 
15         service1.doSome();  16         service1.doSecond();  17         service1.doThird();  18         System.out.println(“­­­­­­­­­­­­­­­­­­­”);  19         ISomeService service2 = (ISomeService)  ac.getBean(“someService2”);  20         service2.doSome();  21         service2.doSecond();  22         service2.doThird();  23     }  24       25 }             2. Bean 名称自动代理生成器                  DefaultAdvisorAutoProxyCreator 会为每一个目标对象 织入所有匹配的 Advisor,不具有选择性。而  BeanNameAutoProxyCreator 的代理生成方式是,根据 bean 的 id,来为 符合相应名称的类生成相应代理对象。                 Spring配置文件:  1 <?xml version="1.0" encoding="UTF­8"?>   2 <beans  xmlns=“http://www.springframework.org/schema/beans”   3     xmlns:xsi=“http://www.w3.org/2001/XMLSchemainstance”   4     xsi:schemaLocation=”   5         http://www.springframework.org/schema/beans     6          http://www.springframework.org/schema/beans/spring
beans.xsd”>   7    8     <!­­ 目标对象 ­­>       9       10       11     <!­­ 切面:通知 ­­>      12       13       14     <!­­ 切面:名称匹配方法切入点顾问 ­­>      15     <bean id=“afterAdvisor”  class="org.springframework.aop.support.NameMatchMethodPointcu
16           17           18       19     <bean id=“beforeAdvisor”  class="org.springframework.aop.support.NameMatchMethodPointcu
20           21           22      
23       24     <!­­ 该自动代理生成器,不仅可以指定目标对象,还可以指定 切面,并且切面可以是通知或顾问 ­­>  25     <bean  class="org.springframework.aop.framework.autoproxy.BeanNameAu
26           27           28       29 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值