那么java中基于java.lang.reflect.Proxy的动态代理模式和Spring生态有什么关系呢?Spring中的所有Bean实例存储在一个名叫IOC容器(Inversion of Control 控制反转容器)中。这个容器中存在着接口和实现类的对应关系(也可以直接存储类的实例,无需这个类有任何接口的实现),而其中Bean实例的存储方式都默认采用单例保存。
一般情况下我们可以通过BeanFactory(Spring IOC容器工厂)接口中getBean()方法,直接获取到这个接口在IOC容器中对应的实例。但当我们需要为这个Bean实例附加AOP切面操作时,这个实例就会被代理——视这个实例实现接口的情况和Spring的配置情况,又可以区别为使用Proxy动态代理还是使用Cglib代理。如下图所示:
org.springframework.aop.framework.JdkDynamicAopProxy类非常重要,它实现了Java对动态代理模式的支持,既实现了java.lang.reflect.InvocationHandler接口(当然不止是实现了这个接口)。注意:需要设定您的Spring主配置文件(这里是Spring Boot)中spring.aop.proxy-target-class=false,否则就算您依赖注入的Bean存在接口,也会使用Cglib代理。
3. Cglib代理(动态)要使用java对动态代理的原生支持,就需要被代理的类至少实现了一个接口。但如果某个需要被代理的类没有实现任何接口,又怎么办呢?这时一个比较好的选择就是使用Cglib代理,Cglib代理并不是java的原生代理模式,而是由第三方提供的一种代理方式。Cglib代理组件是Mockito Framework的一部分,其内部封装了一个java字节码生成框架ASM:
ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or dynamically generate classes, directly in binary form. Provided common transformations and analysis algorithms allow to easily assemble custom complex transformations and code analysis tools.
以上引用自OW2组织对ASM的描述(http://asm.ow2.org)。简单来说就是ASM字节码框架可在运行时动态生成Class字节码内容,或者扩展已有的某个Class。实际上Spring生态中默认使用Cglib动态代理作为主要的动态代理方式——不是java原生的java.lang.reflect.Proxy动态代理。无论被代理的Bean实例是否实现了任何接口,Cglib都能更好胜任代理工作任务。
3.1 Cglib组件基本使用以下是示例代码的执行结果:
09:27:36.939 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 通过debug观察Enhancer生成的子级类,是否有似曾相识的感觉?
09:27:36.964 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 被调用的方法:handleSomething
09:27:36.964 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 在正式调用前执行一些额外的过程
09:27:36.998 [main] INFO yinwenjie.test.proxy.cglib.target.TargetClass - 传入了一个参数:做一些对社会有益的事情!,做了一些业务处理
09:27:36.998 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 在调用执行成功后,执行一些额外的过程
3.2 Cglib替代Java原生动态代理
Cglib组件中有类似java原生动态代理的实现方式,可以完整替代java中原生动态代理的支持。这使得Spring默认使用Cglib动态代理作为主要的动态代理方式有了基础支撑。本小节我们来看看Cglib如何提供类似java原生动态代理的支持(我们使用的Cglib版本还是1.10.19)。
以下是接口和接口的实现类,也是我们需要进行进行代理的业务处理类。这里的代码示例已经在之前的文章中出现过,这里就不再赘述了:
// 被代理的接口
public interface TargetOneInterface {
// 这是一个方法
public void doSomething();
// 这是第二个方法
public void handleSomething();
}
......
// 接口实现,也是真正的业务处理类
public class TargetOneImpl implements TargetOneInterface {
// 日志
private static final Logger LOG = LoggerFactory.getLogger(TargetOneImpl.class);
@Override
public void doSomething() {
LOG.info("doSomething 方法被执行");
}
@Override
public void handleSomething() {
LOG.info("handleSomething 方法被执行");
}
}
以下我们基于Cglib提供的InvocationHandler接口,实现的代理器。注意,在Java的原生动态代理支持中,也有这个名称相同的接口——包名不一样:
......
import org.mockito.cglib.proxy.InvocationHandler;
......
// 代理者处理器
public class ProxyInvocationHandler implements InvocationHandler {
/**
* 模拟一个简单的IOC容器<br>
* 当然实际情况没有这么简单,IOC内部有一个组织结构的
*/
private static Map<Class<?>, Object> simulatorContainer = new HashMap<>();
static {
// 这是TargetOneInterface的实现
simulatorContainer.put(TargetOneInterface.class, new TargetOneImpl());
}
/**
* @param proxy 代理对象,注意是代理者,不是被代理者
* @param method 被代理的方法
* @param args 被执行的代理方法中传入的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 被代理的接口
Class<?> targetInterface = method.getDeclaringClass();
// 从容器中取出执行类
Object target = simulatorContainer.get(targetInterface);
if(target == null) {
return null;
}
// 开始调用
return method.invoke(target, args);
}
}
在以上这个代理器示例中,我们模拟了一个简易的IOC容器,并根据被代理的接口从这个简易的容器中取出接口对应的真实业务处理类,最后执行。
以下是调用代码和执行结果
public class Run {
static {
BasicConfigurator.configure();
}
public static void main(String[] args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//这是Cglib代理处理器
ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
Object proxy = Proxy.newProxyInstance(classLoader,new Class<?>[]{TargetOneInterface.class}, invocationHandler);
// 开始调用(测试第一个接口)
TargetOneInterface targetOne = (TargetOneInterface)proxy;
targetOne.doSomething();
targetOne.handleSomething();
}
}
对以上代码进行debug操作,会发现类似如下的调试信息:
从调试信息可以看出,当我们调用org.mockito.cglib.proxy.Proxy.newProxyInstance这个静态方法时,一旦执行成功,该方法就将返回一个由Cglib组件动态生成的类。上图中表示为:。后文内容中我们会阅读org.mockito.cglib.proxy.Proxy.newProxyInstance中的源代码,来来讲解其中的工作内容。以下是示例的运行结果:
[main] INFO yinwenjie.test.proxy.cglib.dproxy.target.TargetOneImpl - doSomething 方法被执行
[main] INFO yinwenjie.test.proxy.cglib.dproxy.target.TargetOneImpl - handleSomething 方法被执行
=========================
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) | 黑马程序员IT技术论坛 X3.2 |