最近在项目中出现了一个这种情况:我一顿操作猛如虎的写了好几个设计模式,然后在设计模式中的类中想将数据插入数据库,因此调用Mapper持久层,但是数据怎么都写不进去,在我一顿操作猛如虎的查找下,发现在普通类中用@Autowired
注入的Bean是Null,也就是说注入失败了,瞎搞。
针对以上情况,我做了三种解决方案,经测试均可行,解决方案如下:
- 在设计模式中只操作数据,最后还是将数据返回给Controller层,再由Controller向下调用写入数据库
- 简化设计模式,然后将其注册为Service,然后再Service中调用Mapper层
- 通过Bean工具的方式,在普通类中获取Bean,然后将内容写入数据库。
经过测试,三种情况均可行,最终我选择了"3"。
文章目录
- 情况复现
- 正文 | 获取一个Bean
- 方式 1 | 通过实现`ApplicationContextAwre`方式获取Bean
- 1.1 实现
- 1.2 使用
- 方式 2
- 2.2 使用
- 方式 3 | 继承`ApplicationObjectSupport`的方式获取Bean
- 3.1 实现
- 3.2 测试
- 方式 4 | 继承`WebApplicationObjectSupport`
- 4.2 测试
- 方式 5 | 通过`WebApplicationContextUtils`
- 5.1 实现
- 5.2 测试
- 致谢
- 以下代码均经过我的测试,请放心使用 -
情况复现
抄作业可以跳转至正文
情况复现比较简单,我们只需要一个Bean即可,Bean代码如下:
- Bean代码
@Component
public class TestComponent {
public String say() {
System.out.println("执行成功");
return "执行成功";
}
}
在以上代码中,我们将TestComponent
注册成为了一个Bean,为了严禁,我们还需要在Controller中调用一下这个类的方法测试一下该类是否真的被注入进去了,但是为了文章不要太冗余,这一块内容我省略掉,结论是:我测试过,是可以在Controller中调用的。
- 通过普通类调用该Bean
实现思想:我们写一个普通的类,在Controller中new出该类的对象,然后在该普通类中@Autowired
的方式注入该类并调用
验证方式:首先,我们会输出该Bean的地址,如果注入成功的话,我们会得到一长串字符,其次,如果成功的话页面与控制台均有输出
-
Controller层
只写个方法了,类信息略
@GetMapping("/com")
public String common() {
CommonClazz clazz = new CommonClazz();
return clazz.say();
}
- 普通类
CommonClazz
public class CommonClazz {
@Autowired
private TestComponent component;
public String say() {
System.out.println("-----Bean:"+component);
return component.say();
}
}
以上代码中,我们在Controller层new出来了CommonClazz的对象,在CommonClazz中我们Autowired了测试Bean,随后返回+输出,一气呵成,逻辑严谨,看似毫无问题,一执行满是BUG,执行结果如下:
页面:错误信息,找不到该页面(其实是有该路径,只不过后台报错了)
后台:报错,内容如下
可以看到,首先我们对Bean的地址输出是null
,说明我们注入失败,什么都没拿到,因此用null
来执行方法会报错也就可以理解了。
思考一下,如果我这时候new一个Service层的实现类对象,然后调用Mapper,是否可以将数据写入数据库呢?答案是否定的,因为对于new出来的Service实现类来说,它也是一个普通类,而不是Bean,但是Mapper层却是一个Bean,这样又会出现现在的问题。
正文 | 获取一个Bean
方式 1 | 通过实现ApplicationContextAwre
方式获取Bean
!!强烈推荐!!
这是一种比较推荐的写法,我们不用再改写SpringBoot启动类,且获取Bean的时候也不用传入Bean的名称,只需要传入一个
.class
就可以了。
1.1 实现
SpringContextUtil
代码如下:
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext ac;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ac = applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
T bean = ac.getBean(clazz);
return bean;
}
}
正如简介中所说,这种方法不需要修改启动类,我们只需要做这些就可以正常使用了。
1.2 使用
与方式一相比Controller
与CommonClass
类并没有发生太大的变化,但是为了更好的阅读性,我们还是全都展示出来。
- Controller类(依旧省略类信息,只写方法)
@GetMapping("/com")
public void common() {
CommonClazz clazz = new CommonClazz();
clazz.say();
}
CommonClazz
类
public class CommonClazz {
public void say() {
TestComponent bean = SpringContextUtil.getBean(TestComponent.class);
System.out.println("-----Bean:"+bean);
bean.say();
}
}
- Bean类省略,可以去前面复制
浏览器访问Controller层地址,信息正常输出,内容如下
可以看到Bean的地址被正确输出(说明不是null),也输出了Bean中方法的内容,说明Bean被正常注入了。
方式 2
!推荐!
这种实现可以在任意类中获取一个Bean,但是要修改SpringBoot启动类,并且在获取Bean的时候要传入两个参数,有点冗余,个人觉得使用起来不如方式1(当然也可以通过改写
getBean()
方法的方式只传入一个Bean)。
### 2.1 实现
- 首先我们要简单改造一下SpringBoot启动类,变动如下:
@SpringBootApplication
public class JimTestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(JimTestApplication.class, args);
SpringContextUtil.setAc(run);
}
}
- 其次,我们创建一个
SpringContextUtil
工具类,内容如下:
public class SpringContextUtil {
private static ApplicationContext ac;
public static <T> T getBean(String beanName, Class<T> clazz) {
T bean = ac.getBean(beanName, clazz);
return bean;
}
public static void setAc(ApplicationContext applicationContext){
ac = applicationContext;
}
}
**大功告成!**接下来只需要在普通类中调用该工具类中的方法,就可以获得一个Bean了,接下来我们测试一下、
测试思路:与上面一样,我们通过Controller new普通类对象,然后在普通类对象中通过这个工具类获取一个Bean,并且输出Bean的地址以及调用Bean的方法
为了方便测试,所有的方法都不加返回值了,直接输出 ,后面也是如此。
2.2 使用
- Controller类
new CommonClazz()
(依旧省略类信息,直接写方法)
@GetMapping("/com")
public void common() {
CommonClazz clazz = new CommonClazz();
clazz.say();
}
CommonClazz
类信息如下
public class CommonClazz {
public void say() {
// 使用如下
TestComponent bean = SpringContextUtil.getBean("testComponent",TestComponent.class);
System.out.println("-----Bean:"+bean);
bean.say();
}
}
Bean(TestComponent)
没有做多大的改动,只是有返回值改成void了,为了节省篇幅,这里也省略不写了。
我们在浏览器调用Controller的地址之后,控制台得到如下输出:
结论:Bean被正确注入
方式 3 | 继承ApplicationObjectSupport
的方式获取Bean
!!不推荐!!
这是一种比较鸡肋的方法,使用起来有很大的局限性:它只能在Bean中使用。因为它本身也需要作为Bean被注入后才能生效。
3.1 实现
SpringContextUtil
类内容如下
@Service
public class SpringContextUtil extends ApplicationObjectSupport {
public <T> T getBean(Class<T> clazz) {
T bean = getApplicationContext().getBean(clazz);
return bean;
}
}
3.2 测试
老样子,为了方便阅读,我决定省略测试内容,直接说测试结果。
- 在普通类中使用,失败,获取的测试Bean是一个
null
- 在Controller中直接将该类作为Bean
@Autowired
进去(因为@Service
注解就想到了),获取Bean成功 - 我也尝试了一些其他的办法在普通类中使用,均失败了,有好办法的话告诉我吧。
方式 4 | 继承WebApplicationObjectSupport
!!不推荐!!
与**[方式3]**一样,它也只能在Bean中使用,因此也很不推荐。
### 4.1 实现
@Service
public class SpringContextUtil extends WebApplicationObjectSupport {
public <T> T getBean(Class<T> clazz) {
T bean = getApplicationContext().getBean(clazz);
return bean;
}
}
4.2 测试
同[方式3],略。
方式 5 | 通过WebApplicationContextUtils
!!不推荐!!
适用于web项目的b/s结构。只适合获取web项目中。
补充:该bean 定义对应于单个
websocket
的生命周期。该作用域仅适用于WebApplicationContext
环境。
5.1 实现
SpringContextUtil
代码
public class SpringContextUtil {
public static <T> T getBean(ServletContext request, String name, Class<T> clazz){
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request);
// 或者
WebApplicationContext webApplicationContext1 = WebApplicationContextUtils.getWebApplicationContext(request);
// webApplicationContext1.getBean(name, clazz)
T bean = webApplicationContext.getBean(name, clazz);
return bean;
}
}
5.2 测试
- 首先第一个参数不能传递
null
,否则会报错 - 暂时没有这种场景,后面我就没测(偷懒
致谢
感谢 华为云 | springboot获取bean的几种常用方式 对本博客的帮助