您的位置:首页 > 新闻 > 会展 > 手机报价网最新价格_网页界面设计教材_如何进行网站制作_2023第二波疫情已经到来了

手机报价网最新价格_网页界面设计教材_如何进行网站制作_2023第二波疫情已经到来了

2025/2/25 4:42:09 来源:https://blog.csdn.net/u011709538/article/details/145615237  浏览:    关键词:手机报价网最新价格_网页界面设计教材_如何进行网站制作_2023第二波疫情已经到来了
手机报价网最新价格_网页界面设计教材_如何进行网站制作_2023第二波疫情已经到来了

由because it is a JDK dynamic proxy that implements温习Spring的代理

  • 项目场景
  • 原因分析
    • 1、报错位置
    • 2、错误原因
    • 3、业务需求
  • 解决方案
    • 1、注入CGlib代理
    • 2、取出原生对象

项目场景

昨日在启动一个SpringBoot项目时,发现启动失败,并在日志中出现了这样的报错:

The bean ‘XXXXManageImpl‘ could not be injected as a ‘XXXXManageImpl‘ because it is a JDK dynamic proxy that implements : XXXXManage

有些Spring版本则是 The bean ‘XXXXX’ could not be injected because it is a JDK dynamic proxy,如下:

在这里插入图片描述

我们先来看一下产生报错的相关代码

// 类的定义
@Service
public class XXXXManageImpl extends AbstractManage implements XXXXManage {}
// 其他地方要注入 XXXXManageImpl
@Autowired
XXXXManageImpl XXXXManageImpl;

原因分析

1、报错位置

形如

***************************
APPLICATION FAILED TO START
***************************

这种报错实际上是 SpringBoot 分析后的报错,而根据输出的内容看,其原始报错类型其实为 BeanNotOfRequiredTypeException , 经由BeanNotOfRequiredTypeFailureAnalyzer 分析后输出的指导性错误。那么顾名思义,这应该是在Bean装配过程中,出现了指定类型与Bean实际类型不符的场景。

2、错误原因

其实从报错中也不难看出来,说的就是我们希望注入一个类型为”XXXXManageImpl“的Bean,系统确认了这个Bean是存在的,但是现在这个Bean的实例的类型却并不是 ”XXXXManageImpl“,而是一个JDK代理类(com.sum.Proxy),两者虽然都实现了XXXXManager,但只属于兄弟类,此时自然无法注入成功
在这里插入图片描述

如果你还不太清楚动态代理,可以在过去的文章中复习一下《Spring核心特性—— AOP(面向切面编程)》。 一个类如果带有@Component或者@Service等注解,就会被实例化后放进Spring容器。至于最终放进去的可能是这个类的实例,也可能是这个类的代理类的实例
在这里插入图片描述

而至于这个类什么情况下会生成动态代理?其实原因有很多,比如这里我们使用的这个 ”XXXXManageImp“ ,在他的方法中存在着@Transactionol注解,正因为这个注解,导致这个类在创建Bean,并放入Spring容器时,其实放进去的是这个类的代理,而且是JDK代理 (可参见 《Spring事务畅谈 —— 由浅入深彻底弄懂 @Transactional注解》)

而JDK代理的实例类型为 com.sum.Proxy jdk.proxy3.$ProxyXXXX 等(不同jdk版本可能有所不同),但肯定不是我们的业务类 XXXXManageImpl, 所以这次注入是肯定会失败的。

3、业务需求

一般来说,如果我们对一个接口 XXXXManage 有一个实现类 XXXXManageImpl,那么我们在其他地方注入这个Bean 时,最好使用注入的是接口,即如下:

@Autowired
XXXXManage XXXXManage;

但是原代码为什么注入的是实现类呢? 是因为这个实现类本身除了实现了XXXXManage接口外,还继承了一个名叫 AbstractManage 的抽象类,也就是说这个类不单纯是某个接口的实现,他还肩负着一些额外的功能与方法

// 类的定义
@Service
public class XXXXManageImpl extends AbstractManage implements XXXXManage {}

而业务需要调用的就是这个类的其他方法(而非来自接口XXXXManage的方法),所以不能使用注入XXXXManage 的方式。


解决方案

结合我们的业务,我们的核心问题是:我们想要注入一个类,并使用该类自身的方法。但是这个类实际在Spring容器中以JDK代理形式存在,所以如果注入其代理 -> 则无法用到该类自身方法; 注入原生类 -> 启动报错。

所以问题的解决思路可以有以下几种:

1、注入CGlib代理

这也是Spring推荐的一种策略:其原文如下:

Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxiesby setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.
考虑将该 Bean 注入为其接口之一,或者通过在 @EnableAsync 和/或 @EnableCaching 上设置 proxyTargetClass=true 来强制使用基于 CGLib 的代理。

但是我们这里的动态代理,实际是由 @Trasactionol引入的,而@Trasactionol 并没有proxyTargetClass 属性。我们当然可以使用全局的 proxyTargetClass 来解决,比如在配置文件中配置,或者在启动类上的@EnableTransactionManagement注解里加这个属性

@EnableTransactionManagement(proxyTargetClass = true)

而且SpringBoot2就已经将CGlib动态代理设为默认配置,但是由于项目本身是个老项目,积重难返,这个项目一旦全体采用 CGLib 的代理 会导致很多问题。(详见《【问题处理】—— SpringBoot2 动态代理问题排查》)

所以应该说:如果没有历史负债的项目,可以使用这种方式,从而解决该问题。

2、取出原生对象

既然不能使用CGlib,可以采用原生对象解决该问题,即我们虽然是注入代理对象,但在要用到类自己的方法时,可先取出代理的原生对象,然后调用原生对象的方法

// 注入时还是注入JDK代理
@Autowired
XXXXManage XXXXManage;// 需要获取原对象时
XXXXManageImpl obj = (XXXXManageImpl)AopProxyUtils.getSingletonTarget(XXXXManage);

通过AopProxyUtils的方法,就可以通过代理对象,获取其原始对象,进而开始使用了。需要注意的是我们取得并使用原对象,就意味着失去了代理的那些功能。所以这种操作还是需要谨慎一些的

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com