IOC/DI
一种仅通过构造器参数、工厂方法参数或由构造器/工厂方法产生的实例的属性定义依赖关系,由容器在创造bean的过程中注入依赖的过程。下述例子来源: https://www.zhihu.com/question/23277575/answer/169698662
所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”
org.springframework.beans
and org.springframework.context
是IoC容器的基础,
BeanFactory
接口提供管理object的配置机制, ApplicationContext
是其子类,提供更多特性。换句话说,BeanFactory提供基本功能与配置框架,ApplocationContext是其超集。
功能
实现了解耦,解决了bean的依赖问题
由ioc容器动态注入对象的依赖,管理bean生命周期,而不手动创建依赖对象并管理。
IOC容器
org.springframework.context.ApplicationContext
象征着IOC容器,负责beans的实例化,配置与管理。容器通过读取配置元数据(configuration metadata)获取bean的构造信息,配置元数据可以是XML,注解或者java代码。
ClassPathXmlApplicationContext
or FileSystemXmlApplicationContext
是Spring提供的两个Application实例
即使用方式为:在resources下配置数据源xml文件→使用时读取配置文件,获取IOC容器,通过IOC容器与反射机制根据配置文件注入依赖并获取bean
配置bean
IOC容器通过配置元数据读取要管理的bean信息,由元数据决定如何注入依赖,如果使用注解则需要在配置文件中加上
<context:annotation-config/>
使用标签<bean/>与<beans>定义bean
使用IOC容器:
// 创建IOC
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// 获取bean
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// 获取元数据
List<String> userList = service.getUsernameList();
使用静态工厂生成bean
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
对应的xml为:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"<!--通过id引用配置依赖关系-->
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
基于构造器生成bean
默认按照构造器中参数的顺序依次为bean注入属性,参数中的bean由ref指示,按照顺序注入依赖,使用标签< constructor-arg >配置
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
当构造器中的参数包含其它基类型时,使用value注入,或者直接根据index注入
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/><!--通过指示type注入value,默认还是按照顺序的-->
<constructor-arg type="java.lang.String" value="42"/>
</bean>
更通用的,可以使用index,index从0开始
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
也可以通过参数名注入属性,要使用name则必须启动debug模式,或者使用注解 @ConstructorProperties 声明构造器参数,否则运行时无法确定构造器参数名
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
注意,基类型使用value注入,bean使用ref注入
也可以使用c-scheam简化,引入 xmlns:c=“http://www.springframework.org/schema/c”
参数属性值可以直接写,引用bean则在名后加-ref即可,如下所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
基于setter注入依赖
通过setter方法注入依赖,使用标签<property>
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<!--服务层实现依赖于DAO层对象-->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<property name="id" value="001"/><!--Spring提供类型自动转换服务-->
</bean>
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
</bean>
</beans>
在PetStoreServiceImpl中一定有对应的setter方法才行,区别就是使用的标签不同。对于属性值,property可以简写为p:id=“001”,前提是引入 xmlns:p=“http://www.springframework.org/schema/p” ,对于这种写法来说,没有模式定义,也就是直接使用p:bean属性名=value这种形式,对于引用属性来说,直接属性名-ref即可,如下所示:
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/><!--spouse为属性名-->
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
依赖注入过程
- ApplicationContext被创建并且初始化,读取配置元数据
- 对于每个bean,其依赖(dependencies)以属性、构造器参数、静态工厂方法的方式被描述,创建bean时注入依赖
- bean的每个依赖属性都要被实际定义
循环依赖问题
如果通过构造器注入可能导致循环依赖,类A需要类B作为构造器参数,类B需要类A作为构造器参数,因此循环构造依赖,解决办法就是使用setter方式注入依赖
内联bean/inner Beans
既可以通过ref引用一个bean实例,也可以通过内联bean的方式。默认情况下,xml中配置的bean都是单例模式,即每个bean都引用同一个对象,inner Beans相当于配置匿名内部类
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
集合类型Collections
集合元素属性 list,set,map,properties分别用标签<list/>,<set/>,<map/>,<pros/>表示,集合中如果要使用bean则使用带ref的引用标签,下面给出部分示例:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
集合合并 Collection Merging
对于子类来说,可与父类的集合合并,相同元素子类会覆盖父类
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
子类bean指定parent同时启动merge=true,无法合并不同类型(抛出异常),Spring会自动检查语法,因此基类型如int会自动转换而不会当做String类型处理,对于null来说使用标签<null/>
依赖相关
bean通过ref属性引用另一个bean的id表示依赖注入,但有时候依赖关系可能比较低,即出现间接依赖情况
depends-on/@DependsOn
用于控制bean的实例化顺序。 spring容器载入bean顺序是不确定的,spring框架没有约定特定顺序逻辑规范。但spring保证如果A依赖B(如beanA中有@Autowired B的变量),那么B将先于A被加载。但如果beanA不直接依赖B,但在使用时要确保B已经被实例化,即依赖于B的某些实现,典型的如观察者模式,全局缓存访问等,可以通过depends-on或@DependsOn 约定B必须先于A实例化,以避免某些不好的影响。
假设bean C中有一个静态字段为全局参数
public class SystemSettings{
//缓存更新时间
public static int REFRESH_CYCLE = 60;
......
}
bean B用于在系统启动时更新C的值
public class SystemInit{
public SystemInit(){
//模拟从数据库中加载的系统参数配置值
SystemSettings.REFRESH_CYCLE=100;
......
}
}
bean A直接使用C的值
public class CacheManager{
public CacheManager(){
//缓存刷新定时处理
t.schedule(new CacheTask() ,0,SystemSettings.REFRESH_CYCLE);
}
......
}
逻辑上应该希望B先于A实例化,但spring实例化bean的顺序不确定,假如A先于B实例化,那么可能就访问到了不希望的值,出现某些意想不到的错误,但实际上还是要由业务功能确定,注意depends-on/@DependsOn用于控制bean的初始化顺序即可。
在xml中使用depends-on:
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
或在代码中使用@DependsOn:
1:直接标注在带有@Component注解的类上面;
2:直接标注在带有@Bean 注解的方法上面;
lazy-init/default-lazy-init
即设置bean为懒汉模式,因为默认情况下设置的bean都为单例模式,所以有两种初始化bean的方式,一种是先实例化,使用的时候直接给就行了,另一种就是先不实例化,等到要使用的时候才进行实例化操作,默认情况下是先实例化,lazy-init即开启第二种情况,如
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
同时,对于标签<beans>,也提供default-lazy-init,表示所有bean使用懒汉模式
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
bean作用域(bean Scopes)
使用标签<scope>设置
单例模式
默认模式,返回唯一的bean实例
原型模式
每次都返回一个新的bean实例
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
自动装配Bean(Autowire)
自动装配即自动将一个bean依赖装配到bean中而不必使用ref手动引用,自动装配基于setter,即自动将所有setter装配为依赖,默认情况下关闭
byName
根据 Property 的 Name 自动装配,如果一个 bean 的id和另一个 bean 中的 Property 的 name 相同,则自动装配这个 bean 到 Property 中。
<bean id="customerService" class="spring.services.CustomerService" autowire="byName">
</bean>
<bean id="customerDAO" class="spring.dao.CustomerDAO" />
根据setter名字自动装配,CustomerService中有属性 CustomerDAO customerDAO和setCustomerDAO()方法,id和属性名相同,根据名称及setter自动装配
byType
根据属性 Property 的数据类型自动装配,这种情况,CustomerService 设置了 autowire="byType"
,Spring 会自动寻找与属性类型相同的 bean ,找到后,通过调用 setCustomerDAO(CustomerDAO customerDAO) 将其注入。
<bean id="customerService" class="spring.services.CustomerService" autowire="byType">
</bean>
<bean id="customerDAO" class="spring.dao.CustomerDAO" />
即根据bean的属性类型注入,如果有两个一样的类型的bean在xml中被声明,那么直接抛出异常
constructor
根据bean的构造器参数的类型注入,同样不能有多个相同类型
<bean id="customerService" class="spring.services.CustomerService" autowire="constructor">
</bean>
<bean id="customerDAO" class="spring.dao.CustomerDAO" />
常用注解
使用注解必须在配置文件中加上
<context:annotation-config/>
@Required
应用于 bean 属性的 setter 方法,表明这个属性必须被注入,否则抛出异常
@Autowired
用在字段上,表示自动注入,可省略getter与setter,使用时自动扫描容器中的所有bean,按类型(默认byType)进行注入,在配置文件中加入:
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
使用@Autowired不推荐注解静态字段,因为加载机制的问题,扫描时不会注入,因此运行时有空指针异常,@Autowired可用在构造器与方法上,扫描时可通过方法或构造器注入
@Component
public class TestClass {
private static AutowiredTypeComponent component;
//静态字段的注入
@Autowired
public TestClass(AutowiredTypeComponent component) {
TestClass.component = component;
}
// 调用静态组件的方法
public static void testMethod() {
component.callTestMethod();
}
}
@Qualifier
要与@Autowired 配合使用,因为多态的性质,有时候容器可能发现多个符合条件的Bean可以注入从而导致混乱,引入@Qualifier(“ ”)表明注入哪一个bean,该bean是配置文件中的一个bean的id
@Configuration
用在类上,声明一个配置类,等价于配置文件中的beans,要使用该配置类则加载的时候可使用 AnnotationConfigApplicationContext()获取容器
@Bean
用在方法上,声明一个bean,等价于配置文件中的bean,方法名就是bean的id,可指定name,要与@Configuration配合使用,相当于直接在方法中自己实例化一个类,设置属性并返回,交给容器去管理
public class C {
public void c(){
System.out.println("cccccccccccccccccccccccccccc");
}
}
public class D {
public void d(){
System.out.println("dddddddddddddddddddddddddd");
}
}
@Configuration
public class TestConfigure {
@Bean(name = "c")
public C getC(){
return new C();
}
@Bean(name = "d")
public D getD(){
return new D();
}
}
public static void main(String[] args){
ApplicationContext context=new AnnotationConfigApplicationContext(TestConfigure.class);
context.getBean(C.class).c();
context.getBean(D.class).d();
}
@Component
用在class上,开启自动扫描的时候会自动将该bean加入容器当中。在配置文件中加入
<context:component-scan base-package="springTest"/>
<context:annotation-config/>
指定扫描的包并开启自动扫描
除此之外,Spring 有三个与 @Component 等效的注解:
- @Controller:对应表现层的 Bean,也就是 Action 。
- @Service:对应的是业务层 Bean 。
- @Repository:对应数据访问层 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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="springTest"/>
<context:annotation-config/>
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
</beans>
@Component("a")
public class A {
private int id;
@Autowired
private B b;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public A(){
}
@Override
public String toString() {
return "A"+id+b.getId();
}
}
@Component("b")
public class B {
private int id;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public B(){
}
@Override
public String toString() {
return "B" +getId();
}
}
public class BTest {
ApplicationContext context=new ClassPathXmlApplicationContext("/spring/test.xml");
B b=context.getBean(B.class);//通过反射返回实例
A a=context.getBean(A.class);//A B都通过自动扫描注入
@Test
public void test(){
System.out.println(b.toString());
System.out.println(a.toString());
}
}
@Value
@Value的作用是通过注解将常量、配置文件中的值、其他bean的属性值注入到变量中,作为变量的初始值。
常量注入
@Value("normal")
private String normal; // 注入普通字符串
@Value("classpath:com/hry/spring/configinject/config.txt")
private Resource resourceFile; // 注入文件资源
@Value("http://www.baidu.com")
private Resource testUrl; // 注入URL资源
bean属性、系统属性、表达式注入@Value(#{}")
bean属性注入需要注入者和被注入者属于同一个IOC容器,或者父子IOC容器关系,在同一个作用域内。
@Value("#{beanInject.another}")
private String fromAnotherBean; // 注入其他Bean属性:注入beanInject对象的属性another
@Value("#{systemProperties['os.name']}")
private String systemPropertiesName; // 注入系统属性
@Value("#{ T(java.lang.Math).random() * 100.0 }")
private double randomNumber; //注入表达式结果
配置文件属性注入@Value("${}")
@Value("#{}")读取配置文件中的值,注入到变量中去。配置文件分为默认配置文件application.properties和自定义配置文件
-
application.properties:application.properties在spring boot启动时默认加载此文件
-
自定义属性文件:自定义属性文件通过@PropertySource加载。@PropertySource可以同时加载多个文件,也可以加载单个文件。如果相同第一个属性文件和第二属性文件存在相同key,则最后一个属性文件里的key起作用。加载文件的路径也可以配置变量,如下文的${anotherfile.configinject},此值定义在第一个属性文件config.properties
@Component
// 引入自定义配置文件。
@PropertySource({"classpath:com/hry/spring/configinject/config.properties",
"classpath:com/hry/spring/configinject/config_**${anotherfile.configinject}**.properties"})
public class ConfigurationFileInject{
@Value("${app.name}")//缺省的配置文件路径就是src/main/application.properties
private String appName; //这里的值来自application.properties,spring boot启动时默认加载此文件
@Value("${book.name}")
private String bookName;//上述已声明配置文件,所以会查找声明的配置文件的属性
@Value("${book.name.placeholder}")
private String bookNamePlaceholder;
}
###
来源:CSDN
作者:黑 夜 使 者
链接:https://blog.csdn.net/qq_40918961/article/details/103681098