Spring AOP
在Spring Framework简介中介绍了spring的整体框架,在Spring IOC中介绍了spring的核心功能,那么spring framework中,另外一个比较重要的模块就是Spring AOP。那么,什么是AOP呢?AOP是Aspect-Oriented Programming的缩写,是一种不同与面向对象(Object-Oriented Programming (OOP))的编程方式。其主要的设计目的是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码,也就是无入侵式的编程。
AOP概念
要理解AOP,就必须要对一些关键词非常了解,下面列表是AOP中常用的一些关键词:
- Aspect,切面,可以包含多个类,多个方法组成,由Advice,Pointcut等元素组成。在常见的事务管理中,我们通常会使用如下的配置:
<aop:config>
<aop:pointcut id="service" expression="execution(* com.ckg.luohong.biz.*.*.service.impl.*.*(..))" />
<aop:advisor pointcut-ref="service" advice-ref="advice" />
</aop:config>
- Join Point,切入点,通常指的是一个方法,也就是在调用时,匹配Pointcut表达式指定的方法。
- Advice,增强点,通常是在执行某个方法时,根据需要来执行某种操作。比如在发送邮件前,先要记录下日志,这种就是属于前置增强。发送邮件后发送通知给管理员,这种就属于后置增强点。
- Pointcut,切入点集合,通常是一个表达式,比如:execution(* com.ckg.luohong.biz.*.*.service.impl.*.*(..))。
- Introduction:为目标对象声明一些字段或者方法,通常是声明接口。
- Target Object:目标对象,需要使用AOP增强的目标类
- AOP proxy:代理对象,由spring framework动态创建
- Wearving:根据配置信息,在编译、加载类、运行时,动态的代码组织起来。
Advice的类型
在AOP的规定中,Advice的类型主要有:Before Advice,After Advice,Around Advice,After returning Advice, After throwing advice。如下图所示:
通过上面的图片,我们可以看出,这些定义都是围绕着代码的执行时间点来命名的,比如Before advice就是在执行Join point前插入一段执行代码,After advice就是在执行成功后插入一段代码,After throwing advice就是在抛出异常后执行一段代码,Around advice就是把Before advice、After advice同时都执行。
AOP的实现原理
介绍完AOP的一些基本概念后,下面我们来探讨下AOP的实现。在设计模式中,有一种设计模式叫做代理设计模式。那么什么是代理设计模式呢?在生活中,该设计模式也随处可见。比如我们经常在淘宝上海淘,通常会去某家海淘店铺购买物品,然后店主帮我们去购买,发货给我们。如下图:
在这里面,我们就是购买者,换成java角度而言,就是调用者;海淘店主就是代理对象(Proxy);实际货品提供者就是目标对象(Target object)。通过代理模式,即可为Target object提供一些而外的功能,也就是上文所述的Advice。在这里面,可能大家都很疑惑,Advice是在代码实现层是如何动态加入进去的呢?这个其实也不难,在JDK中,对于动态代理模式提供的相应的API,可以动态的生成代理对象。如下代码所示:
<span style="font-size:18px;">/**
* 接口PersonService,Target Object的接口定义,因为jdk只能生成接口的动态代理对象
* */
public interface PersonService{
public void save();
public void add();
public void update();
}
/**
* 要被代理的对象,也就是Target object
* */
public class PersonServiceImpl implements PersonService{
private String user;
public PersonServiceImpl(String user){
this.user = user;
}
//给外部提供接口
public String getUser(){
return user;
}
public void add(){
System.out.println("I am the PersonServiceImpl add() method");
}
public void update(){
}
public void save(){
}
}
</span>
<span style="font-size:18px;">import java.lang.reflect.*;
/*在这里,直接让工厂实现了InvocationHandler,所以在调用createProxy()的时候,给Proxy.netProxyInstance()的第三个参数传进去了一个this。如果要把工厂和InvocationHandler解耦,可以重新顶一个实现InvocationHandler的类*/
public class JDKProxyFactory implements InvocationHandler{
//要代理的目标对象
private Object target;
public JDKProxyFactory(Object obj){
target = obj;
}
//生产一个代理对象
public Object createProxy(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
//当客户端调用目标对象进行工作的时候,就会被这个代理对象拦截到
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
//将目标对象向下转型
PersonServiceImpl bean = (PersonServiceImpl)target;
//这里进行拦截
if(bean.getUser() != null && !"".equals(bean.getUser())){
//调用目标对象的方法
Object result = method.invoke(target, args);
return result;
}else{
System.out.println("sorry, you have no limitation to access this resource!!!");
return null;
}
}
}</span>
在代码中,我们发现创建一个代理对象并不难,主要步骤如下:
1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(...);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))
但是凡事都有弊端,java提供生成代理对象的API其实是不支持为类生成代理对象,只能给接口生成代理对象。所以在这里面,Spring AOP借助于Aspectj代理框架来完成该任务,实现类的动态代理对象的生成。有了代理模式,动态代理对象的生成,那么面向切面大功告成了。
AOP的应用
在spring framework中,AOP通常和IOC模块一同使用,借助AOP来实现声明式的事务。下面我们通过配置一个声明式的事务,通过观察日志来了解AOP的工作原理。首先我们在IOC中声明如下几个bean:
<span style="font-size:18px;"> <!-- 对数据源进行事务管理 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource" />
<!-- 配置事务传播特性 -->
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="find*" propagation="REQUIRED" />
<tx:method name="get*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="apply*" propagation="REQUIRED" />
<tx:method name="list*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!--配置一个AOP切面,同时指定advice、pointcut,那么一个最简单的AOP就配置完毕。-->
<aop:config>
<aop:pointcut id="service"
expression="execution(* com.ckg.luohong.biz.*.*.service.impl.*.*(..))" />
<aop:advisor pointcut-ref="service" advice-ref="advice" />
</aop:config></span>
单元测试类,这里面使用spring + junit的方式来运行。
<span style="font-size:18px;">package com.skg.luohong.biz.ou.system;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import com.skg.luohong.biz.ou.system.service.IDaysUserService;
/**
*
* @author 骆宏
* @date 2015-08-29 10:27:09
* @author 846705189@qq.com
* @author 15013336884
* @blog http://blog.csdn.net/u010469003
* */
@ContextConfiguration({"classpath:conf/spring-mybatis.xml",
"classpath:conf/spring.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
public class DaysUserTest{
@Autowired
private IDaysUserService service;
@Test
public void testCrud(){
System.out.println(service.countAll());
System.out.println(service.findAll());
}
}
</span>
日志输出,下面为IOC容器初始化bean的过程,下面的日志为IOC创建Pointcut, Advice,TransactionManager bean的过程:
<span style="font-size:18px;">2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Finished creating instance of bean 'sqlSessionFactory'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.mybatis.spring.mapper.MapperScannerConfigurer#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Creating shared instance of singleton bean 'transactionManager'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Creating instance of bean 'transactionManager'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Eagerly caching bean 'transactionManager' to allow for resolving potential circular references
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'dataSource'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Invoking afterPropertiesSet() on bean with name 'transactionManager'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Finished creating instance of bean 'transactionManager'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Creating shared instance of singleton bean 'advice'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Creating instance of bean 'advice'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Eagerly caching bean 'advice' to allow for resolving potential circular references
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'transactionManager'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Creating instance of bean '(inner bean)'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [save*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [del*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [update*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [add*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [countAll*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [find*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [get*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [insert*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [apply*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource]-[DEBUG] Adding transactional method [list*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Finished creating instance of bean '(inner bean)'
2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Invoking afterPropertiesSet() on bean with name 'advice'</span>
下面的日志展示了AOP对事物的拦截:
<span style="font-size:18px;">2015-08-29 10:39:01 [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'transactionManager'
2015-08-29 10:39:01 [org.springframework.jdbc.datasource.DataSourceTransactionManager]-[DEBUG] Creating new transaction with name [testCrud]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2015-08-29 10:39:01 [org.springframework.jdbc.datasource.DataSourceTransactionManager]-[DEBUG] Acquired Connection [com.mysql.jdbc.JDBC4Connection@3ff1b8db] for JDBC transaction
2015-08-29 10:39:01 [org.springframework.jdbc.datasource.DataSourceTransactionManager]-[DEBUG] Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@3ff1b8db] to manual commit
2015-08-29 10:39:01 [org.springframework.test.context.transaction.TransactionalTestExecutionListener]-[DEBUG] No method-level @Rollback override: using default rollback [true] for test context [TestContext@7475b962 testClass = DaysUserTest, testInstance = com.skg.luohong.biz.ou.system.DaysUserTest@5cde0ca9, testMethod = testCrud@DaysUserTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@76115ae0 testClass = DaysUserTest, locations = '{classpath:conf/spring-mybatis.xml, classpath:conf/spring.xml}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]
2015-08-29 10:39:01 [org.springframework.test.context.transaction.TransactionalTestExecutionListener]-[INFO] Began transaction (1): transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@19dde4d9]; rollback [true]</span>
总结
AOP是一种不同于OOP的编程风格,在事务管理,日志管理等需要横跨多个类的地方,有着非常广泛的用途。AOP本身的概念其实并不难,如果对代理设计模式掌握的较好,那么AOP就更加不在话下。最近这段时间,工作较为充实,抽空把Spring AOP的知识整理了一番,受益匪浅。