spring cglib和jdk动态代理

事务 proxyTargetClass不生效的解决问题

一个项目中,避免不了使用事务,而在Springboot项目中,我们一般使用@Transactional注解来设置事务控制,@Transactional的详情使用可见博客https://www.cnblogs.com/pengpengdeyuan/p/12737891.html。

在加完@Transactional注解启动项目时,可能会出现以下报错

Description:

The bean 'testService' could not be injected as a 'com.xxx.xxx.service.impl.TestService' because it is a JDK dynamic proxy that implements:
    com.pk.kxl.service.ITestService


Action:

Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching

这类情况主要是jdk自动代理与CGlib代理的注入方式差异造成的。

一、查看是否开启事务支持

首先查看项目是否是SpringBoot项目,SpringBoot项目会自动开启事务支持。

二、查看注入方式

从错误信息中可以看出,它是在我的Controller层中,注入testService这个Bean时失败,(失败的原因就是我导入的是接口实现,而springboot的事务默认是使用jdk的动态代理,是基于接口进行注入的)。意思就是我注入的是service层的实现类,这种操作是非法的,改成注入service层接口就可以解决。

这时可以想到,既然jdk动态代理不行,那我们就改成CGlib动态代理(基于类,即设置proxyTargetClass=True在启动事务管理上@EnableTransactionManagement(proxyTargetClass=True)),此时需要引入相应的cglib库的jar包,在springboot中已经集成了。但在spring3.2之前是需要引入的。

@SpringBootApplication
//启用事务管理(可省略)
@EnableTransactionManagement(proxyTargetClass=True)

第三种方式就可以使用@Scop注解去解决

第三种方式就可以使用@Scop注解去解决,他会将所有方法上的事务都强制改为是CGLib方式代理的。

方法:在service层上添加一个注解

@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)

@EnableAspectJAutoProxy注解,在SpringBoot中设置proxyTargetClass不生效?

最近在SpringBoot中使用@EnableAspectJAutoProxy注解,好奇

proxyTargetClass的作用,看了API文档说是用来切换jdk代理和cglib代理的,可以实际上设置true或者false都是cglib的代理,难道是SpringBoo的bug?

前置准备

  • 一个有接口的实现,理论上默认使用jdk的动态代理
@Service
@Slf4j
public class LoginServiceImpl implements LoginService {
    @Override
    public void login() {
        log.info("Interface Login...");
    }
}

  • 一个没有接口的实现,用的是cglib代理
@Service
@Slf4j
public class PlainLoginService {
    public void login() {
        log.info("Class Login...");
    }
}

  • Aspect切面配置
@Aspect
@Slf4j
public class FooAspect {

    @Pointcut("execution(* io.spring.action.aop.service..*.*(..))")
    public void pointcut() {
    }

    @After(value = "pointcut()")
    public void after() {
        log.info(this.getClass().toString());
        log.info("after login");
    }
}

SpringBoot环境

@SpringBootApplication
@Slf4j
// 指示是否创建基于子类(CGLIB)的代理,而不是创建基于标准Java接口的代理。 默认值是{@code false}。
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringAopApplicationDemo implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(SpringAopApplicationDemo.class, args);
    }

    // 定义切面,或者直接使用@Component注解
    @Bean
    public FooAspect fooAspect() {
        return new FooAspect();
    }

    // 有接口实现
    @Autowired
    LoginService loginService;

    // 无接口实现
    @Autowired
    PlainLoginService plainLoginService;

    @Override
    public void run(ApplicationArguments args) throws Exception {

        log.info(loginService.getClass().toString()); // class io.spring.action.aop.service.impl.LoginServiceImpl$$EnhancerBySpringCGLIB$$d3171a1f
        loginService.login();

        log.info(plainLoginService.getClass().toString()); // class io.spring.action.aop.service.PlainLoginService$$EnhancerBySpringCGLIB$$5b51dab3
        plainLoginService.login();
    }
}

可以看到运行结果,proxyTargetClass设置为true,生成的依旧是cglib的代理类而不是jdk的代理

Spring环境

@Configuration
@ComponentScan
@EnableAspectJAutoProxy(proxyTargetClass = false)
@Slf4j
public class SpringApplicationDemo {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringApplicationDemo.class);

        LoginService loginService = context.getBean(LoginService.class); //jdk
        PlainLoginService plainLoginService = context.getBean(PlainLoginService.class); // cglib
        log.info("jdk class name = {}", loginService.getClass()); 
        log.info("cglib class name = {}", plainLoginService.getClass()); 
    }

    @Autowired
    private LoginService loginService;

    // 定义切面,或者直接使用@Component注解
    @Bean
    public FooAspect fooAspect() {
        return new FooAspect();
    }
}

proxyTargetClass = true

jdk class name = class io.spring.action.aop.service.impl.LoginServiceImpl$$EnhancerBySpringCGLIB$$8808e216
cglib class name = class io.spring.action.aop.service.PlainLoginService$$EnhancerBySpringCGLIB$$1043a2aa

proxyTargetClass = false

jdk class name = class com.sun.proxy.$Proxy21
cglib class name = class io.spring.action.aop.service.PlainLoginService$$EnhancerBySpringCGLIB$$3b8f4e09

  1. 可以看出,在Spring的环境下设置为true生成的都是cglib的代理,设置为false,默认有接口实现的是jdk的动态代理,没有接口的是cglib代理
  2. 说明在Spring环境下是有作用的,应该是SpringBoot做了什么操作,修改了这个行为。
  3. SpringBoot什么东西会修改默认行为呢?很容易想到自动装配

springboot 自动配置 ----切面AopAutoConfiguration

  1. 可以看到玄机在这个类里面,SpringBoot使用了Aop自动装配,将代理类默认设置成cglib的
  2. 另一方面,也看到能够修改这个行为的,是通过spring.aop.proxy-target-class 属性来控制,所以,如果期望使用jdk+ cglib这种方式,可以在yml配置文件中修改这个属性
  3. 这个规则是在SpringBoot2.0后改的,之前的版本和Spring的效果一致
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
		AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {

	}

}

为什么SpringBoot都要将代理默认成cglib的,它比jdk有哪些好处呢?

实际上,jdk的代理类只能赋值给接口,不能赋值给具体实现,如果有人注入实现类,那么会导致启动报错,默认cglib避免了这种问题的发生

@Configuration
@ComponentScan
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Slf4j
public class SpringApplicationDemo {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringApplicationDemo.class);

        LoginService loginService = context.getBean(LoginService.class); //jdk
        PlainLoginService plainLoginService = context.getBean(PlainLoginService.class); // cglib
        log.info("jdk class name = {}", loginService.getClass());
        log.info("cglib class name = {}", plainLoginService.getClass());

    }

    //Bean named 'loginServiceImpl' is expected to be of type 'io.spring.action.aop.service.impl.LoginServiceImpl' but was actually of type 'com.sun.proxy.$Proxy21'
    // **AOP代理对象如果使用jdk代理,只能赋值给接口,不能赋值给实现**
    // SpringBoot中出于这个考虑,将所有代理对象默认为cglib
    @Autowired
	private LoginServiceImpl loginService;

    // 定义切面,或者直接使用@Component注解
    @Bean
    public FooAspect fooAspect() {
        return new FooAspect();
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值