面向方面的编程,即 AOP,是一种编程技术,它允许程序员对横切关注点或横切典型的职责分界线的行为(例如日志和事务管理)进行模块化。AOP 的核心构造是方面,
它将那些影响多个类的行为封装到可重用的模块中。
通常情况下,对于AOP,我们有两种方式来实现。
使用DynamicProxy实现AOP
下面是一个简单的示例,首先定义业务对象:
1 public interface UserDao { 2 3 void save(); 4 } 5 6 public class UserDaoImpl implements UserDao 7 { 8 private String name; 9 10 public void save() { 11 System.out.println("save() is called for " + name); 12 } 13 14 public void setName(String name) { 15 this.name = name; 16 } 17 18 public String getName() { 19 return name; 20 } 21 }
下面是一个实现了InvocationHandler的类:
1 public class ProxyFactory implements InvocationHandler 2 { 3 private Object target; 4 5 public Object createUserDao(Object target) 6 { 7 this.target = target; 8 return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), 9 this.target.getClass().getInterfaces(), this); 10 } 11 12 public Object invoke(Object proxy, Method method, Object[] args) 13 throws Throwable { 14 15 UserDaoImpl userDao = (UserDaoImpl)target; 16 Object result = null; 17 if(userDao.getName() != null) 18 { 19 result = method.invoke(target, args); 20 } 21 else 22 { 23 System.out.println("The name is null."); 24 } 25 return result; 26 } 27 }
接下来是测试代码:
1 private static void test1() 2 { 3 ProxyFactory pf = new ProxyFactory(); 4 UserDao userDao = (UserDao)pf.createUserDao(new UserDaoImpl()); 5 userDao.save(); 6 }
执行结果如下:
The name is null.
这是因为userDao的类型是UserDao,它是一个接口,并没有定义name字段,因此name=null。
使用Cglib实现AOP
同样是上面的需求,我们假设并没有继承的接口,这我们可以使用cglib来实现。
首先我们重新定义一个UserDaoImpl2,它不会实现任何接口:
1 public class UserDaoImpl2 2 { 3 private String name; 4 5 public void save() throws InterruptedException { 6 Thread.sleep(3000); 7 System.out.println("save() is called for " + name); 8 } 9 10 public void setName(String name) { 11 this.name = name; 12 } 13 14 public String getName() { 15 return name; 16 } 17 18 public void raiseException() 19 { 20 throw new RuntimeException("This is test."); 21 } 22 }
然后是创建CglibFactory:
1 public class CglibFactory implements MethodInterceptor 2 { 3 private Object target; 4 public Object createUserDao(Object target) 5 { 6 this.target = target; 7 Enhancer enhancer = new Enhancer(); 8 enhancer.setSuperclass(target.getClass()); 9 enhancer.setCallback(this); 10 return enhancer.create(); 11 } 12 13 public Object intercept(Object proxy, Method method, Object[] args, 14 MethodProxy methodProxy) throws Throwable { 15 UserDaoImpl2 userDao = (UserDaoImpl2)target; 16 if (userDao.getName() != null) 17 { 18 return method.invoke(target, args); 19 } 20 else 21 { 22 System.out.println("The name is null."); 23 } 24 return null; 25 } 26 }
它实现了MethodInterceptor接口,其中包括intercept方法,这个方法就会通过反射的方式来触发目标方法,同时还可以添加一些其他处理。
下面是测试方法:
1 private static void test2() throws InterruptedException 2 { 3 CglibFactory cf = new CglibFactory(); 4 UserDaoImpl2 temp = new UserDaoImpl2(); 5 UserDaoImpl2 userDao = (UserDaoImpl2)cf.createUserDao(temp); 6 userDao.save(); 7 temp.setName("Zhang San"); 8 userDao = (UserDaoImpl2)cf.createUserDao(temp); 9 userDao.save(); 10 }
输出结果如下:
The name is null. save() is called for Zhang San
使用Spring实现AOP
Spring框架集合了ProxyFactory和Cglib两种方式来实现AOP。
我们来看一个示例,还是使用上面定义的UserDaoImpl以及UserDaoImpl2。
首先需要定义一个interceptor:
1 @Aspect 2 public class MyInterceptor { 3 4 @Pointcut("execution (* sample.spring.aop.*.*(..))") 5 public void anyMethod(){} 6 7 @Before("anyMethod()") 8 public void before() 9 { 10 System.out.println("Before"); 11 } 12 13 @After("anyMethod()") 14 public void after() 15 { 16 System.out.println("After"); 17 } 18 19 @Around("anyMethod()") 20 public void Around(ProceedingJoinPoint pjp) throws Throwable 21 { 22 long start = System.currentTimeMillis(); 23 pjp.proceed(); 24 long end = System.currentTimeMillis(); 25 System.out.println("执行时间:" + (end - start)); 26 } 27 28 @Before("anyMethod() && args(name)") 29 public void before(String name) 30 { 31 System.out.println("The name is " + name); 32 } 33 34 @AfterReturning(pointcut="anyMethod()", returning="result") 35 public void afterReturning(String result) 36 { 37 System.out.println("The value is " + result); 38 } 39 40 @AfterThrowing(pointcut="anyMethod()", throwing="e") 41 public void afterThrowing(Exception e) 42 { 43 e.printStackTrace(); 44 } 45 }
我们可以看到上面的代码中包含了一些Annotation,这些Annotation是用来实现AOP的关键。
然后需要修改beans.xml,添加如下内容:
1 <aop:aspectj-autoproxy /> 2 <bean id="userDaoImpl" class = "sample.spring.aop.UserDaoImpl"/> 3 <bean id="userDaoImpl2" class = "sample.spring.aop.UserDaoImpl2"/> 4 <bean id="myInterceptor" class="sample.spring.aop.MyInterceptor"/>
其中第一行是让Spring打开AOP的功能,下面三行定义了三个bean,这里我们把interceptor也看做是一个bean对象。
接下来是测试代码:
1 private static void test3() throws InterruptedException 2 { 3 ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/spring/aop/beans.xml"); 4 UserDao userDao = (UserDao)ctx.getBean("userDaoImpl"); 5 userDao.save(); 6 UserDaoImpl2 userDao2 = (UserDaoImpl2)ctx.getBean("userDaoImpl2"); 7 userDao2.save(); 8 userDao2.setName("Zhang San"); 9 String name = userDao2.getName(); 10 // userDao2.raiseException(); 11 }
这里我们可以看到,测试方法中既使用了UserDaoImpl1(这里是UserDao接口),也是用了UserDaoImpl2。正如我们上面所言,在Spring中,如果类实现了接口,Spring会按照ProxyFactory的方式来处理;如果没有实现接口,Spring会按照Cglib的方式来处理。
上面测试方法的输出如下:
Before Before save() is called for null 执行时间:1 The value is null After After 执行时间:1 The value is null Before Before save() is called for null 执行时间:3001 The value is null After After 执行时间:3002 The value is null Before The name is Zhang San Before 执行时间:26 The value is null After After 执行时间:27 The value is null Before Before 执行时间:0 The value is null After After 执行时间:1 The value is null
使用Spring配置文件来配置AOP
上面的示例中,我们使用Annotation来配置AOP的信息,同样我们也可以使用xml文件的方式来配置AOP。
还是以上面定义的interceptor为基础,我们去掉里面所有的Annotation,然后在beans.xml中添加如下内容:
1 <bean id="myInterceptor2" class="sample.spring.aop.MyInterceptor2"/> 2 <aop:config> 3 <aop:aspect id="asp" ref="myInterceptor2"> 4 <aop:pointcut id="anyMethod" expression="execution (* sample.spring.aop.*.*(..))"/> 5 <aop:before pointcut-ref="anyMethod" method="before"/> 6 <aop:after pointcut-ref="anyMethod" method="after"/> 7 <aop:around pointcut-ref="anyMethod" method="around"/> 8 <aop:after-returning pointcut-ref="anyMethod" method="afterReturning" returning="result"/> 9 <aop:after-throwing pointcut-ref="anyMethod" method="afterThrowing" throwing="e"/> 10 </aop:aspect> 11 </aop:config>
测试方法和输出结果同上。