AOP是目前Spring框架中的核心之一,在应用中具有非常重要的作用,也是Spring其他组件的基础。它是一种面向切面编程的思想。
今天来讲解下AOP的核心功能的底层实现机制:如何用动态代理来实现切面拦截。
AOP的拦截功能是由java中的动态代理来实现的。说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的Interceptor的种类,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。 那么动态代理是如何实现将切面逻辑(advise)织入到目标类方法中去的呢?下面我们就来详细介绍并实现AOP中用到的两种动态代理。 AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。 还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
一、jdk动态代理实现AOP拦截(代码中的关键地方都添加了注释
package com.service;
/**
* 该类是所有被代理类的接口类,jdk实现的代理要求被代理类基于统一的接口
* @author xx
* 2017年3月24日 下午4:17:53
*/
public interface StudentInfoService {
void findInfo(String studentName);
}
2、实现类,我们真正的业务代码,需要做切面
package com.service.impl;
import com.service.StudentInfoService;
public class StudentInfoServiceImpl implements StudentInfoService{
@Override
public void findInfo(String studentName) {
//
System.out.println("#####你目前输入的名字是:"+studentName);
System.out.println("#####业务代码xxx1");
System.out.println("#####业务代码xxx2");
}
}
3,实现动态动态代理类 3.1先写一个工厂类AOPFactory
,连接需要代理的接口(可以不写)
package com.handler;
public class AOPFactory {
public static <T> Object getAOPProxyedObject(Class<T> cls){
Object proxy = null;
MyHandler handler = new MyHandler();
try {
Object obj = cls.newInstance();
if (obj != null) {
proxy = handler.bind(obj);
} else {
System.out.println("Can't get the proxyobj");
}
} catch (Exception e) {
e.printStackTrace();
}
return proxy;
}
}
代理类MyHandler
package com.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.apache.log4j.Logger;
public class MyHandler implements InvocationHandler {
private Object proxyObj;
private static Logger log = Logger.getLogger(MyHandler.class);
public Object bind(Object obj) {
this.proxyObj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
Object result=null;
try{
//请在这里插入代码,在方法前调用
log.debug("调用log日志方法 before"+method.getName());
result=method.invoke(proxyObj,args); //原方法
//请在这里插入代码,方法后调用
log.debug("调用log日志方法 after"+method.getName());
}catch(Exception e){
e.printStackTrace();
}
return result;
}
}
4.运行结果
03/24/2017,16:35:26:com.handler.MyHandler[main]-DEBUG:调用log日志方法 beforefindInfo
#####你目前输入的名字是:阿飞
#####业务代码xxx1
#####业务代码xxx2
03/24/2017,16:35:26:com.handler.MyHandler[main]-DEBUG:调用log日志方法 afterfindInfo
5.分析一下。这个是最基本的JDK动态代理,通过java反射原理,将需要代理的method前后绑定了逻辑,不影响其方法内的运行! 缺陷是,该方法必须是通过实现接口的。
Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), this)
二、cglib动态代理实现AOP拦截
还是拿刚才的代理类做示范。 一,
cglib代理处理类(实现了MethodInterceptor类,用将目标类传进
Enhancer调用
intercept方法,intercept和jdk invoke类似)
package com.handler.AOPInstrumenter;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import org.apache.log4j.Logger;
public class AOPInstrumenter implements MethodInterceptor {
private Logger log = Logger.getLogger(AOPInstrumenter.class);
private Enhancer enhancer = new Enhancer();
public Object getInstrumentedClass(Class<?> clz) {
enhancer.setSuperclass(clz);
//this便是传进来的clz,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
// 请在这里插入代码,在方法前调用
log.debug("调用log日志方法 before" + method.getName());
Object result = proxy.invokeSuper(o, args);
// 请在这里插入代码,方法后调用
log.debug("调用log日志方法 after" + method.getName());
return result;
}
}
二 测试代码
AOPInstrumenter instrumenter=new AOPInstrumenter();
StudentInfoServiceImpl studentInfo=(StudentInfoServiceImpl)instrumenter.getInstrumentedClass(StudentInfoServiceImpl.class);
studentInfo.findInfo("阿飞");
三 ,结果
03/24/2017,17:03:11:com.handler.AOPInstrumenter.AOPInstrumenter[main]-DEBUG:调用log日志方法 beforefindInfo
#####你目前输入的名字是:阿飞
#####业务代码xxx1
#####业务代码xxx2
03/24/2017,17:03:11:com.handler.AOPInstrumenter.AOPInstrumenter[main]-DEBUG:调用log日志方法 afterfindInfo
完成,基本的原来就是这些,spring 源码使用它的AOP当然远远比这个复杂,后续再使用spring aop分析