Spring官网阅读(二)(依赖注入及方法注入)
上篇文章我们学习了官网中的
1.2
,1.3
两小节,主要是涉及了容器,以及Spring实例化对象的一些知识。这篇文章我们继续学习Spring官网,主要是针对1.4
小节,主要涉及到Spring的依赖注入。虽然只有一节,但是涉及的东西确不少。话不多说,开始正文。
文章目录
依赖注入:
根据官网介绍,依赖注入主要分为两种方式
构造函数注入
Setter方法注入
官网:
我们分别对以上两种方式进行测试,官网上用的是XML的方式,我这边就采用注解的方式了:
测试代码如下,我们通过在Service中注入LuBanService这个过程来
public class Main02 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new
// config类主要完成对类的扫描
AnnotationConfigApplicationContext(Config.class);
Service service = (Service) ac.getBean("service");
service.test();
}
}
@Component
public class LuBanService {
LuBanService(){
System.out.println("luBan create ");
}
}
测试setter方法注入
@Component
public class Service {
private LuBanService luBanService;
public Service() {
System.out.println("service create");
}
public void test(){
System.out.println(luBanService);
}
// 通过autowired指定使用set方法完成注入
@Autowired
public void setLuBanService(LuBanService luBanService) {
System.out.println("注入luBanService by setter");
this.luBanService = luBanService;
}
}
输出如下:
luBan create
service create
注入luBanService by setter // 验证了确实是通过setter注入的
com.dmz.official.service.LuBanService@5a01ccaa
测试构造函数注入
@Component
public class Service {
private LuBanService luBanService;
public Service() {
System.out.println("service create by no args constructor");
}
// 通过Autowired指定使用这个构造函数,否则默认会使用无参
@Autowired
public Service(LuBanService luBanService) {
System.out.println("注入luBanService by constructor with arg");
this.luBanService = luBanService;
System.out.println("service create by constructor with arg");
}
public void test(){
System.out.println(luBanService);
}
}
输出如下:
luBan create
注入luBanService by constructor // 验证了确实是通过constructor注入的
service create by constructor
com.dmz.official.service.LuBanService@1b40d5f0
疑问:
在上面的验证中,大家可能会有以下几个疑问:
@Autowired
直接加到字段上跟加到set方法上有什么区别?为什么我们验证的时候需要将其添加到setter方法上?- 首先我们明确一点,直接添加
@Autowired
注解到字段上,不需要提供setter方法也能完成注入。以上面的例子来说,Spring会通过反射获取到Service中luBanService这个字段,然后通过反射包的方法,Filed.set(Service,luBanService)这种方式来完成注入 - 我们将
@Autowired
添加到setter方法时,我们可以通过断点看一下方法的调用栈,如下:
- 首先我们明确一点,直接添加
对于这种方式来说,最终是通过Method.invoke(object,args)的方式来完成注入的,这里的method对象就是我们的setter方法
@Autowired
为什么加到构造函数上可以指定使用这个构造函数?- 我们先可以测试下,如果我们不加这个注解会怎么样呢?我把前文中的
@Autowired
注解注释,然后运行发现
luBan create
service create by no args constructor // 可以看到执行的是空参构造
null
先不急得出结论,我们再进行一次测试,就是两个函数上都添加
@Autowired
注解呢?Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service': Invalid autowire-marked constructor: public com.dmz.official.service.Service(com.dmz.official.service.LuBanService). Found constructor with 'required' Autowired annotation already: public com.dmz.official.service.Service()
发现直接报错了,报错的大概意思是已经找到了一个被
@Autowired
注解标记的构造函数,同时这个注解中的required属性为true。后来我测试了将其中一个注解中的required属性改为false,发现还是报同样的错,最终将两个注解中的属性都改为false测试才通过,并且测试结果跟上面的一样,都是执行的无参构造。要说清楚这一点,涉及到两个知识
- Spring中的注入模型,下篇文章专门讲这个
- Spring对构造函数的推断。这个到源码阶段我打算专门写一篇文章,现在我们暂且记得:
在***默认的注入模型***下,Spring如果同时找到了两个***符合要求的构造函数***,那么Spring会采用默认的无参构造进行实例化,如果这个时候没有无参构造,那么此时会报错
java.lang.NoSuchMethodException
。什么叫符合要求的构造函数呢?就是构造函数中的参数Spring能找到,参数被Spring所管理。这里需要着重记得:一,默认注入模型;二,符合要求的构造函数
- 我们先可以测试下,如果我们不加这个注解会怎么样呢?我把前文中的
如果我们同时采用构造注入加属性注入会怎么样呢?
在没有进行测试前,我们可以大胆猜测下,Spring虽然能在构造函数里完成属性注入,但是这属于实例化对象阶段做的事情,那么在后面真正进行属性注入的时候,肯定会将其覆盖掉。现在我们来验证我们的结论
@Component
public class Service {
private LuBanService luBanService;
public Service(LuBanService luBanService) {
System.out.println("注入luBanService by constructor with arg");
this.luBanService = luBanService;
System.out.println("service create by constructor with arg");
}
public void test(){
System.out.println(luBanService);
}
@Autowired
public void setLuBanService(LuBanService luBanService) {
System.out.println("注入luBanService by setter");
this.luBanService = null;
}
}
运行结果:
注入luBanService by constructor with arg // 实例化时进行了一次注入
service create by constructor with arg // 完成了实例化
注入luBanService by setter // 属性注入时将实例化时注入的属性进行了覆盖
null
区别:
根据上图中官网所说,我们可以得出如下结论:
- 构造函数注入跟setter方法注入可以混用
- 对于一些强制的依赖,我们最好使用构造函数注入,对于一些可选依赖我们可以采用setter方法注入
- Spring团队推荐使用构造函数的方式完成注入。但是对于一些参数过长的构造函数,Spring是不推荐的
方法注入:
我们不完全按照官网顺序进行学习,先看这一小节,对应官网上的位置如下图:
为什么需要方法注入:
首先我们思考一个问题,在有了依赖注入的情况下,为什么还需要方法注入这种方式呢?换而言之,方法注入解决了什么问题?
我们来看下面这种场景:
@Component
public class MyService {
@Autowired
private LuBanService luBanService;
public void test(int a){
luBanService.addAndPrint(a);
}
}
@Component
// 原型对象
@Scope("prototype")
public class LuBanService {
int i;
LuBanService() {
System.out.println("luBan create ");
}
// 每次将当前对象的属性i+a然后打印
public void addAndPrint(int a) {
i+=a;
System.out.println(i);
}
}
public class Main02 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
MyService service = (MyService) ac.getBean("myService");
service.test(1);
service.test(2);
service.test(3);
}
}
在上面的代码中,我们有两个Bean,MyService为单例的Bean,LuBanService为原型的Bean。我们的本意可能是希望每次都能获取到不同的LuBanService,预期的结果应该打印出:
1,2,3
实际输出:
1
3
6
这个结果说明我们每次调用到的LuBanService是同一个对象。当然,这也很好理解,因为在依赖注入阶段我们就完成了LuBanService的注入,之后我们在调用测试方法时,不会再去进行注入,所以我们一直使用的是同一个对象。
我们可以这么说,原型对象在这种情况下,失去了原型的意义,因为每次都使用的是同一个对象。那么如何解决这个问题呢?只要我每次在使用这个Bean的时候都去重新获取就可以了,那么这个时候我们可以通过方法注入来解决。
通过注入上下文(applicationContext对象)
又分为以下两种方式:
- 实现
org.springframework.context.ApplicationContextAware
接口
@Component
public class MyService implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void test(int a) {
LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));
luBanService.addAndPrint(a);
}
@Override
public void setApplicationContext(@Nullable ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
- 直接注入上下文
@Component
public class MyService{
@Autowired
private ApplicationContext applicationContext;
public void test(int a) {
LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));
luBanService.addAndPrint(a);
}
}
通过@LookUp的方式(也分为注解跟XML两种方式,这里只演示注解的)
@Component
public class MyService{
public void test(int a) {
LuBanService luBanService = lookUp();
luBanService.addAndPrint(a);
}
//
@Lookup
public LuBanService lookUp(){
return null;
}
}
方法注入 之 replace-method
方法注入还有一种方式,即通过replace-method
这种形式,没有找到对应的注解,所以这里我们也就用XML的方式测试一下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myService" class="com.dmz.official.service.MyService">
<replaced-method replacer="replacer" name="test"/>
</bean>
<bean id="replacer" class="com.dmz.official.service.MyReplacer"/>
</beans>
public class MyReplacer implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("替代"+obj+"中的方法,方法名称:"+method.getName());
System.out.println("执行新方法中的逻辑");
return null;
}
}
public class MyService{
public void test(int a) {
System.out.println(a);
}
}
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application.xml");
MyService myService = ((MyService) cc.getBean("myService"));
myService.test(1);
}
}
执行结果:
替代com.dmz.official.service.MyService$$EnhancerBySpringCGLIB$$61c14242@63e31ee中的方法,方法名称:test
执行新方法中的逻辑
这里需要注意一点:
我在测试replace-method这种方法注入的方式时,受动态代理的影响,一直想将执行我们被替代的方法。用代码体现如下:
public class MyReplacer implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
// System.out.println("替代"+obj+"中的方法,方法名称:"+method.getName());
// System.out.println("执行新方法中的逻辑");
method.invoke(obj,args);
return null;
}
}
但是,这段代码是无法执行的,会报栈内存溢出。因为obj是我们的代理对象,method.invoke(obj,args)
执行时会进入方法调用的死循环。最终我也没有找到一种合适的方式来执行被替代的方法。目前看来这可能也是Spring的设计,所以我们使用replace-method的场景应该是想完全替代某种方法的执行逻辑,而不是像AOP那样更多的用于在方法的执行前后等时机完成某些逻辑。
依赖注入跟方法注入的总结:
- 我们首先要明确一点,什么是依赖(Dependencies)?来看官网中的一段话:
可以说,一个对象的依赖就是它自身的属性,Spring中的依赖注入就是属性注入。
- 我们知道一个对象由两部分组成:属性+行为(方法),可以说Spring通过属性注入+方法注入的方式掌控的整个bean。
- 属性注入跟方法注入都是Spring提供给我们用来处理Bean之间协作关系的手段
- 属性注入有两种方式:构造函数,Setter方法。
- 方法注入(LookUp Method跟Replace Method)需要依赖动态代理完成
- 方法注入对属性注入进行了一定程度上的补充,因为属性注入的情况下,原型对象可能会失去原型的意义,见:为什么需要方法注入
画图如下:
Spring官网阅读(二)(依赖注入及方法注入)的更多相关文章
- Spring官网阅读 | 总结篇
接近用了4个多月的时间,完成了整个<Spring官网阅读>系列的文章,本文主要对本系列所有的文章做一个总结,同时也将所有的目录汇总成一篇文章方便各位读者来阅读. 下面这张图是我整个的写作大 ...
- Spring官网阅读(十六)Spring中的数据绑定
文章目录 DataBinder UML类图 使用示例 源码分析 bind方法 doBind方法 applyPropertyValues方法 获取一个属性访问器 通过属性访问器直接set属性值 1.se ...
- Spring官网阅读(十七)Spring中的数据校验
文章目录 Java中的数据校验 Bean Validation(JSR 380) 使用示例 Spring对Bean Validation的支持 Spring中的Validator 接口定义 UML类图 ...
- Spring官网阅读(十八)Spring中的AOP
文章目录 什么是AOP AOP中的核心概念 切面 连接点 通知 切点 引入 目标对象 代理对象 织入 Spring中如何使用AOP 1.开启AOP 2.申明切面 3.申明切点 切点表达式 excecu ...
- Spring官网阅读(三)自动注入
上篇文章我们已经学习了1.4小结中关于依赖注入跟方法注入的内容.这篇文章我们继续学习这结中的其他内容,顺便解决下我们上篇文章留下来的一个问题-----注入模型. 文章目录 前言: 自动注入: 自动注入 ...
- Spring官网阅读(一)容器及实例化
从今天开始,我们一起过一遍Spring的官网,一边读,一边结合在路神课堂上学习的知识,讲一讲自己的理解.不管是之前关于动态代理的文章,还是读Spring的官网,都是为了之后对Spring的源码做更全面 ...
- Spring官网阅读(七)容器的扩展点(二)FactoryBean
在上篇文章中我们已经对容器的第一个扩展点(BeanFactoryPostProcessor)做了一系列的介绍.其中主要介绍了Spring容器中BeanFactoryPostProcessor的执行流程 ...
- Spring官网阅读(九)Spring中Bean的生命周期(上)
文章目录 生命周期回调 1.Bean初始化回调 2.Bean销毁回调 3.配置默认的初始化及销毁方法 4.执行顺序 5.容器启动或停止回调 Lifecycle 接口 LifecycleProcesso ...
- Spring官网阅读(四)BeanDefinition(上)
前面几篇文章已经学习了官网中的1.2,1.3,1.4三小结,主要是容器,Bean的实例化及Bean之间的依赖关系等.这篇文章,我们继续官网的学习,主要是BeanDefinition的相关知识,这是Sp ...
随机推荐
- CVE-2020-1938:Apache-Tomcat-Ajp漏洞-复现
0x00 漏洞简介 Apache与Tomcat都是Apache开源组织开发的用于处理HTTP服务的项目,两者都是免费的,都可以做为独立的Web服务器运行. Apache Tomcat服务器存在文件包含 ...
- 【转】Centos7启动网卡(获取ip地址)
这里之所以是查看下IP ,是我们后面要建一个Centos远程工具Xshell 连接Centos的时候,需要IP地址,所以我们这里先 学会查看虚拟机里的Centos7的IP地址 首先我们登录操作系统 用 ...
- 不使用 if-elif 语句,如何优雅地判断某个数字所属的等级?
偶然看到了 stackoverflow 上的一个问题,还挺有启发,故分享一下. 题目大意是:有从 A 到 F 的 5 个等级,现要判断某个数值(从 0 到 1 之间)所属的等级.举例,如数值 > ...
- TCP的分分合合(面试必问)
TCP连接与断开 目录 TCP连接与断开 前言 握手 挥手 最后 前言 相信面试过的小伙伴对这个话题应该不陌生,算是面试必问了,三次握手,四次挥手,以及其中的一些衍生问题. TCP/IP(Transm ...
- Problem C Careful Ascent
数学问题. 在不经过shield时,竖直速度是1.所以时间就是y/1=y,,,,在经过shield时,时间为shield的数值长度*影响因素,然后总时间把他们加起来,最后再用水平方向的长度除以总时间, ...
- 【特征检测】BRISK特征提取算法
[特征检测]BRISK特征提取算法原创hujingshuang 发布于2015-07-24 22:59:21 阅读数 17840 收藏展开简介 BRISK算法是2011年ICCV上< ...
- Cucumber(1) —— 环境配置
目录 学习资料 cucumber简介 cucumber环境配置 学习资料 1.cucumber官方学习网站 cucumber简介 1.cucumber是一种支持BBD(behavior-driven ...
- Numpy学习-(2)
我学习numpy过程的记录 1. 切片和索引 (1) 两种切片方式示例: (2) 多维数组: import numpy as np a = np.array([[1,2,3],[3,4,5],[4,5 ...
- [linux][nginx] 通过nginx扩展nginx-rtmp-module简单做了一个流媒体直播
做的过程出现很多问题,环境其实就需要nginx就可以,然后就是在播放的问题,m3u8的格式,mac直接访问就支持,苹果系统原生H5支持m3u8,还有就是手机直接访问也支持!但是其他其他系统PC端不支持 ...
- 【学习笔记】 $learn \ from \ failure \ ? ( 雾$
\(1.\)变量名不要用 \(next\) ,在某些编译器里可能是关键词,可以用 \(nxt\) 代替 \(\\\) \(2.\)在判断某些条件时应该写成 flag = 条件 ? 1 : flag; ...