代理与Spring事务

代理与Spring事务

    • Java代理方式
      • JDK静态代理
        • 实现思路
        • 特点
        • 代码示例
      • JDK动态代理
        • 实现思路
        • 特点
        • 代码示例
      • Cglib动态代理
        • 实现思路
        • 特点
        • 代码示例
    • Spring事务
      • 代理的使用
      • 事务与AOP
        • 原理概述
        • 业务场景总结

Java代理方式

JDK静态代理

实现思路

  • 被代理类和代理类实现同一个接口
  • 被代理人持有代理类的对象
  • 代理类为被代理类预处理消息、过滤消息并在此之后将消息转发给被代理类,之后还能进行消息的后置处理

特点

  • 在编译后、运行前就生成了字节码文件。
  • 很容易就完成了对一个类的代理操作,缺点也很明显:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。

代码示例

public interface HelloInterface{
	void sayHello();
}

public class Hello implement HelloInterface{
	@Override
	public void sayHello(){
		System.out.println("hello");
	}
}

public class HelloProxy implement HelloInterface{
	
	private Hello;
	public HelloProxy(Hello hello){
		this.hello = hello;
	}
	
	@Override
	public void sayHello(){
		System.out.println("before");
		hello.sayHello();
		System.out.println("after");
	}
}

public class Test{
	public static void main(String... args){
		Hello hello = new Hello();
		HelloProxy proxy = new HelloProxy(hello);
		proxy.sayHello();
	}
}

JDK动态代理

实现思路

  • 创建与持有被代理对象的调用处理器 InvocationHandler
invoke(Object proxy, Method method, Object[] args)

proxy:生成的代理对象。
method:接口中的方法。
args:方法参数。	

  • 生成代理对象。
Proxy.newInstance(ClassLoader loader,Class[] interfaces,InvocationHandler handler)

loader:定义代理类的类加载器,必须能加载接口字节码文件,一般使用被代理对象的类加载器。例如接口为自定义接口,使用根加载器加载,会报"接口加载器不可见"异常。
interfaces:代理类实现的接口,可以为空数组,不能为null,实现被代理类的接口比较有意义。
handler:代理对象关联的调用处理程序,对代理对象实例方法的调用都是通过InvocationHandler中的invoke方法来完成的,而invoke方法会根据传入的代理对象、方法名称以及参数决定调用代理的哪个方法。	

特点

  • 运行时通过反射生成代理对象。
  • 不必为特定对象与方法编写特定的代理对象,可以使得一个处理者(Handler)服务于各个对象。
  • JDK的动态代理机制只能代理实现了接口的类,虽然在生成代理对象的方法参数Class[] interfaces中传入空数组也可以,但是产生的代理对象实际上是一个Object对象的代理,没有什么实际意义。

代码示例

public interface HelloInterface{
	void sayHello();
}

public class Hello implement HelloInterface{
	@Override
	public void sayHello(){
		System.out.println("hello");
	}
}

public class HelloHandler implement InvocationHandler{
	
   private Object target;
   public HelloHandler(Object target) {
		this.target = target;
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("before");
		Object obj = method.invoke(target, args);
		System.out.println("after");
		return obj;
	}
}

public class Test{
	public static void main(String... args){
		Hello hello = new Hello();
		HelloHandler h = new HelloHandler(hello);
		HelloInterface proxy = (HelloInterface)Proxy.newInstance(Test.class.getClassLoader,Hello.class.getInterfaces(),h);
		proxy.sayHello();
	}
}

Cglib动态代理

实现思路

  • 实现MethodInterceptor接口,定义方法的拦截器。
intercept(Object obj, Method method, Object[] args, MethodProxy proxy)

obj:被代理对象
method:被拦截的方法
args:方法参数
proxy:代理方法

  • 利用Enhancer类生成代理类。
1、生成代理类Class的二进制字节码;
2、通过Class.forName加载二进制字节码,生成Class对象;
3、通过反射机制获取实例构造,并初始化代理类对象。

特点

  • 运行时通过反射生成代理对象,底层生成字节码和JDK动态代理稍有不同。
  • 可以直接代理类,原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

代码示例


public class Hello{
	
	public void sayHello(){
		System.out.println("hello");
	}
}

public class MyMethodInterceptor implement MethodInterceptor{

   @Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
	    System.out.println("before");
	    proxy.invokeSuper(obj, args);//执行被代理对象的原有方法            
	    System.out.println("after");
	    return null;
	}
}

public class Test{
	public static void main(String... args){
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperClass(Hello.class);//为代理类设置父类
		enhancer.setCallback(new MyMethodInterceptor());//设置回调方法,类似于JDK动态代理的InvocationHandler
		Hello proxy = (Hello)enhancer.create();
		proxy.sayHello();
	}

Spring事务

代理的使用

1、Spring会为容器中的对象生成一个jdk或者cglib的动态代理对象,并缓存到对象工厂中,当其他类需要注入某些类时,实际注入的是代理对象。
2、Spring中默认使用的是jdk动态代理,可以通过spring.aop.proxy-target-class属性设置代理方式,true为cglib代理,false为jdk代理。

事务与AOP

原理概述

1、Spring容器在初始化每个单例bean的时候,会遍历容器中的所有BeanPostProcessor实现类,并执行其postProcessAfterInitialization方法。这一过程中会遍历容器中所有的切面,并查找与当前实例化bean匹配的切面,如果切面存在就为Bean创建代理JDK或者Cglib对象,@Transactional注解属于事务属性切面。

参考Spring源码中AbstractAutoProxyCreator类的postProcessAfterInitialization和wrapIfNecessary方法,wrapIfNecessary是创建代理对象的核心方法。

2、执行service的事务方法时,执行的是代理对象经过增强的方法。首先获取方法的拦截器链,如果不存在就直接执行代码中的service方法。存在事务注解,则会得到TransactionInterceptor,执行其中的invoke方法。

参考Spring源码中JdkDynamicAopProxy.invoke()、CglibAopProxy.DynamicAdvisedInterceptor.intercept(),以及TransactionInterceptor.invoke()方法。

3、依次开启事务、执行目标方法、提交事务,发生异常事务回滚。

参考Spring源码中TransactionAspectSupport类的createTransactionIfNecessary,cleanupTransactionInfo,commitTransactionAfterReturning以及completeTransactionAfterThrowing方法。

业务场景总结

1、@Transactional注解只有在public方法上才有效。JDK代理对象是被代理类接口的实现类,接口类都是public;cglib代理对象继承自被代理对象,理论上public和protected修饰的方法都可以被代理,但是Spring中代理时遍历的是public方法。

2、@Transactional默认遇到非受查异常(RuntimeException和Error)回滚,发生受查异常时不回滚。可以设置rollBackFor和noRollbackFor属性定义事务回滚的异常类型。

3、同一个Service中方法相互调用,如A调用B,则是否存在事务取决于A是否存在事务注解。原因是,在使用代理对象调用A方法时,只会根据A的注解来确定是否开启事务,A中调用B调用的是原生的B方法。

可以以另外的方法实现情形下的事务:
ServiceImpl bean = applicationContext.getBean(this.getClass());
从容器中获取对象,bean是被cglib增强的对象。通过对象执行加了事务注解的方法,此时,A主方法的事务和B方法的事务是独立的。

4、不同的Service方法相互调用,如S1.A()调用S2.B(),S1和S2都是被代理的对象,会分别根据方法是否含有事务注解来开启事务。具体处于哪一个事务,取决于事务的传播行为,即Propagation属性,默认是PROPAGATION_REQUIRED。

5、在主线程中开子线程,各个线程之间事务独立。因为事务信息是利用ThreadLocal和当前线程绑定的,保证在同一个线程的不同方法中操作数据库使用同一个连接。新开线程之后,子线程没法共享主线程的事务信息。参考TransactionAspectSupport.bindToThread()方法。

你可能感兴趣的:(Spring)