Spring的自动装配与依赖注入
Spring的自动装配与依赖注入
装配 = 创建Bean + 注入Bean
- 创建Bean
- 自动发现
- 显式注册Bean
- 注入Bean
- 基于配置的注入
- 自动注入
Spring的装配分为显式装配和隐式装配,涉及到自动装配、隐式的Bean发现机制、依赖注入等术语,但是在我看来装配整个过程分为两步,创建和注入。首先要创建Bean,不管是通过Spring的扫描机制去自动发现我们声明好的Bean还是在配置文件中把Bean声明出来;其次就是把满足依赖关系的Bean注入到需要的地方去。
创建Bean
自动发现
虽然是自动发现,但也是需要配置的,也就是开启自动扫描,Spring容器会帮我们把标注了特定注解的Bean扫描出来并创建,然后放到容器里面,任人使用。
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.hex" />
</beans>
package com.hex;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.ContextConfiguration;
@ComponentScan //开启组件扫描
@ContextConfiguration //表示这是一个Java配置类,用来配置应用上下文(ApplicationContext)
public class JavaConfig {
}
使用这两种方式的效果是一样的,两种方式的核心在于component-scan
和ComponentScan
,字面上理解为“组件扫描”,意思是将标注了特定注解的类(也可以当作一个组件)扫描进容器。我们常见的注解有@Component、@Controller、@Service、@Dao、@Bean等,其中@Component是一个比较宽泛的概念,而@Controller、@Service、@Dao是在Spring MVC中常用的注解,@Bean注解我们稍后就有提到。
假设我们在一个类上标注了@Component注解,又开启了组件扫描,那么Spring会把这个类实例化产生的Bean放进容器。
显式注册Bean
在某些场景下,我们需要用到别人封装好的包里面的类,当我们不能修改源码的时候,也就不能给类上增加一个注解然后再通过扫描的方式来创建Bean,此时就可以自己显式来注册Bean。下面的例子是创建一个com.hex.LoginController类的Bean。
<?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 -->
<bean id="myLoginController" class="com.hex.LoginController" />
</beans>
package com.hex;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.ContextConfiguration;
@ComponentScan //开启组件扫描
@ContextConfiguration //表示这是一个Java配置类,用来配置应用上下文(ApplicationContext)
public class JavaConfig {
@Bean
public LoginController myLoginController(){
return new LoginController();
}
}
@Bean注解表示把这个方法返回的对象注册到Spring容器中去。这里我们注册的还是一些简单的Bean,还没涉及到注入的问题。
以上两种方式,自动发现和显式注册Bean都能帮助我们创建Bean,鼓励使用自动扫描的机制进行Bean发现,代码简洁。
注入Bean
前面还没涉及到注入,都是创建一些简单的Bean。那如果是LoginController中依赖LoginService呢,像这样:
package com.hex;
import org.springframework.beans.factory.annotation.Autowired;
public class LoginController {
@Autowired
LoginService loginService;
public void login(){
loginService.login();
}
}
当然这里你已经看到我使用了@Autowired注解,接着往下看。
自动注入
所谓自动注入,就是用@Autowired注解标记一个对象,那么Spring会去容器中寻找合适的Bean注入进来(就像是赋初值),至于Spring是按照什么条件去找到对的Bean,那是另外一个话题(DI 依赖注入,后面再说)。这个时候就可以使用这个对象了。
@Autowired注解可以标注在变量上,也可以标注在构造方法上、标注在set方法上,甚至是标注在任何一个需要对象的方法上。总之,你需要什么对象都可以让Spring去容器中找。找不到Spring会报错,这种情况可以通过设置@Autowired注解的required属性来解决;找到多个Spring不知道注入哪一个进来也会报错,这种情况可以通过其他一切注解来帮助Spring缩小匹配范围。
基于配置的注入
如果不想使用@Autowired注解来实现这一功能,那么配置(XML配置文件或Java配置类)可以帮你达到同样的效果。
package com.hex;
public class LoginService {
public void login() {
System.out.println("login ...");
}
}
package com.hex;
public class LoginController {
LoginService loginService;
public LoginController(LoginService loginService) {
this.loginService = loginService;
}
public void login() {
loginService.login();
}
}
<?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="myLoginService" class="com.hex.LoginService"/>
<bean id="myLoginController" class="com.hex.LoginController">
<constructor-arg index="0" ref="myLoginService"/>
</bean>
</beans>
package com.hex;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.ContextConfiguration;
@ComponentScan
@ContextConfiguration
public class JavaConfig {
@Bean
public LoginService myLoginService(){
return new LoginService();
}
@Bean
public LoginController myLoginController(LoginService loginService){
return new LoginController(loginService);
}
}
这里的LoginService和LoginController和我们平时写的类没有什么区别,没有给它加上一些注解,都还是保持一个“干净”的状态。下面的配置(XML配置文件或Java配置类)中展示的就是如何进行依赖注入。
关于依赖注入的几种方式,后面会讨论,这里统一使用构造方法来进行依赖注入。
在XML配置文件中,先声明了一个叫“myLoginService”的Bean,然后在“myLoginController”这个Bean中通过constructor-arg标签注入,翻译过来就是构造器参数,里面index表示注入到第几个参数里面,ref表示依赖哪一个Bean,这里很明显依赖的是LoginService的Bean对象。
在Java配置类中,先注册一个LoginService的Bean叫myLoginService(注意:如果没有使用@Bean标签的name属性手动设置Bean的名字,那么将以方法名作为Bean的名字),然后把这个Bean通过构造方法注入进去,这里就比在XML中明显多了,是直接调用的构造方法。
以上这两种注入方式都有一定的局限性,因为是基于构造方法注入,那没有构造方法就歇了。所以Spring肯定不可能只给我们提供了构造方法注入这一种方式,后面会讨论。
验证装配结果
package com.hex;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertNotNull;
@RunWith(SpringJUnit4ClassRunner.class)
//导入应用上下文的方式在Java和XML中二选一
//@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@ContextConfiguration(classes = {JavaConfig.class})
public class LoginTest {
@Autowired
LoginController loginController;
@Test
public void testLogin() {
assertNotNull(loginController);
loginController.login();
}
}
这里我们编写了一个测试类。
- 与普通的junit测试类不同的是,Spring的测试类要使用@RunWith(SpringJUnit4ClassRunner.class)来表示这个一个Spring的测试类;
- 这里还使用了一个和JavaConfig.java中一样的注解@ContextConfiguration,只不过这里加上了要导入上下文的路径或指定的类名。加上@ContextConfiguration表示这是一个配置类,再加上上下文配置的路径或类名表示要导入上下文配置(Spring的测试类中不导入Spring的上下文,肯定不能正常执行啦)。
- 这里还使用到了一个org.junit.Assert.assertNotNull静态方法。在Assert这个类中定义了很多用来做断言的静态方法,assertNotNull就可以用来判断loginController对象是否为空,如果容器中没有这个Bean,那注入肯定失败,程序走到断言这里肯定会报错。还有很多有用的断言,比如assertTrue()、assertFalse()、assertEquals()、assertNotEquals()等。
依赖注入
- 基于配置的注入
- 基于Java配置类的注入
- 基于XML配置文件的注入
- 构造器注入
- 标签
- c-命名空间
- set方法注入
- 标签
- p-命名空间
- 装配集合
- 构造器注入
- 混合注入
- 自动注入
- @Autowired
- 歧义性
基于Java配置类的注入
前面已经演示过了。
基于XML配置文件的注入
构造器注入,就是通过类的构造方法把属性注入进去,所以构造方法是必须的。
package com.hex;
public class LoginController {
LoginService loginService;
String username;
Integer age;
public LoginController(LoginService loginService, String username, Integer age) {
this.loginService = loginService;
this.username = username;
this.age = age;
}
}
<?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.hex.LoginService"/>
<bean id="myController" class="com.hex.LoginController">
<constructor-arg index="0" ref="myService"/>
<constructor-arg name="username" value="Hex"/>
<constructor-arg type="java.lang.Integer" value="23"/>
</bean>
</beans>
基于构造器注入的时候可以通过index指定注入第几个构造方法参数,通过name指定注入参数的参数名,通过type指定注入参数的类型。而用value来表示实际注入的值,ref用来表示注入引用的对象。
Spring3.0引入了c-命名空间的注入方式,这个c也就是constructor的意思,所以使用c-命名空间必然也是通过构造器注入。使用c-命名空间要引入特定的xml约束xmlns:c="http://www.springframework.org/schema/c"
。
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
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.hex.LoginService"/>
<bean id="myController" class="com.hex.LoginController" c:_0-ref="myService" c:username="Hex" c:_2="23"/>
</beans>
c:_0-ref 表示索引为0的参数依赖的对象
c:username 表示参数username注入的值
c:_2 表示所以为2的参数注入的值
set方法注入,依赖的是类中关于属性的setXXX方法,所以没有set方法肯定是不行的。
package com.hex;
public class LoginController {
LoginService loginService;
String username;
Integer age;
public void setLoginService(LoginService loginService) {
this.loginService = loginService;
}
public void setUsername(String username) {
this.username = username;
}
public void setAge(Integer age) {
this.age = age;
}
}
<?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.hex.LoginService"/>
<bean id="myController" class="com.hex.LoginController">
<property name="loginService" ref="myService"/>
<property name="username" value="Hex"/>
<property name="age" value="23"/>
</bean>
</beans>
同样的,有c-命名空间来简化构造器注入,就有p-命名空间来简化set方法注入。当然,此处引入的就不是c的xml约束,而是p的xml约束。
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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.hex.LoginService"/>
<bean id="myController" class="com.hex.LoginController" p:loginService-ref="myService" p:username="Hex" p:age="23"/>
</beans>
p:loginService-ref 表示注入属性loginService的引用对象
p:username 表示注入属性username的值
p:age 表示注入属性age的值
看到这里,你会发现,其实这两种方式的注入形式都差不多,代码都差不多。只本质也无非就是构造方法和set方法而已。
接下来看看怎么装配一个集合。
set方法不能实现集合的装配
package com.hex;
import java.util.List;
public class LoginController {
List<LoginService> loginServiceList;
public LoginController(List<LoginService> loginServiceList) {
this.loginServiceList = loginServiceList;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
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.hex.LoginService"/>
<bean id="myController1" class="com.hex.LoginController">
<constructor-arg index="0">
<list>
<ref bean="myService"/>
<ref bean="myService"/>
<ref bean="myService"/>
</list>
</constructor-arg>
</bean>
<util:list id="list">
<ref bean="myService"/>
<ref bean="myService"/>
<ref bean="myService"/>
</util:list>
<bean id="myController2" class="com.hex.LoginController" c:_0-ref="list"/>
</beans>
要么在标签内嵌一个标签,要么使用util:list标签,再用ref的形式注入进来。
list、set、array、map、props的注入都大同小异。
混合注入
基于Java配置类和基于XML配置文件,这两种方式都各有好处。实际运用中可能会将两种方式配合使用。
- Java配置类中引入Java配置类,@Import
- Java配置类中引入XML配置文件,@ImportResource
- XML配置文件中引入Java配置类,注册即可
- XML配置文件中引入XML配置文件,
@Autowired
前面已经提到过了@Autowired注解,自动注入的好处就在于不用我们去理会那些复杂的依赖关系,Spring可以自动去管理Bean。
歧义性
使用@Autowired仍然不是万能的,毕竟代码是死的。在很多情况下,Spring可能从容器找到多个能匹配上的Bean,那到底注入哪一个就成了很头疼的问题。Spring为了解决Bean注入的歧义性,提供了一些解决方案。
解决方法:
- @Primary注解
- @Qualifier(value = "bean_name")
- @Qualifier()
案例:有一个Food接口,有三个实现类分别是Rice、Noodle、IceCream。使用@ComponentScan扫描到Spring的容器中。
package com.hex;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {JavaConfig.class})
public class FoodTest {
@Autowired
Food food; //这里注入一定是会报错的
}
方法一:在Bean上增加@Primary注解,表示在注入Bean冲突时首选这个Bean。
@Primary
@Component
public class Rice implements Food {
}
方法二:在@Autowired时加上@Qualifier注解,并给出Bean的名字,表示进一步缩小Bean的范围,只注入指定名字的Bean。
@Autowired
@Qualifier(value = "rice")
Food food;
方法三:在Bean上增加@Qualifier注解,表示特殊标注一下这个Bean,而在@Autowired时也用@Qualifier注解声明使用刚才标注的Bean,这样的好处在于可以自定义特征的名字,比如说这里冰淇淋是冷的,就定义一个cold。
@Component
@Qualifier("cold")
public class IceCream implements Food {
}
@Autowired
@Qualifier("cold")
Food food;
方法四:有时候可能一个Bean有很多种属性,我们要通过很多种属性才能把它筛选出来的时候,而又不能定义很多个@Qulifier来同时使用在一个地方(Java注解中不允许一个条目上出现相同类的注解,比如说一个类上不能有两个@Qualifier注解)。这个时候可以通过自定义注解的形式达成复合注解来实现。
这里定义多个注解,我只展示其中一个注解怎么写。
package com.hex;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}
然后在要标注的Bean上按规则加上我们刚创建的注解,比如说冰淇淋是冰的,是很多奶油的。
@Component
@Cold
@Creamy
public class IceCream implements Food { }
这里我就不贴大段大段的代码了,假设我们给米饭和面条都加上了一些注解。然后在测试类中只需要使用刚才定义的注解就行了。注解就是定义了一堆约束条件,而注入的时候通过约束条件来筛选就行了。比如下面这段代码,筛选出来的就是加了@Cold和@Creamy两个自定义注解的Bean,那就只有IceCream了。那如果这个时候筛选出来还有多个Bean的话,仍然会报错,你只能继续细化约束条件的粒度。
@Autowired
@Cold
@Creamy
Food food;
总结
在《Spring in action》这本书中,是分自动装配、基于XML的装配和基于Java代码的装配三个部分来讲的。我认为拆分成注册Bean和注入Bean两个步骤会对整个过程有更清晰的理解。至于怎么去注册Bean,使用XMl还是Java来注入Bean,选什么方式进行依赖注入,看个人习惯和喜好罢了,喜欢启动扫描就用扫描,习惯用XML配置就写XML。
另外,Spring中Bean还有一些其他的特性。比如说条件化Bean的创建、Bean的作用域等,没有在本文讨论。
Spring的自动装配与依赖注入的更多相关文章
- Spring基于的注解自动装配和依赖注入(***)
#自动装配的小Demo: package com.gyf.annotation; //DAO层 public interface UserDao { public void save(); } pac ...
- Spring自动装配之依赖注入(DI)
依赖注入发生的时间 当Spring IOC 容器完成了Bean 定义资源的定位.载入和解析注册以后,IOC 容器中已经管理类Bean定义的相关数据,但是此时IOC 容器还没有对所管理的Bean 进行依 ...
- Spring Boot 自动装配(二)
目录 目录 前言 1.起源 2.Spring Boot 自动装配实现 2.1.@EnableAutoConfiguration 实现 2.1.1. 获取默认包扫描路径 2.1.2.获取自动装配的组件 ...
- spring框架--IOC容器,依赖注入
思考: 1. 对象创建创建能否写死? 2. 对象创建细节 对象数量 action 多个 [维护成员变量] service 一个 [不需要维护公共变量] dao 一个 [不需要维护 ...
- Spring IOC(三)依赖注入
本系列目录: Spring IOC(一)概览 Spring IOC(二)容器初始化 Spring IOC(三)依赖注入 Spring IOC(四)总结 目录 1.AbstractBeanFactory ...
- Spring框架使用(控制反转,依赖注入,面向切面AOP)
参见:http://blog.csdn.net/fei641327936/article/details/52015121 Mybatis: 实现IOC的轻量级的一个Bean的容器 Inversion ...
- Spring的自动装配Bean
spring的自动装配功能的定义:无须在Spring配置文件中描述javaBean之间的依赖关系(如配置<property>.<constructor-arg>).IOC容器会 ...
- spring完成自动装配
让spring完成自动装配 Autowiring 解决标签为javaBean注入时难以维护而实现的 下面是几种autowire type的说明: 1,byname:试图在容器中寻找和需要自动装配的属性 ...
- spring的自动装配,骚话@Autowired的底层工作原理
前言 开心一刻 十年前,我:我交女票了,比我大两岁.妈:不行!赶紧分! 八年前,我:我交女票了,比我小两岁,外地的.妈:你就不能让我省点心? 五年前,我:我交女票了,市长的女儿.妈:别人还能看上你?分 ...
随机推荐
- 团队作业4-Day3
团队作业4-Day3 项目git地址 1. 站立式会议 2. 项目燃尽图 3. 适当的项目截图 今日暂无较大代码更新 4. 代码/文档签入记录(部分) 5. 每人每日总结 吴梓华:今天未进行开发,学习 ...
- cookie的理解
第一:每个特定的域名下最多生成20个cookie IE6或更低版本最多20个cookie IE7和之后的版本最多可以有50个cookie Firefox最多50个cookie chrome和Safar ...
- 【题解】「SP34013」SEUG - Seetha’s Unique Game
这道题一看就是 贪心 . 使放的石头少,就需要石头大. 那么就可以将石头重量排序,从大到小. 这道题里面看似东西很多,但是很多东西都是没有用的.比如说:箱子的长和宽,因为题目中说「每加一个石头,水的高 ...
- TMOOC-1709-小明复仇
题目描述 小明所在的世界上一共有n个城市,城市间有m条双向道路.小明现在在城市1,他想到位于城市n的小韩隆家询问他为什么没有将自己的五三复原完成.由于小韩隆手下有许多小弟,小明担心自己可能再也回不来, ...
- 我对js数据类型的理解和深浅(copy)的应用场景
本人毕业一所专科院校,所学专业是计算机应用技术,在大学时对前端有了一定的了解之后,觉得自己对前端的兴趣十分强烈,开始自学前端,一路上也是坎坎坷坷,也有想要放弃的时候,还好坚持了下来,并且从事前端开发已 ...
- Springboot websocket学习Demo
使用的是springboot2.1.4版本 <parent> <groupId>org.springframework.boot</groupId> <art ...
- Android之window机制token验证
前言 很高兴遇见你~ 欢迎阅读我的文章 这篇文章讲解关于window token的问题,同时也是Context机制和Window机制这两篇文章的一个补充.如果你对Android的Window机制和Co ...
- docker 连接MySQL·集群
1 指定端口 docker run -p 3307:3306 --name root -e MYSQL_ROOT_PASSWORD=root -d mysql # 6380 root password ...
- jwt 简单基本使用加密解密
import jwt # 加密 encode_jwt=jwt.encode({'uid':'123'},'密钥123',algorithm='HS256') print(encode_jwt) # 解 ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...