Spring AOP 深入剖析
本文内容纲要:
AOP是Spring提供的关键特性之一。AOP即面向切面编程,是OOP编程的有效补充。使用AOP技术,可以将一些系统性相关的编程工作,独立提取出来,独立实现,然后通过切面切入进系统。从而避免了在业务逻辑的代码中混入很多的系统相关的逻辑——比如权限管理,事物管理,日志记录等等。这些系统性的编程工作都可以独立编码实现,然后通过AOP技术切入进系统即可。从而达到了将不同的关注点分离出来的效果。本文深入剖析Spring的AOP的原理。
**1.**AOP相关的概念
1)Aspect:切面,切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面;
2)Joinpoint:连接点,也就是可以进行横向切入的位置;
3)Advice:通知,切面在某个连接点执行的操作(分为:Beforeadvice,Afterreturningadvice,Afterthrowingadvice,After(finally)advice,Aroundadvice);
4)Pointcut:切点,符合切点表达式的连接点,也就是真正被切入的地方;
**2.**AOP的实现原理
AOP分为静态AOP和动态AOP。静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP为动态AOP,实现的技术为:JDK提供的动态代理技术和CGLIB(动态字节码增强技术)。尽管实现技术不一样,但都是基于代理模式,都是生成一个代理对象。
1)JDK动态代理
主要使用到InvocationHandler接口和Proxy.newProxyInstance()方法。JDK动态代理要求被代理实现一个接口,只有接口中的方法才能够被代理。其方法是将被代理对象注入到一个中间对象,而中间对象实现InvocationHandler接口,在实现该接口时,可以在被代理对象调用它的方法时,在调用的前后插入一些代码。而Proxy.newProxyInstance()能够利用中间对象来生产代理对象。插入的代码就是切面代码。所以使用JDK动态代理可以实现AOP。我们看个例子:
被代理对象实现的接口,只有接口中的方法才能够被代理:
publicinterfaceUserService{
publicvoidaddUser(Useruser);
publicUsergetUser(intid);
}
被代理对象:
publicclassUserServiceImplimplementsUserService{
publicvoidaddUser(Useruser){
System.out.println("adduserintodatabase.");
}
publicUsergetUser(intid){
Useruser=newUser();
user.setId(id);
System.out.println("getUserfromdatabase.");
returnuser;
}
}
代理中间类:
importjava.lang.reflect.InvocationHandler;
importjava.lang.reflect.Method;
publicclassProxyUtilimplementsInvocationHandler{
privateObjecttarget;//被代理的对象
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
System.out.println("dosthbefore....");
Objectresult=method.invoke(target,args);
System.out.println("dosthafter....");
returnresult;
}
ProxyUtil(Objecttarget){
this.target=target;
}
publicObjectgetTarget(){
returntarget;
}
publicvoidsetTarget(Objecttarget){
this.target=target;
}
}
测试:
importjava.lang.reflect.Proxy;
importnet.aazj.pojo.User;
publicclassProxyTest{
publicstaticvoidmain(String[]args){
ObjectproxyedObject=newUserServiceImpl();//被代理的对象
ProxyUtilproxyUtils=newProxyUtil(proxyedObject);
//生成代理对象,对被代理对象的这些接口进行代理:UserServiceImpl.class.getInterfaces()
UserServiceproxyObject=(UserService)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
UserServiceImpl.class.getInterfaces(),proxyUtils);
proxyObject.getUser(1);
proxyObject.addUser(newUser());
}
}
执行结果:
dosthbefore....
getUserfromdatabase.
dosthafter....
dosthbefore....
adduserintodatabase.
dosthafter....
我们看到在UserService接口中的方法addUser和getUser方法的前面插入了我们自己的代码。这就是JDK动态代理实现AOP的原理。
我们看到该方式有一个要求,被代理的对象必须实现接口,而且只有接口中的方法才能被代理。
2)CGLIB(codegeneratelibary)
字节码生成技术实现AOP,其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。因为需要Override被代理对象的方法,所以自然CGLIB技术实现AOP时,就必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖。我们使用CGLIB实现上面的例子:
packagenet.aazj.aop;
importjava.lang.reflect.Method;
importnet.sf.cglib.proxy.Enhancer;
importnet.sf.cglib.proxy.MethodInterceptor;
importnet.sf.cglib.proxy.MethodProxy;
publicclassCGProxyimplementsMethodInterceptor{
privateObjecttarget;//被代理对象
publicCGProxy(Objecttarget){
this.target=target;
}
publicObjectintercept(Objectarg0,Methodarg1,Object[]arg2,MethodProxyproxy)throwsThrowable{
System.out.println("dosthbefore....");
Objectresult=proxy.invokeSuper(arg0,arg2);
System.out.println("dosthafter....");
returnresult;
}
publicObjectgetProxyObject(){
Enhancerenhancer=newEnhancer();
enhancer.setSuperclass(this.target.getClass());//设置父类
//设置回调
enhancer.setCallback(this);//在调用父类方法时,回调this.intercept()
//创建代理对象
returnenhancer.create();
}
}
publicclassCGProxyTest{
publicstaticvoidmain(String[]args){
ObjectproxyedObject=newUserServiceImpl();//被代理的对象
CGProxycgProxy=newCGProxy(proxyedObject);
UserServiceproxyObject=(UserService)cgProxy.getProxyObject();
proxyObject.getUser(1);
proxyObject.addUser(newUser());
}
}
输出结果:
dosthbefore....
getUserfromdatabase.
dosthafter....
dosthbefore....
adduserintodatabase.
dosthafter....
我们看到达到了同样的效果。它的原理是生成一个父类enhancer.setSuperclass(this.target.getClass())的子类enhancer.create(),然后对父类的方法进行拦截enhancer.setCallback(this).对父类的方法进行覆盖,所以父类方法不能是final的。
**3)**接下来我们看下spring实现AOP的相关源码:
@SuppressWarnings("serial")
publicclassDefaultAopProxyFactoryimplementsAopProxyFactory,Serializable{
@Override
publicAopProxycreateAopProxy(AdvisedSupportconfig)throwsAopConfigException{
if(config.isOptimize()||config.isProxyTargetClass()||hasNoUserSuppliedProxyInterfaces(config)){
Class<?>targetClass=config.getTargetClass();
if(targetClass==null){
thrownewAopConfigException("TargetSourcecannotdeterminetargetclass:"+
"Eitheraninterfaceoratargetisrequiredforproxycreation.");
}
if(targetClass.isInterface()){
returnnewJdkDynamicAopProxy(config);
}
returnnewObjenesisCglibAopProxy(config);
}
else{
returnnewJdkDynamicAopProxy(config);
}
}
从上面的源码我们可以看到:
if(targetClass.isInterface()){
returnnewJdkDynamicAopProxy(config);
}
returnnewObjenesisCglibAopProxy(config);
如果被代理对象实现了接口,那么就使用JDK的动态代理技术,反之则使用CGLIB来实现AOP,所以Spring默认是使用JDK的动态代理技术实现AOP的。
JdkDynamicAopProxy的实现其实很简单:
finalclassJdkDynamicAopProxyimplementsAopProxy,InvocationHandler,Serializable{
@Override
publicObjectgetProxy(ClassLoaderclassLoader){
if(logger.isDebugEnabled()){
logger.debug("CreatingJDKdynamicproxy:targetsourceis"+this.advised.getTargetSource());
}
Class<?>[]proxiedInterfaces=AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
returnProxy.newProxyInstance(classLoader,proxiedInterfaces,this);
}
**3.**SpringAOP的配置
Spring中AOP的配置一般有两种方法,一种是使用aop:config标签在xml中进行配置,一种是使用注解以及@Aspect风格的配置。
**1)**基于aop:config的AOP配置
下面是一个典型的事务AOP的配置:
<tx:adviceid="transactionAdvice"transaction-manager="transactionManager"?>
<tx:attributes>
<tx:methodname="add*"propagation="REQUIRED"/>
<tx:methodname="append*"propagation="REQUIRED"/>
<tx:methodname="insert*"propagation="REQUIRED"/>
<tx:methodname="save*"propagation="REQUIRED"/>
<tx:methodname="update*"propagation="REQUIRED"/>
<tx:methodname="get*"propagation="SUPPORTS"/>
<tx:methodname="find*"propagation="SUPPORTS"/>
<tx:methodname="load*"propagation="SUPPORTS"/>
<tx:methodname="search*"propagation="SUPPORTS"/>
<tx:methodname="*"propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcutid="transactionPointcut"expression="execution(*net.aazj.service..*Impl.*(..))"/>
<aop:advisorpointcut-ref="transactionPointcut"advice-ref="transactionAdvice"/>
</aop:config>
再看一个例子:
<beanid="aspectBean"class="net.aazj.aop.DataSourceInterceptor"/>
<aop:config>
<aop:aspectid="dataSourceAspect"ref="aspectBean">
<aop:pointcutid="dataSourcePoint"expression="execution(public*net.aazj.service..*.getUser(..))"/>
<aop:pointcutexpression=""id=""/>
<aop:beforemethod="before"pointcut-ref="dataSourcePoint"/>
<aop:aftermethod=""/>
<aop:aroundmethod=""/>
</aop:aspect>
<aop:aspect></aop:aspect>
</aop:config>
aop:aspect配置一个切面;aop:pointcut配置一个切点,基于切点表达式;aop:before,aop:after,aop:around是定义不同类型的advise.aspectBean是切面的处理bean:
publicclassDataSourceInterceptor{
publicvoidbefore(JoinPointjp){
DataSourceTypeManager.set(DataSources.SLAVE);
}
}
**2)**基于注解和@Aspect风格的AOP配置
我们以事务配置为例:首先我们启用基于注解的事务配置
<!--使用annotation定义事务-->
<tx:annotation-driventransaction-manager="transactionManager"/>
然后扫描Service包:
<context:component-scanbase-package="net.aazj.service,net.aazj.aop"/>
最后在service上进行注解:
@Service("userService")
@Transactional
publicclassUserServiceImplimplementsUserService{
@Autowired
privateUserMapperuserMapper;
@Transactional(readOnly=true)
publicUsergetUser(intuserId){
System.out.println("inUserServiceImplgetUser");
System.out.println(DataSourceTypeManager.get());
returnuserMapper.getUser(userId);
}
publicvoidaddUser(Stringusername){
userMapper.addUser(username);
//inti=1/0;//测试事物的回滚
}
publicvoiddeleteUser(intid){
userMapper.deleteByPrimaryKey(id);
//inti=1/0;//测试事物的回滚
}
@Transactional(rollbackFor=BaseBusinessException.class)
publicvoidaddAndDeleteUser(Stringusername,intid)throwsBaseBusinessException{
userMapper.addUser(username);
this.m1();
userMapper.deleteByPrimaryKey(id);
}
privatevoidm1()throwsBaseBusinessException{
thrownewBaseBusinessException("xxx");
}
publicintinsertUser(Useruser){
returnthis.userMapper.insert(user);
}
}
搞定。这种事务配置方式,不需要我们书写pointcut表达式,而是我们在需要事务的类上进行注解。但是如果我们自己来写切面的代码时,还是要写pointcut表达式。下面看一个例子(自己写切面逻辑):
首先去扫描@Aspect注解定义的切面:
<context:component-scanbase-package="net.aazj.aop"/>
启用@AspectJ风格的注解:
<aop:aspectj-autoproxy/>
这里有两个属性,<aop:aspectj-autoproxyproxy-target-class="true"expose-proxy="true"/>,proxy-target-class="true"这个最好不要随便使用,它是指定只能使用CGLIB代理,那么对于final方法时会抛出错误,所以还是让spring自己选择是使用JDK动态代理,还是CGLIB.expose-proxy="true"的作用后面会讲到。
切面代码:
importorg.aspectj.lang.JoinPoint;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.annotation.Before;
importorg.aspectj.lang.annotation.Pointcut;
importorg.springframework.core.annotation.Order;
importorg.springframework.stereotype.Component;
@Aspect//foraop
@Component//forautoscan
@Order(0)//executebefore@Transactional
publicclassDataSourceInterceptor{
@Pointcut("execution(public*net.aazj.service..*.get*(..))")
publicvoiddataSourceSlave(){};
@Before("dataSourceSlave()")
publicvoidbefore(JoinPointjp){
DataSourceTypeManager.set(DataSources.SLAVE);
}
}
我们使用到了@Aspect来定义一个切面;@Component是配合context:component-scan/,不然扫描不到;@Order定义了该切面切入的顺序,因为在同一个切点,可能同时存在多个切面,那么在这多个切面之间就存在一个执行顺序的问题。该例子是一个切换数据源的切面,那么他应该在事务处理切面之前执行,所以我们使用@Order(0)来确保先切换数据源,然后加入事务处理。@Order的参数越小,优先级越高,默认的优先级最低:
/**
*Annotationthatdefinesordering.Thevalueisoptional,andrepresentsordervalue
*asdefinedinthe{@linkOrdered}interface.Lowervalueshavehigherpriority.
*Thedefaultvalueis{@codeOrdered.LOWEST_PRECEDENCE},indicating
*lowestpriority(losingtoanyotherspecifiedordervalue).
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
public@interfaceOrder{
/**
*Theordervalue.Defaultis{@linkOrdered#LOWEST_PRECEDENCE}.
*@seeOrdered#getOrder()
*/
intvalue()defaultOrdered.LOWEST_PRECEDENCE;
}
关于数据源的切换可以参加专门的博文:http://www.cnblogs.com/digdeep/p/4512368.html
**3)**切点表达式(pointcut)
上面我们看到,无论是aop:config风格的配置,还是@Aspect风格的配置,切点表达式都是重点。都是我们必须掌握的。
1>pointcut语法形式(execution):
execution(modifiers-pattern?ret-type-patterndeclaring-type-pattern?name-pattern(param-pattern)throws-pattern?)
带有?号的部分是可选的,所以可以简化成:ret-type-patternname-pattern(param_pattern)返回类型,方法名称,参数三部分来匹配。
配置起来其实也很简单:*表示任意返回类型,任意方法名,任意一个参数类型;..连续两个点表示0个或多个包路径,还有0个或多个参数。就是这么简单。看下例子:
execution(*net.aazj.service..*.get*(..)):表示net.aazj.service包或者子包下的以get开头的方法,参数可以是0个或者多个(参数不限);
execution(*net.aazj.service.AccountService.*(..)):表示AccountService接口下的任何方法,参数不限;
注意这里,将类名和包路径是一起来处理的,并没有进行区分,因为类名也是包路径的一部分。
参数param-pattern部分比较复杂:()表示没有参数,(..)参数不限,(*,String)第一个参数不限类型,第二参数为String.
2>within()语法:
within()只能指定(限定)包路径(类名也可以看做是包路径),表示某个包下或者子报下的所有方法:
within(net.aazj.service.*),within(net.aazj.service..*),within(net.aazj.service.UserServiceImpl.*)
3>this()与target():
this是指代理对象,target是指被代理对象(目标对象)。所以this()和target()分别限定代理对象的类型和被代理对象的类型:
this(net.aazj.service.UserService):实现了UserService的代理对象(中的所有方法);
target(net.aazj.service.UserService):被代理对象实现了UserService(中的所有方法);
4>args():
限定方法的参数的类型:
args(net.aazj.pojo.User):参数为User类型的方法。
5>@target(),@within(),@annotation(),@args():
这些语法形式都是针对注解的,比如带有某个注解的类,带有某个注解的方法,参数的类型带有某个注解:
@within(org.springframework.transaction.annotation.Transactional)
@target(org.springframework.transaction.annotation.Transactional)
两者都是指被代理对象类上有@Transactional注解的(类的所有方法),(两者似乎没有区别???)
@annotation(org.springframework.transaction.annotation.Transactional):方法带有@Transactional注解的所有方法
@args(org.springframework.transaction.annotation.Transactional):参数的类型带有@Transactional注解的所有方法
6>bean():指定某个bean的名称
bean(userService):bean的id为"userService"的所有方法;
bean(*Service):bean的id为"Service"字符串结尾的所有方法;
另外注意上面这些表达式是可以利用||,&&,!进行自由组合的。比如:execution(public*net.aazj.service..*.getUser(..))&&args(Integer,..)
**4.**向注解处理方法传递参数
有时我们在写注解处理方法时,需要访问被拦截的方法的参数。此时我们可以使用args()来传递参数,下面看一个例子:
@Aspect
@Component//forautoscan
//@Order(2)
publicclassLogInterceptor{
@Pointcut("execution(public*net.aazj.service..*.getUser(..))")
publicvoidmyMethod(){};
@Before("myMethod()")
publicvoidbefore(){
System.out.println("methodstart");
}
@After("myMethod()")
publicvoidafter(){
System.out.println("methodafter");
}
@AfterReturning("execution(public*net.aazj.mapper..*.*(..))")
publicvoidAfterReturning(){
System.out.println("methodAfterReturning");
}
@AfterThrowing("execution(public*net.aazj.mapper..*.*(..))")
//@Around("execution(public*net.aazj.mapper..*.*(..))")
publicvoidAfterThrowing(){
System.out.println("methodAfterThrowing");
}
@Around("execution(public*net.aazj.mapper..*.*(..))")
publicObjectAround(ProceedingJoinPointjp)throwsThrowable{
System.out.println("methodAround");
SourceLocationsl=jp.getSourceLocation();
Objectret=jp.proceed();
System.out.println(jp.getTarget());
returnret;
}
@Before("execution(public*net.aazj.service..*.getUser(..))&&args(userId,..)")
publicvoidbefore3(intuserId){
System.out.println("userId-----"+userId);
}
@Before("myMethod()")
publicvoidbefore2(JoinPointjp){
Object[]args=jp.getArgs();
System.out.println("userId11111:"+(Integer)args[0]);
System.out.println(jp.getTarget());
System.out.println(jp.getThis());
System.out.println(jp.getSignature());
System.out.println("methodstart");
}
}
方法:
@Before("execution(public*net.aazj.service..*.getUser(..))&&args(userId,..)")
publicvoidbefore3(intuserId){
System.out.println("userId-----"+userId);
}
它会拦截net.aazj.service包下或者子包下的getUser方法,并且该方法的第一个参数必须是int型的,那么使用切点表达式args(userId,..)就可以使我们在切面中的处理方法before3中可以访问这个参数。
before2方法也让我们知道也可以通过JoinPoint参数来获得被拦截方法的参数数组。JoinPoint是每一个切面处理方法都具有的参数,@Around类型的具有的参数类型为ProceedingJoinPoint。通过JoinPoint或者ProceedingJoinPoint参数可以访问到被拦截对象的一些信息(参见上面的before2方法)。
5.SpringAOP的缺陷
因为SpringAOP是基于动态代理对象的,那么如果target中的方法不是被代理对象调用的,那么就不会织入切面代码,看个例子:
@Service("userService")
@Transactional
publicclassUserServiceImplimplementsUserService{
@Autowired
privateUserMapperuserMapper;
@Transactional(readOnly=true)
publicUsergetUser(intuserId){
returnuserMapper.getUser(userId);
}
publicvoidaddUser(Stringusername){
getUser(2);
userMapper.addUser(username);
}
看到上面的addUser()方法中,我们调用了getUser()方法,而getUser()方法是谁调用的呢?是UserServiceImpl的实例,不是代理对象,那么getUser()方法就不会被织入切面代码。
切面代码如下:
@Aspect
@Component
publicclassAOPTest{
@Before("execution(public*net.aazj.service..*.getUser(..))")
publicvoidm1(){
System.out.println("inm1...");
}
@Before("execution(public*net.aazj.service..*.addUser(..))")
publicvoidm2(){
System.out.println("inm2...");
}
}
执行如下代码:
publicclassTest{
publicstaticvoidmain(String[]args){
ApplicationContextcontext=newClassPathXmlApplicationContext(
newString[]{"config/spring-mvc.xml","config/applicationContext2.xml"});
UserServiceus=context.getBean("userService",UserService.class);
if(us!=null){
us.addUser("aaa");
输出结果如下:
inm2...
虽然getUser()方法被调用了,但是因为不是代理对象调用的,所以AOPTest.m1()方法并没有执行。这就是Springaop的缺陷。解决方法如下:
首先:将<aop:aspectj-autoproxy/>改为:
<aop:aspectj-autoproxyexpose-proxy="true"/>
然后,修改UserServiceImpl中的addUser()方法:
@Service("userService")
@Transactional
publicclassUserServiceImplimplementsUserService{
@Autowired
privateUserMapperuserMapper;
@Transactional(readOnly=true)
publicUsergetUser(intuserId){
returnuserMapper.getUser(userId);
}
publicvoidaddUser(Stringusername){
((UserService)AopContext.currentProxy()).getUser(2);
userMapper.addUser(username);
}
((UserService)AopContext.currentProxy()).getUser(2);先获得当前的代理对象,然后在调用getUser()方法,就行了。
expose-proxy="true"表示将当前代理对象暴露出去,不然AopContext.currentProxy()或得的是null.
修改之后的运行结果:
inm2...
inm1...
本文内容总结:
原文链接:https://www.cnblogs.com/digdeep/p/4528353.html