文章目录

  • 一、AOP切面编程:Spring AOP 与 AspectJ
  • 二、Spring事务管理
  • 1、spring中开启事务的配置方式
  • 1.1、注解开启:@EnableTransactionManagement
  • 1.2、创建 bean 开启
  • 1.3、使用 xml 开启
  • 2、开启事务控制:@EnableTransactionManagement
  • 1.1、属性mode作用:声明事务控制方式(spring aop或aspectj)
  • 1.2、属性proxyTargetClass作用:是否使用cglib作为默认代理
  • 3、spring中事务创建的方式
  • 3.1、声明式事务:@Transactional
  • 3.2、编程式事务:TransactionTemplate
  • 4、springboot中事务的动态代理对象的默认类型:CGLIB
  • 4.1、原因
  • 4.2、[代理类型的修改方式]()
  • 三、Spring AOP切面原理:动态代理
  • 1、两种代理的定义
  • 1.1、JDK dynamic proxy
  • 1.2、cglib
  • 1.3、javassist
  • 2、两种代理的实现
  • 1.1、java dynamic proxy
  • 1.2、cglib

一、AOP切面编程:Spring AOP 与 AspectJ

扩展spring中的切面编程Spring AOP VS. AspectJ

二、Spring事务管理

1、spring中开启事务的配置方式

1.1、注解开启:@EnableTransactionManagement

1.2、创建 bean 开启

参照@EnableTransactionManagement的文档解释:使用@configuration,@enabletransactionmanager创建事务管理配置java文件,创建bean。

1.3、使用 xml 开启

在xml文件中使用tx:*配置.

<tx:annotation-driven/>, <aop:aspectj-autoproxy/>, 或 <aop:config/> 中的任意一个元素指定了proxy-target-class=“true”,就会强制使用cglib生成动态代理。

2、开启事务控制:@EnableTransactionManagement

springboot 默认开启事务(无需显示声明@EnableTransactionManagement)。 开启事务控制后,需要对待进行事务控制的方法加上 @Transactional 注解才会对开启事务,可以注解到类\方法上,待进行事务控制的方法必须是public的访问级别。

protect\default 也不行,原因如下: Due to the proxy-based nature of Spring’s AOP framework, protected methods are by definition not intercepted, neither for JDK proxies (where this isn’t applicable) nor for CGLIB proxies (where this is technically possible but not recommendable for AOP purposes). As a consequence, any given pointcut will e matched against public methods only! 虽然cglib 允许访问代理对象的非public方法,但spring不允许对动态代理访问非public方法。 若需要对非public级别的方法甚至构造函数进行拦截,应该使用AspectJ 进行编织切入。 摘自 spring中的切面编程:6.8.2. Other Spring aspects for AspectJ

1.1、属性mode作用:声明事务控制方式(spring aop或aspectj)

The mode attribute controls how advice is applied: 1.1、If the mode is AdviceMode.PROXY (the default), then the other attributes control the behavior of the proxying. Please note that proxy mode allows for interception of calls through the proxy only; local calls within the same class cannot get intercepted that way.(当mode属性为默认的“AdviceMode.Proxy”,利用动态代理在运行时对目标编织进切面代码。则其他属性可以控制代理的行为。此时处于代理模式,只有代理调用的方法可以被拦截,在同一类中的本地调用无法被拦截) 此时:因为事务是有代理对象开启的,所以对实例的内部调用方法不会开启事务(self-invocation, any method calls that it may make on itself, is not going to result in the advice associated with a method invocation getting a chance to execute),即使方法声明了@Transactional注解。只有被代理对象直接调用的方法,才能开启事务控制。即被controller直接调用的service方法才可以进行事务控制。 若想需对非代理对象调用的方法进行事务控制(如需要对方法A,由service方法调用的方法A,进行事务控制),可以新建代理,利用该代理调用需要进行事务控制的方法。

1.2、Note that if the mode is set to AdviceMode.ASPECTJ, then the value of the proxyTargetClass attribute will be ignored. Note also that in this case the spring-aspects module JAR must be present on the classpath, with compile-time weaving or load-time weaving applying the aspect to the affected classes. There is no proxy involved in such a scenario; local calls will be intercepted as well.(若模式设置为AdviceMode.Aspectj,会在编译时或加载时向切面影响目标(方法/类)位置编织进切面代码;与代理无关,所以自调用也可以被编入切面。此时proxyTargetClass属性将会被忽略,并且spring-aspects模块的jar文件需要配置在classpath… …。无论调用是否涉及代理、无论调用方法是否声明了@Transactional注解,调用都会被拦截,包括本地调用。)

1.2、属性proxyTargetClass作用:是否使用cglib作为默认代理

配置项:spring.aop.proxy-target-class 值不为空时,会忽略本属性值

Indicate whether subclass-based (CGLIB) proxies are to be created (true) as opposed to standard Java interface-based proxies (false). The default is false. Applicable only if mode() is set to AdviceMode.PROXY. Note that setting this attribute to true will affect all Spring-managed beans requiring proxying, not just those marked with @Transactional. 为true时表明会对所有spring管理的bean创建基于CGLIB代理(基于java类的代理),而不会创建默认的JDK代理(基于java接口的代理)。但默认值为false. 注意只有当mode=AdviceMode.Proxy时该属性才有作用。

3、spring中事务创建的方式

3.1、声明式事务:@Transactional

请见另外两篇博客查看相关内容@Transactional特性spring.aop.proxy-target-class 对生成事务代理类型 的影响

//controller -> service.hasTransaction :controller直接调用可以对方法hasTransaction开启事务
@Transactional
public String hasTransaction(String name) {
    return dao.select(name);
}

//controller -> service.sayHi -> service.hasTransaction :controller直接调用sayHi,可以对方法sayHi开启事务。
//方法hasTransaction被处于同一个service中的方法sayHi调用,由当前的service的实例化对象调用,必须使用如下代码获取当前代理对象,通过代理对象调用才可以开启对hasTransaction方法的事务
@Transactional
public String sayHi(String name) {
    //利用Spring上下文获取当前代理对象
    DemoServiceClass service = (DemoServiceClass)AopContext.currentProxy();
    return service.hasTransaction(name);
}

3.2、编程式事务:TransactionTemplate

当方法比较耗时,应该使用编程式事务。 因为事务的开启,会建立数据库连接。若耗时太长,则会导致连接一直无法释放,项目的并发量较大时容易导致连接耗尽。而数据库的连接资源是有限的,可能会导致其他用户无法、获取建立数据库连接。

TransactionTemplate tt  = new TransactionTemplate();
tt.execute(new TransactionCallback<Object>() {
    @Override
    public Object doInTransaction(TransactionStatus status) {
        dao.delete(1L);
        return null;
    }
});

4、springboot中事务的动态代理对象的默认类型:CGLIB

4.1、原因

避免混合模式时,若被代理类没有实现接口,会出现恶心的现象。

springboot项目开发人员Phil Webb的解释: 基于接口的代理有时会导致难以跟踪root cause的报错ClassCastExceptions。 具体来说,有可能@Bean的bean对象将被JDK代理替换,然后无法以bean的原始类形式注入(因为jdk基于接口实现,不能强转为实现类类型)。

相关讨论: https://github.com/spring-projects/spring-framework/issues/21075 https://github.com/spring-projects/spring-framework/issues/19047

4.2、代理类型的修改方式

1、使用 application.properties 文件的配置项spring.aop.proxy-target-class 2、@EnableAspectJAutoProxy, @EnableAsync, @EnableCaching,和@EnableTransactionManagement四者的proxyTargetClass

事务代理默认使用CGLIB

spring事务默认优先级_spring事务默认优先级

三、Spring AOP切面原理:动态代理

1、两种代理的定义

1.1、JDK dynamic proxy

a JDK dynamic proxy cannot be casted to the original target class because it’s simply a dynamic proxy that happens to implement the same interface(s) as the target

只能对实现了接口的类进行代理。生成接口的实例,无法强转为实现类的类型。

JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的,生成被代理接口的实例。但是,JDK中要进行动态代理的类必须要直接或间接地至少实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。

注意,动态代理应该首选JDK动态代理(JDK dynamic proxies are preferred why?? )。因为cglib 不属于jdk工具,属于外部依赖包,所有外部依赖都应该作为不可靠依赖。

1.2、cglib

If the target class implements no interfaces, Spring will use CGLIB to create a new class on the fly that is a subclass (“extends”) the target class

可以对非接口实现类进行代理,但无法对类的final\static等修饰的方法进行代理。

使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为static或final的方法进行代理(因为无法被override),因为CGLib原理是动态生成被代理类的子类

1.3、javassist

https://www.baeldung.com/javassist

2、两种代理的实现

1.1、java dynamic proxy

//自定义代理实现类:原生jdk
class MyHandler implements InvocationHandler{
    private Object target;

    private MyHandler(Object target) {
        this.target = target;
    }

    public static void main(String[] args){
        GreetingsService instanceOfInterface = new GreetingsServiceImpl();
        MyHandler handler = new MyHandler(instanceOfInterface);
        GreetingsService proxy = (GreetingsService) Proxy.newProxyInstance(GreetingsService.class.getClassLoader(),new Class[]{GreetingsService.class},handler);
        System.out.println(proxy.sayHi("ayowei"));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理要做的事:增强、事务、记录日志、权限控制

        //最终还是要交给目标对象处理
        method.invoke(target,args);
        return null;
    }
}

1.2、cglib

//自定义代理实现类:Cglib代理
class CglibTest implements MethodInterceptor {

   public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setInterfaces(new Class[]{One.class});
        enhancer.setSuperclass(Two.class);
        enhancer.setCallback(new Cglib());
        One obj = (OneImp) enhancer.create();
        obj.print();
        obj.println();
    }

    @Override
    public Object intercept(Object object, Method method, Object[] arg2, MethodProxy methodProxy) throws Throwable {

        System.out.println("调用真实对象前");
        Object result = methodProxy.invokeSuper(object, arg2);
        System.out.println("调用真实对象后");

        return result;
    }
}
interface One {
    void print();

    void println();
}

class OneImp implements One {

    @Override
    public void print() {
        System.out.println(this);
    }

    @Override
    public void println() {
        System.out.println("\n换行");
    }
}

class Two extends OneImp {
    @Override
    public void print() {
        System.out.println("this is two");
    }
}