全注解下的Spring IoC
1.什么是Bean?
在Spring中把每一个需要管理的对象称为Spring Bean(简称Bean)
2.什么是IoC容器?
Spring IoC容器是一个管理Bean的容器,它要求所有的IoC容器都需要实现接口BeanFactory。
下面是BeanFactory接口源码:
package org.springframework.beans.factory;
public interface BeanFactory {
/**
* 用来引用一个实例,或把它和工厂产生的Bean区分开,就是说,如果一个FactoryBean的名字为a,那么,&a会得到那个Factory
*/
String FACTORY_BEAN_PREFIX = "&";
/*
* 四个不同形式的getBean方法,获取实例
*/
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
boolean containsBean(String name); // 是否存在
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 是否为单实例
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 是否为原型(多实例)
boolean isTypeMatch(String name, Class<?> targetType)
throws NoSuchBeanDefinitionException;// 名称、类型是否匹配
Class<?> getType(String name) throws NoSuchBeanDefinitionException; // 获取类型
String[] getAliases(String name);// 根据实例的名字获取实例的别名
}
默认情况下,Bean都是以单例形式存在,即使用getBean方法返回的都是同一对象。
3.如何从IoC容器中取出Bean
通过上面的源码可以看到,getBean方法有按类型(type)获取Bean的,也有按名称(name)获取Bean的,这对于后面介绍的Spring依赖注入很重要。
4.ApplicationContext和BeanFactory的关系
由于BeanFactory的功能不够强大,Spring在BeanFactory的基础上,设计了一个ApplicationContext接口,是BeanFactory的子接口,他们的关系如下(图片来源于网络):

在Spring Boot中,主要通过注解方式装配Bean到Spring IoC容器中(也支持XML).
5.装配Bean注入IoC容器(通过两个实例理解)
(1)@Bean
首先定义一个Java简单对象,文件User.java,代码如下:
package com.springboot.chapter3.pojo;
import java.util.LongSummaryStatistics;
/**
* Created by lizeyang on 2019/5/7.
*/
public class User {
private Long id;
private String userName;
private String note;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
然后再定义一个Java配置文件AppConfig.java,代码如下:
package com.springboot.chapter3.config;
import com.springboot.chapter3.pojo.User;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by lizeyang on 2019/5/7.
*/
@Configuration
public class AppConfig {
@Bean(name="user")
public User initUser(){
User user = new User();
user.setId(1L);
user.setUserName("user_name_1");
user.setNote("note_1");
return user;
}
}
其中@Configuration代表这是一个Java配置文件,Spring容器会根据它来生成IoC容器装配Bean。@Bean代表将initUser方法返回的POJO返回到IoC容器中,属性name定义这个Bean的名称,若不配置,方法名即为Bean名称。
最后,使用AnnotationConfigApplicationContext构建IoC容器,代码如下:
package com.springboot.chapter3.config;
import com.springboot.chapter3.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* Created by lizeyang on 2019/5/7.
*/
public class IoCTest {
public static void main(String[] args){
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User user = ctx.getBean(User.class);
System.out.println(user.getId());
}
}
运行IoCTest结果如下:
显然,配置在配置文件中名称为user的Bean已经装配到IoC容器中,并且可以通过getBean方法获取对应的Bean,并将Bean的属性输出出来。
(2)@ComponentScan
除了@Bean,还可以使用@Component和@ComponentScan,@Component是标明哪个类被扫描进入Spring IoC容器,@ComponentScan是标明采用何种策略扫描装配Bean。
首先,在包com.springboot.chapter3.config内新建UserCpoy.java文件,代码如下:
package com.springboot.chapter3.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* Created by lizeyang on 2019/5/7.
*/
@Component("UserCopy")
public class UserCopy {
@Value("1")
private Long id;
@Value("user_name_1")
private String userName;
@Value("note_1")
private String note;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
修改配置文件为:AppConfigCopy,代码为:
package com.springboot.chapter3.config;
import com.springboot.chapter3.pojo.User;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* Created by lizeyang on 2019/5/7.
*/
@Configuration
@ComponentScan
public class AppConfigCopy {
// public User initUser(){
// User user = new User();
// user.setId(1L);
// user.setUserName("user_name_1");
// user.setNote("note_1");
// return user;
// }
}
运行IoCTest,结果与上面相同。
6.什么是依赖注入?
依赖注入就是Bean之间的依赖,最常用的是@Autoeired注解,它会根据属性的类型(type)找到对应的Bean进行注入(还记得BeanFactory里面getBean方法的两种方式吗?)
另外。可以通过@Primary修改多个不同类型的Bean的优先权,或者将@Autowired和@Qualifier结合使用,通过类型和名称一起找到Bean。
7.Spring IoC初始化和销毁Bean过程——Bean生命周期
分为Bean定义、Bean初始化、Bean生存期和Bean的销毁4个部分。
下图为Spring Bean的初始化流程:
下面是Spring Bean的生命周期:
8.属性文件使用——application.properties
在Spring Boot中,我们一般先在Maven配置文件中加载依赖,Spring Boot将创建读取属性文件的上下文。如:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
有了依赖,就可以直接使用application.properties文件,通过其机制读取到上下文中,之后便可引用它。如:
spring.datasource.url=jdbc:mysql://localhost:3306/toutiao?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=nowcoder
mybatis.config-location=classpath:mybatis-config.xml
#logging.level.root=DEBUG
引用有两种方法,一种是Spring表达式,如通过@Value注解,使用${...}占位符读取配置在属性文件中的内容,如:
@Component
public class DataBaseProperties{
@Value("${database.drivename}")
private String driveName = null;
}
另一种方法就是使用@ConfigurationProperties注解,如:
@Component
@ConfigurationProperties("database")
public class DataBaseProperties{
......
}
9.单例(isSingleton)和原型(isPrototype)的区别
我们通过一段代码来进行比较两者区别:
@Component
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ScopeBean{
...
}
//测试作用域
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
ScopeBean scopeBean1 = ctx.getBean(ScopeBean.class);
ScopeBean scopeBean2 = ctx.getBean(ScopeBean.class);
System.out.println(scopeBean1 == scopeBean2);
在第二行代码注释之后,测试结果为true,说明scopeBean1和scopeBean2变量都指向同一个实例,即为单例。
若将注释消去,则设置Bean的作用域为prototype,让IoC容器每次获取Bean时,都新建一个Bean的实例返回给调用者。
10.XML配置Bean
尽管Sping Boot建议使用注解和扫描配置Bean,但也不拒绝XML配置Bean.
通过一个例子来了解XML配置Bean:
先新建一个松鼠实现类,代码如下:
package com.springboot.other.pojo;
import com.springboot.chapter3.pojo.definition.Animal;
public class Squirrel implements Animal {
@Override
public void use() {
System.out.println("松鼠可以采集松果");
}
}
这个文件所在的包不在@ComponentScan定义的扫描包com.springboot.chapter3.*之内,也没有标注@Component,所以不会被扫描机制所装配,我们使用XML文件来装配,代码如下:
<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="squirrel" class="com.springboot.other.pojo.Squirrel"/>
</beans>
最后,装配XML定义的Bean,代码如下:
@Configuration
@ComponentScan(basePackages = "com.springboot.chapter3.*")
@ImportResource(value = {"classpath:spring-other.xml"})
public class AppConfig {
......
}
这样就可以引入对应的XML,进而将XML定义的Bean装配到IoC容器中。
11.使用Spring EL(了解即可)
Spring EL是一种表达式语言,通过Spring EL可以拥有更强大的运算规则来装配Bean,最常用的是读取属性文件的值,如:
@Value("#{3.14}")
private float pi;
@Value("#{1+2}")
private int run;
总结
Spring Boot关于IoC的部分基本就是这些,本节代码已上传github:
https://github.com/lizeyang18/SpringBoot-2.x/tree/master/Chapter3