Spring AOP示例与实现原理总结——传统spring aop、基于切面注入、基于@Aspect注解的实现

一、代码实践

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代理差一些;

 

posted on 2016-09-12 00:10  浪荡绅士  阅读(11908)  评论(0编辑  收藏  举报

导航