最近在阅读到了Spring源码关于两种动态署理运用在不同场景下的运用,两种办法各有利弊写一篇文加深自己的知道。文中关于源码的触及较少,更多的是作者自己的了解和举例,然后经过部分源码验证。
首先看两个面试经常会遇到的关于Spring的问题:
- @Configuration和@Component注解的不同
- @Configuration润饰的类会被Cglib动态署理,在类内部办法彼此调用添加了@Bean注解的办法时经过在切面办法中调用getBean()办法来保证调用该办法返回的都是同一个实例
- @Component润饰的类不会被署理,每次办法内部调用都会生成新的实例,这样就不能保证其生成的方针是一个单例方针。
- @Transactional失效的原因
- @Transactional能够JDK或Cglib动态署理完成的事务(默认JDK),在Bean创立时如果检测到类中有@Transactional就会对其进行动态署理,如果类内部没有被@Transactional润饰的办法中调用了其它被@Transactional润饰的内部办法,那么此时事务注解是不会收效的,原因在于只有外部调用才会走署理增强逻辑而内部类的彼此调用只是原方针的办法调用,没有经过署理类。
其实上面能够看出出Spring在运用两种署理办法时的不同处理:@Configuration润饰的类被Cglib动态署理后,类内部办法调用也能够走增强逻辑,而含有@Transactional注解的类无论是Cglib仍是JDK动态署理都不能进行办法内部的彼此调用。
两种署理办法的调用逻辑
JDK动态署理
规范的用法
public interface TestInterface {
void sayHello();
}
public static class Test implements TestInterface {
@Override
public void sayHello() {
System.out.println("hello");
}
}
//需求界说一个完成了InvocationHandler接口的方针,目的是获取被署理类的实例和自界说增强办法
public static class MyInvocationHandler implements InvocationHandler{
//被署理类的实例方针
protected Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增强办法");
//调用被署理类的实例方针经过反射履行方针办法
Object result = method.invoke(target, args);
return result;
}
}
public static void main(String[] args) {
Test test = new Test();
TestInterface testInterface = (TestInterface) Proxy.newProxyInstance(test.getClass().getClassLoader(), Test.class.getInterfaces(), new MyInvocationHandler(test));
testInterface.sayHello();
}
下面是署理类的逻辑代码,这个署理类并不是用反编译内存中的署理类来获获得,是作者自己整了一个相似的,如果要获取真正的署理类代码网上办法许多
//署理类的父类,里边有生成署理类的首要逻辑
public static class Proxy{
//被署理方针实例的调用方针
protected InvocationHandler h;
}
//生成的署理类承继Proxy首要是为了运用父类中的InvocationHandler方针来调用被署理类方针的方针办法
//完成共同接口是为了获取需求增强的方针办法
public static class TestProxy extends Proxy implements TestInterface{
protected TestProxy(InvocationHandler h) {
super(h);
}
@Override
public void sayHello() {
try {
//这儿对获取接口办法做了简化处理
//调用父类中存储的被署理方针的handler履行署理逻辑
super.h.invoke(this, this.getClass().getInterfaces()[0].getMethods()[0],null);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
逻辑图
Cglib动态署理
规范的用法
public static class Test implements TestInterface {
@Override
public void sayHello() {
System.out.println("hello");
}
}
//完成MethodInterceptor接口注册回调函数对署理类中所有办法进行阻拦增强
public static class MyInvocationHandler implements MethodInterceptor {
//o为承继了被署理类的署理类方针,method为履行办法,objects为办法参数
//methodProxy为署理方针办法,其中有被署理办法和署理办法的映射关系
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("增强办法");
//invokeSuper办法传入的方针必须是署理类的实例方针
//invoke办法则能够穿入被署理类的实例方针,经过被署理类实例调用办法
return methodProxy.invokeSuper(o,objects);
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Test.class);
enhancer.setCallback(new MyInvocationHandler());
TestInterface testInterface = (TestInterface)enhancer.create();
testInterface.sayHello();
}
动态生成署理类的伪代码,省略了许多许多细节
//Cglib中都是经过署理类中的办法来替换被署理类,然后直接调用署理类方针的办法即可
public static class TestProxy extends Test{
public final void sayHello() {
System.out.println("增强办法");
super.sayHello();
}
}
逻辑图
==============================================================
从上面能够看出Cglib和JDK最大的差异在于Cglib完成的动态署理并没有被署理类的实例方针,所有的办法调用都是经过署理类来完成的(子类办法 -> 增强逻辑 -> 子类署理办法 -> 父类办法),而JDK则一起生成了被署理类和署理类的实例方针,然后在署理类中保存有被署理类的引证,方针办法的调用仍是被署理方针履行的。Cglib办法调用时是运用署理类方针内部办法的彼此调用完成的,因为署理类的所有办法都进行了改写,所以内部调用也会被增强,JDK办法调用时是署理类方针和被署理类方针间办法的彼此调用完成的,只有经过调用署理类方针的署理办法时才会走增强逻辑,而如果是被署理方针自己的内部调用,被署理方针办法没有改动,所以无法增强。 了解了这一点再看Spring动态署理的运用就好了解了
Spring源码验证
调用@Configuration注解的类时会用到的署理类阻拦器
//Spring中Enhancer方针注册的三种阻拦器
//回调数组,依据CALLBACK_FILTER中accept办法返回的索引值去从该数组中选择对应的Callback
private static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
//BeanMethodInterceptor的intercept办法,对父类中所有带有@Bean注解的办法都进行阻拦增强
//无论是Spring经过反射实例化Bean仍是配置类中办法的内部调用,都会经过BeanFactory来生成和获取Bean实例
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
//是否为Spring经过反射调用
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
//调用父类办法生成新的Bean方针,并将其注册到Spring上下文中
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
//类办法的内部调用,从BeanFactory中获取bean
//即便经过内部办法直接调用为能保证获取的方针为同一实例
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
}
Cglib关于@Transactional注解采用的署理类阻拦器DynamicAdvisedInterceptor
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target = null;
try {
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
//Spring缓存了被署理类的实例
//获取被署理类实例
target = getTarget();
if (target != null) {
targetClass = target.getClass();
}
//获取方针办法的阻拦器链,被@Transactional润饰的办法会有缓存办法和调用链关系
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
//没有被@Transactional润饰的办法会直接调用被署理类自身来履行
//此处和Cglib通用的处理不一样,Spring缓存和被署理实例,用被署理类实例来履行办法
//所以未被注解润饰的办法调用注解润饰的办法不能触发阻拦器
retVal = methodProxy.invoke(target, argsToUse);
}
else {
//@Transactional润饰的办法会经过署理类方针来履行,进入阻拦器履行增强逻辑
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null) {
releaseTarget(target);
}
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
}
JDK的处理逻辑同样是调用被署理类来履行未加@Transactional注解的办法,就不多写了。
小结
Cglib动态署理与JDK动态署理的差异本质上应该关于署理方针的调用办法有不同,Cglib是直接将署理类方针作为方针方针运用,增强逻辑直接写入署理类的子类办法中,调用办法时只需一个署理类方针即可,而JDK则是将被署理类方针引证寄存在署理类方针中,增强逻辑在署理方针中寄存而实际履行办法还需求调用被署理方针。当然Cglib经过缓存被署理类的实例方针也能够做到JDK的作用。
两种署理办法一个经过承继获取类办法信息,一种经过接口获取类办法信息,在了解其原理后,如何选型运用仍是看事务场景和两种办法的履行功率来决定。