一、代碼實踐
1)經典的Spring Aop
經典的spring aop,是基於動態代理技術的。實現方式上,最常用的是實現MethodInterceptor接口來提供環繞通知,創建若干代理,然后使用ProxyBeanFactory配置工廠bean,生成攔截器鏈,完成攔截。示例如下:
1 package demo.spring; 2 3 import org.aopalliance.intercept.MethodInterceptor; 4 import org.aopalliance.intercept.MethodInvocation; 5 import org.junit.Test; 6 import org.junit.runner.RunWith; 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.test.context.ContextConfiguration; 9 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 10 11 @RunWith(SpringJUnit4ClassRunner.class) 12 @ContextConfiguration("classpath:spring-config.xml") 13 public class TraditionalSpringAopDemo { 14 @Autowired 15 private Service proxy; 16 17 @Test 18 public void test() { 19 proxy.execute("hello world!"); 20 } 21 } 22 23 interface Service { 24 void execute(String str); 25 } 26 27 class ServiceImpl implements Service { 28 @Override 29 public void execute(String str) { 30 System.out.println("execute invoke: " + str); 31 } 32 } 33 34 class Interceptor1 implements MethodInterceptor { 35 @Override 36 public Object invoke(MethodInvocation methodInvocation) throws Throwable { 37 System.out.println("interceptor1,before invoke"); 38 Object ret = methodInvocation.proceed(); 39 System.out.println("interceptor1,after invoke"); 40 return ret; 41 } 42 } 43 44 class Interceptor2 implements MethodInterceptor { 45 @Override 46 public Object invoke(MethodInvocation methodInvocation) throws Throwable { 47 System.out.println("interceptor2,before invoke"); 48 Object ret = methodInvocation.proceed(); 49 System.out.println("interceptor2,after invoke"); 50 return ret; 51 } 52 }
xml文件配置:
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/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> 7 8 <context:component-scan base-package="demo.spring"/> 9 10 <bean class="demo.spring.ServiceImpl" id="service"></bean> 11 <bean class="demo.spring.Interceptor1" id="interceptor1"></bean> 12 <bean class="demo.spring.Interceptor2" id="interceptor2"></bean> 13 <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy"> 14 <property name="target" ref="service"/> 15 <property name="interceptorNames"> 16 <list> 17 <value>interceptor1</value> 18 <value>interceptor2</value> 19 </list> 20 </property> 21 </bean> 22 </beans>
結果:
interceptor1,before invoke interceptor2,before invoke execute invoke: hello world! interceptor2,after invoke interceptor1,after invoke
可以看到攔截鏈的執行過程與攔截器順序的關系。
2)spring中的聲明式aop
上述經典的spring aop,編碼起來十分繁瑣,spring框架中已經提供了新的方式,更簡潔地完成切面的配置與使用。spring在aop命名空間中,提供了切面、切點、通知簡潔的聲明方式,使得spring aop更加易於配置和使用。另外,還提供了與之配套的切面系列注解。Spring AOP的demo,見下面的小例子。
* 首先是利用<aop:config>元素,聲明切面的方式:
1 package demo.spring; 2 3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.junit.Test; 5 import org.junit.runner.RunWith; 6 import org.springframework.beans.factory.annotation.Autowired; 7 import org.springframework.test.context.ContextConfiguration; 8 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 10 @RunWith(SpringJUnit4ClassRunner.class) 11 @ContextConfiguration("classpath:spring-config.xml") 12 public class AopDemo { 13 @Autowired 14 private Target target; 15 16 @Test 17 public void testSayHello() { 18 target.doSomething("hello world"); 19 } 20 } 21 22 class Target { 23 24 public void doSomething(String params) { 25 System.out.println(params); 26 } 27 } 28 29 class Interceptor3 { 30 31 public void invoke(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 32 System.out.println("interceptor3, before invoke"); 33 proceedingJoinPoint.proceed(); 34 System.out.println("interceptor3, after invoke"); 35 } 36 } 37 38 class Interceptor4 { 39 40 public void invoke(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 41 System.out.println("interceptor4, before invoke"); 42 proceedingJoinPoint.proceed(); 43 System.out.println("interceptor4, after invoke"); 44 } 45 }
相應xml配置:
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/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> 7 8 <context:component-scan base-package="demo.spring"/> 9 10 <bean class="demo.spring.Interceptor3" id="interceptor3"/> 11 <bean class="demo.spring.Interceptor4" id="interceptor4"/> 12 13 <bean class="demo.spring.Target" id="target"/> 14 <aop:config> 15 <aop:pointcut id="pointcut" expression="execution(* demo.spring.Target.doSomething(String))" /> 16 <aop:aspect ref="interceptor3"> 17 <aop:around pointcut-ref="pointcut" method="invoke"/> 18 </aop:aspect> 19 <aop:aspect ref="interceptor4"> 20 <aop:around pointcut-ref="pointcut" method="invoke"/> 21 </aop:aspect> 22 </aop:config> 23 24 </beans>
3)@Aspect注解
下面是不使用<aop:config>,而是使用更加簡潔的@Aspect注解,完成切面的生成與配置:
1 package demo.spring; 2 3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.aspectj.lang.annotation.Around; 5 import org.aspectj.lang.annotation.Aspect; 6 import org.junit.Test; 7 import org.junit.runner.RunWith; 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.stereotype.Component; 10 import org.springframework.test.context.ContextConfiguration; 11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 13 @RunWith(SpringJUnit4ClassRunner.class) 14 @ContextConfiguration("classpath:spring-config.xml") 15 public class AopDemo { 16 @Autowired 17 private Target target; 18 19 @Test 20 public void testSayHello() { 21 target.doSomething("hello world"); 22 } 23 } 24 25 @Component 26 class Target { 27 28 public void doSomething(String params) { 29 System.out.println(params); 30 } 31 } 32 33 @Aspect 34 @Component 35 class Interceptor5 { 36 37 @Around("execution(* demo.spring.Target.doSomething(String))") 38 public void invoke(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 39 System.out.println("interceptor5, before invoke"); 40 proceedingJoinPoint.proceed(); 41 System.out.println("interceptor5, after invoke"); 42 } 43 }
spring-config.xml文件配置如下:
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/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> 7 8 <context:component-scan base-package="demo.spring"/> 9 <aop:aspectj-autoproxy /> 10 11 </beans>
結果:
interceptor5, before invoke
hello world
interceptor5, after invoke
值得說明的點:
* context:component-scan,使用@Component自動發布bean,需要配置這個元素;
* aop:aspectj-autoproxy,使用@AspectJ及其它AOP注解需要配置,否則無法使用注解;@AspectJ注解,將@Component自動發布出來的"interceptor" bean轉換為一個aspectj切面,而@Pointcut、@Before、@After、@Around等注解,功能與在xml文件中配置是一樣的;@Pointcut注解下面的方法內容無意義,只是要求一個相應方法提供注解依附。
* 注解只能在使用能獲得源碼的場景,如果不能獲取源碼,則只能通過xml配置的形式,將指定的對象配置成攔截器,對指定的目標進行攔截;因此,通過xml文件配置,而不是注解,是更加通用的方式。
* 除基礎的springframework框架的jar包外,還需要依賴cglib、aspectj的jar包,maven配置:
1 <dependency> 2 <groupId>cglib</groupId> 3 <artifactId>cglib</artifactId> 4 <version>2.2</version> 5 </dependency> 6 <dependency> 7 <groupId>org.aspectj</groupId> 8 <artifactId>aspectjweaver</artifactId> 9 <version>1.6.11</version> 10 </dependency>
二、框架實現原理
Spring框架中的AOP攔截技術,是POJO的方法層面的攔截。其底層實現原理,是動態代理技術。對於面向接口的方法攔截,依賴於jdk的動態代理技術,即java.lang.reflect.Proxy#newProxyInstance,將對被代理的目標對象的調用,委托到代理對象,觸發攔截通知;而當被攔截的方法, 不是在接口中定義時,使用的是cglib,對字節碼進行動態增強,生成被代理類的子對象,以實現代理。下面是兩種代理技術實現原理描述的demo:
1 package demo.spring; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 7 import org.springframework.cglib.proxy.Enhancer; 8 9 /** 10 * jdk動態代理實現aop攔截 11 * @author jacksonshi 12 * @version $Id: DynamicProxy.java, v 0.1 16/9/11, 11:02 jacksonshi Exp $ 13 */ 14 public class AopProxy { 15 16 public static Object createProxyByJdkDynamicProxy(final Object target) { 17 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass() 18 .getInterfaces(), new InvocationHandler() { 19 @Override 20 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 21 before(proxy, method, args); 22 Object ret = method.invoke(target, args); 23 after(proxy, method, args); 24 return ret; 25 } 26 }); 27 } 28 29 public static <T> T createProxyByCglib(final T target) { 30 Enhancer enhancer = new Enhancer(); 31 enhancer.setClassLoader(AopProxy.class.getClassLoader()); 32 enhancer.setSuperclass(target.getClass()); 33 enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() { 34 @Override 35 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 36 before(proxy, method, args); 37 Object ret = method.invoke(target, args); 38 after(proxy, method, args); 39 return ret; 40 } 41 }); 42 return (T)enhancer.create(); 43 } 44 45 private static void before(Object proxy, Method method, Object[] args) { 46 System.out.println("do something before: " + method.getName()); 47 } 48 49 private static void after(Object proxy, Method method, Object[] args) { 50 System.out.println("do something after: " + method.getName()); 51 } 52 53 public static void main(String[] args) { 54 ITarget t = new TargetImpl(); 55 ITarget proxy = (ITarget) createProxyByJdkDynamicProxy(t); 56 proxy.sayHello(); 57 58 TargetImpl cglibProxy = (TargetImpl) createProxyByCglib(t); 59 cglibProxy.sayHello(); 60 cglibProxy.sayHello2(); 61 } 62 63 } 64 65 interface ITarget { 66 void sayHello(); 67 } 68 69 class TargetImpl implements ITarget { 70 @Override 71 public void sayHello() { 72 System.out.println("hello"); 73 } 74 75 public void sayHello2() { 76 System.out.println("hello 2!"); 77 } 78 }
createProxyByJdkDynamicProxy()方法,利用jdk的動態代理技術,對TargetImpl#sayHello()進行代理,生成的代理對象是ITarget接口的一個實例,其只有sayHello()接口是可見的,因此也只能攔截sayHello();
createProxyByCglib()方法,利用cglib庫,對TargetImpl的兩個方法均可進行代理,無論是否是接口中定義的方法;
在spring框架中,動態代理的策略是如果被攔截的方法,是接口中定義的方法,以jdk動態代理生成代理對象,實現通知;否則,使用cglib進行生成子類代理實例,實現通知;事實上,無論是否在接口中定義的方法,均可使用cglib生成動態代理對象,完成攔截和通知,是更通用的方式,但由於jdk動態代理的性能更佳,因此spring框架中優先選擇jdk動態代理技術。
總結:
* spring實現aop,動態代理技術的兩種實現是jdk動態代理、cglib代理,根據被通知的方法是否為接口方法,來選擇使用哪種代理生成策略
* jdk動態代理,原理是實現接口的實例,攔截定義於接口中的目標方法,性能更優,是spring生成代理的優先選擇
* cglib代理,原理是使用cglib庫中的字節碼動態生成技術,生成被代理類的子類實例,可以攔截代理類中的任一public方法的調用,無論目標方法是否定義於接口中,更通用,但性能相對jdk代理差一些;