一、代码实践
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代理差一些;