1. 基础概念
IOC:即控制反转,是一种解耦类与类之间关系的一种设计模式;例如当A类中需要B类的时候,并不是在A类中直接new一个B类,而是通过IOC容器统一创建A类和B类,并通过DI(依赖注入),将B类注入到A类中!
DI:即依赖注入,是IOC模式的一种实现方式;包括设值注入和构造注入
SpringBean:与JavaBean不同,所有被Spring IOC容器管理的实例都可以称作SpringBean,下文简称Bean
2. SpringBean的装配
SpringBean的装配有三种配置方式:xml文件配置方式、JavaConfig配置类配置方式、注解实现自动装配的配置方式
2.1 xml文件配置:
Bean类代码:
package springtest;
public class TestBean {
private String str;
private DIBean di;
public TestBean() {
System.out.println("TestBean init");
}
/*通过构造方法注入直接量*/
public TestBean(String s) {
this.str = s;
System.out.println("TestBean init s is " + s);
}
/*通过构造方法注入Bean*/
public TestBean(DIBean di) {
this.di = di;
System.out.println("TestBean init di ");
}
/*通过设值方法注入直接量*/
public void setStr(String s) {
this.str = s;
System.out.println("Set str s is " + s);
}
/*通过设值方法注入Bean*/
public void setDi(DIBean di) {
this.di = di;
System.out.println("Set di");
}
public void fun() {
System.out.println("TestBean fun");
}
}
class DIBean {
/*Bean的构造方法为private还是public属性,并不影响装配*/
private DIBean () {
System.out.println("DIBean init");
}
}
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"
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">
<beans>
<!-->不设置id的Bean配置<-->
<bean class="springtest.TestBean" />
<!-->设置id的Bean配置<-->
<bean id="TBean1" class="springtest.TestBean" />
<!-->通过构造器注入常量<-->
<bean id="TBean2" class="springtest.TestBean">
<constructor-arg value = "Hello"/>
</bean>
<!-->通过设值方法注入常量<-->
<bean id="TBean3" class="springtest.TestBean">
<property name="str" value = "Hello"/>
</bean>
<bean id="DIBean" class="springtest.DIBean" />
<!-->通过构造器注入Bean<-->
<bean id="TBean4" class="springtest.TestBean">
<constructor-arg ref = "DIBean"/>
</bean>
<!-->通过设值方法注入Bean<-->
<bean id="TBean5" class="springtest.TestBean">
<property name="di" ref = "DIBean"/>
</bean>
</beans>
</beans>
Bean获取代码:
package springtest;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
public static void main(String[] args) {
/*通过xml配置文件,生成Spring IOC容器*/
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
/*通过包名.类名#序号为id的格式,获取无显示id的Bean*/
TestBean t = (TestBean)context.getBean("springtest.TestBean#0");
t.fun();
/*通过id获取Bean*/
TestBean t1 = (TestBean)context.getBean("TBean1");
t1.fun();
}
}
Ps:
- xml中配置一个Bean的时候最好指定id,如果不指定id会被默认用包名.类名#序号的格式赋予一个默认的id
- 获取Bean的时候,如果该类型的Bean只有一个实例,可以通过Bean的类类型对象来获取;如果存在多个实例的时候,则必须使用id来获取!
- 可以使用util命名空间的标签来实现容器类型的注入(包括构造注入和设值注入)
2.2 JavaConfig配置类配置:
Bean类代码(与上述代码相似,此处只展示差异代码):
/*Java Bean示例代码与上面代码,仅仅修改了DIBean类的构造方法为public属性*/
class DIBean {
/*JavaConfig类方式配置Bean,构造方法不能是private属性*/
public DIBean () {
System.out.println("DIBean init");
}
JavaConfig配置:
package springtest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
//不设置id的Bean配置
@Bean
public TestBean TBean() {
return new TestBean();
}
//设置id的Bean配置
@Bean(name = "TBean1")
public TestBean TBean1() {
return new TestBean();
}
//通过构造器注入常量
@Bean(name = "TBean2")
public TestBean TBean2() {
return new TestBean("Hello");
}
//通过设值方法注入常量
@Bean(name = "TBean3")
public TestBean TBean3() {
TestBean t = new TestBean();
t.setStr("Hello");
return t;
}
@Bean(name = "DIBean")
public DIBean DIBeanInit() {
return new DIBean();
}
//通过构造器注入Bean
@Bean(name = "TBean4")
public TestBean TBean4() {
/*通过DIBeanInit()方法,获取id为"DIBean"的Bean实例,
该方法由于有@Bean注解的封装,并不是真正的执行方法调用,只是获取Bean实例而已*/
return new TestBean(DIBeanInit());
}
//通过设值方法注入Bean
@Bean(name = "TBean5")
public TestBean TBean5() {
TestBean t = new TestBean();
t.setDi(DIBeanInit());
return t;
}
}
Bean获取代码:
package springtest;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTest {
public static void main(String[] args) {
/*通过Java Config类,生成Spring IOC容器*/
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
/*通过Bean id获取Bean实例*/
TestBean t = (TestBean)context.getBean("TBean1");
t.fun();
}
}
Ps:
- JavaConfig类实现Bean装配的配置,可以自己实现具体Bean装配时的初始化逻辑,对应Bean的初始化是通过调用被@Bean注解修饰的方法实现的!
- JavaConfig类中被@Bean注解修饰的方法,装配的Bean始终是一个单例;该方法的调用并不会像普通调用方法一样,每次都重复执行方法体,而仅仅是通过该方法获取Bean实例而已!
2.3 通过注解实现自动装配:
Bean类代码:
package springtest;
import javax.inject.Named;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/*通过@Component注解,配置当前类为一个JavaBean*/
@Component("TBean1")
//@Named("TBean1")
public class TestBean {
//@Autowired
public DIBean di;
/*通过@Value注解设值注入直接量*/
@Value("Hello")
public String s;
/*默认构造方法*/
public TestBean (){
System.out.println("TestBean init");
}
/*通过@Value注解构造注入直接量*/
public TestBean(@Value("Hello") String s) {
System.out.println("Init TestBean S is" + s);
}
/*通过构造方法注入Bean*/
/*通过@Autowired注解,注入一个JavaBean*/
@Autowired
public TestBean(DIBean di) {
this.di = di;
System.out.println("TestBean init di ");
}
/*通过设值方法注入Bean*/
//@Autowired
public void setDi(DIBean di) {
this.di = di;
System.out.println("Set di");
}
public void fun() {
System.out.println("TestBean fun");
}
}
@Component
class DIBean {
public int a;
private DIBean () {
this.a = 100;
System.out.println("DIBean init");
}
}
JavaConfig类配置自动扫描:
package springtest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"springtest"})
public class Config {
}
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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
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="springtest"/>
</beans>
Bean获取代码:
package springtest;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTest {
public static void main(String[] args) {
/*通过Java Config类,生成Spring IOC容器*/
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
/*通过Bean id获取Bean实例*/
TestBean t = (TestBean)context.getBean("TBean1");
t.fun();
System.out.println(t.di.a);
}
}
Ps:
- 依赖注入分为两种:设值注入和构造注入,两种方式分别可以注入直接量和Bean引用;切记通过设值注入的时候,被注入的属性必须要有一个public属性的setter方法!
- Bean的自动装配,需要配置自动扫描包路径,可以使用xml配置,也可以使用JavaConfig类配置;Java Config配置类使用@ComponentScan注解的时候,可以传入一个包名数组给basePackages属性,也可以传入一个类名数组给basePackageClasses属性;用来指示扫描自动装配Bean的范围!当不设置参数的时候,默认扫描当前Java Config类的同包范围!
- 通过@Component注解,实现Bean的配置;可以通过参数指定Bean的id
- 通过@Autowired注解,实现Bean的依赖注入,但是@Autowired只能注入Bean,不能注入直接量;可以通过@Autowired注解修饰构造方法、setter方法、成员变量来实现Bean的注入
- 通过@Bean注解,实现直接量的注入;可以通过@Bean注解修饰成员变量、方法入参来实现直接量的注入
- 当通过@Autowired注解,自动注入Bean失败的时候,Spring会抛出异常,可以通过使用@Autowired注解的required属性设置为false来屏蔽这个异常的抛出
- Spring支持JSR330的相关注解,可以使用@Named注解替换@Component注解;使用@Inject注解替换@Autowired注解
- Spring支持JSR250的相关注解,可以使用@Resource注解替换@Autowired注解,@Resource注解可以明确通过Bean id注入;支持@PostConstruct注解(创造资源之后的回调处理),支持@PreDestroy注解(销毁资源之前的回调处理)
2.4 混合配置(即上述三种方式是可以混合使用的):
2.4.1 xml配置中引入JavaConfig配置类:
<!-- xml配置文件中引入其他xml配置文件 -->
<import resource="配置文件.xml" />
<!-- xml配置文件中引入JavaConfig配置类 -->
<bean class="包路径.JavaConfig配置类名" />
2.4.2 JavaConfig配置类中引入xml配置:
@Configuration
/*JavaConfig配置类中引入其他配置类*/
@Import({Config.class})
/*JavaConfig配置类中引入xml配置文件*/
@ImportResource({"config.xml"})
public class Config {
}
比较:
- xml配置和JavaConfig类配置属于显示装配Bean和显示注入依赖,注解配置属于根据扫描范围和IOC容器实现自动装配及自动注入依赖!
- xml配置和注解自动装配的Bean的构造方法可以为private属性,JavaConfig类配置的Bean的构造方法必须要是public属性
- xml文件相比JavaConfig配置类,依赖注入配置比较麻烦;而且JavaConfig配置类是基于Java代码实现的比起xml配置可以更好的利用Java的语言特性,并且在Bean的初始配置的时候可以增加自己的逻辑!
- 注解自动装配在很多场景下使用都比较简单,但是在针对一个类进行多个Bean装配的时候和多个同类型的Bean注入的时候,就显的比较复杂了(即由于同一个类不能有两个@Component注解,@Autowired注解不可以明确指定注入的Bean id)!!!
3. Profile配置
Spring可以通过Profile的配置,来控制不同Bean在不同的Profile属性中差异化装配
3.1 Profile属性的配置:
3.1.1 JavaConfig配置类配置:
可以通过@Profile注解整体标记一个JavaConfig配置类,也可以通过@Profile标记一个Bean的配置方法
/*标记一个JavaConfig类*/
@Configuration
@Profile("profile1")
public class JavaConfig {
}
/*标记一个Bean*/
@Configuration
public class JavaConfig {
@Bean
@Profile("profile1")
public TestBean TBean() {
return new TestBean();
}
}
3.1.2 xml文件的配置:
可以通过<beans>标签的profile属性,标记一个或者多个Bean
<beans profile="profile1">
<bean class="A1"/>
<bean class="A2"/>
</beans>
<beans profile="profile2">
<bean class="B1"/>
</beans>
3.2 Profile属性的激活:
Profile属性默认情况下是不激活的,即所有被标记为Profile的Bean都是默认不装配的;只有当对应的Profile属性值被激活后,对应的Profile标记的Bean才会进行装配!
3.2.1 属性值:
spring.profiles.default:profile的默认值,优先active;active没设置,再找defualt
spring.profiles.active: profile的属性值
3.2.2 激活属性值:
- DispatcherServlet的初始参数
- web应用上下文参数
- 环境变量
- JVM系统属性
可以通过上面描述的参数设置方式,激活Profile属性值!
4. 条件化创建Bean
Spring4引入了@Conditional注解,可以在@Component或@Bean注解后面使用@Conditional注解,并给出Condition函数式接口的实现类,通过返回值确定Bean是否可以创建;返回true,则创建;返回false,则不创建
Ps:Spring4后的版本,@Profile注解也是依赖@Conditional注解实现的
5. @Autowired和@Inject注解注入的歧义性
由于@Autowired和@Inject注解都是依靠类类型进行注入的,所以当存在多个同类型Bean的情况下,依赖注入就会发生歧义性!针对这样的歧义性,Spring给出两种解决方式!
5.1 Primary标签:
可以在@Component注解和@Bean注解后面增加@Primary注解,用来指示当前Bean类型被注入的时候进行首选注入!也可以在xml配置文件<bean>标签中增加Primary属性!
/*JavaConfig配置类中,使用@Primary注解*/
@Configuration
public class JavaConfig {
@Bean
@Primary
public TestBean TBean() {
return new TestBean();
}
}
/*通过注解自动装配Bean使用@Primary注解*/
@Component("TBean")
@Primary
public class TestBean {
}
<bean class="Bean" primary="true"/>
5.2 @Qualifier注解:
可以在@Component注解后面使用@Qualifier注解设置限定符,在@Autowired注解后面使用相同的@Qualifier限定符就可以把指定的Bean注入进来!但是Spring并没有约束@Component后面@Qualifier注解限定符必须是唯一的,所以依然有可能存在多对一的歧义场景!
@Component("DIBean")
@Qualifier("DI")
public class DIBean {
}
@Component("TBean")
public class TestBean {
@Autowired
@Qualifier("DI")
DIBean di;
}
5.3 @Autowired、@Inject和@Resource区别:
- @Resource是JSR250规范中描述的注解、@Inject是JSR330规范中描述的注解、@Autowired是Spring框架的注解;
- 使用上@Autowired除了比@Inject多一个required参数,并无其他区别
- @Resource相比@Autowired和@Inject,可以指定Bean id或者Bean类型进行注入
6. SpringBean的作用域
SpringBean有四种作用域:单例作用域、原型作用域、会话作用域、请求作用域
单例作用域:
整个应用中,只有一个Bean实例,每次依赖注入都是相同的一个Bean实例
/*可以修饰JavaConfig类中的@Bean配置*/
/*可以修饰注解配置的@Component*/
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
<bean class="Bean1" scope="singleton"/>
原型作用域:
每次注入或者通过Spring应用上下文获取Bean的时候,都会创建一个新的Bean实例
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
<bean class="Bean2" scope="prototype"/>
会话作用域:
在web应用中,每次会话重新创建一个新的Bean实例
@Scope(value=WebApplicationContext.SCOPE_SESSION)
<bean class="Bean3" scope="session"/>
请求作用域:
在web应用中,每次请求重新创建一个新的Bean实例
@Scope(value=WebApplicationContext.SCOPE_REQUEST)
<bean class="Bean3" scope="rquest"/>
会话作用域和请求作用域的代理:
当会话作用域或者请求作用域的Bean注入的时候,由于Spring初始上下文的时候并不存在会话和请求,需要注入一个代理类,通过代理类进行不同会话作用域或者请求作用域Bean的委托!
- 当注入类型为接口的时候,使用ScopedProxyMode.INTERFACES代理模式
- 当注入类型为实现类的时候,使用ScopedProxyMode.TARGET_CLASS代理模式
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
<bean class="Bean4" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
<bean/>
7 Spring获取资源文件
7.1 加载资源文件:
7.1.1 注解方式:
在JavaConfig配置类上,@Configuration注解后面使用@PropertySource注解指定资源文件路径,实现资源文件的加载!
@Configuration
@PropertySource("classpath:app.properties")
public class JavaConfig {
@Bean
public PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
7.1.2 xml配置文件方式:
xml文件配置同样可以实现资源文件的加载,可以使用context:property-placeholder命名空间和装配PropertySourcesPlaceholderConfigurer类两种方式
context:property-placeholder命名空间:
<context:property-placeholder location="classpath:app.properties" file-encoding="UTF-8"/>
装配PropertySourcesPlaceholderConfigurer类:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="location">
<value>app.properties</value>
</property>
</bean>
7.2 资源信息获取:
资源文件
#app.properties文件
t1=test1
t2=test2
7.2.1 Environment接口实现(通过getProperty方法获取参数):
@Component("TBean")
public class TestBean {
@Autowired
public Environment env;
}
public class SpringTest {
public static void main(String[] args) {
/*通过Java Config类,生成Spring IOC容器*/
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
/*通过Bean id获取Bean实例*/
TestBean t = (TestBean)context.getBean("TBean");
System.out.println(t.env.getProperty("t1"));
}
}
7.2.2 占位符方式:
@Component("TBean")
public class TestBean {
@Value("${t1}")
public String s;
}
7.2.3 SpEL表达式方式:
@Component("TBean")
public class TestBean {
@Value("#{'${t1}'}")
public String s;
}
Ps:
- 当使用$占位符的时候,一定要装配PropertySourcesPlaceholderConfigurer类,因为PropertySourcesPlaceholderConfigurer是占位符实现类,否则报错!JavaConfig配置类需要显示配置、xml配置文件也可以显示配置,同样也可以使用context:property-placeholder命名空间,Spring会自动完成装配!
- SpEL表达式比较复杂,这里就先不展开描述了!