1 Spring IoC概述
控制反转是一种通过描述(XML或者注解)并通过第三方去产生或获取特定对象的方式,最大的好处在于降低对象之间的耦合。
2 Spring IoC容器
2.1 BeanFactory和ApplicationContext
IoC容器主要基于BeanFactory和ApplicationContext两个接口,其中BeanFactroy是SpringIoC容器所定义的最底层接口,ApplicationContext是其高级接口之一,并且对BeanFactory功能做了许多拓展,所以一般情况下都会使用ApplicationCOntext作为SpringIoC容器。其接口设计图如下图所示:
2.1.1 BeanFactory源码分析
BeanFactory源码如下:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
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;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
-
getBean()顾名思义是用来获取SpringIoC容器管理的Bean,从参数类型看可以是字符串,也可以是Class类型;
-
isSingleton()方法用于判断是否是单例模式,如果判断为真,则该Bean在容器中作为唯一单例存在(默认);
-
isPrototype()如果判断为真,则每次从容器中取Bean,容器都会生成一个新的实例;
-
isTypeMatch()方法按照Java类型进行Bean的匹配;
-
getAliases()方法用于获取别名。
2.1.2 ApplicationContext的实现类
-
ClassPathXmlApplicationContext
从XML文件中加载Beans的定义,XML文件路径基于ClassPath;
-
FileSystemXmlApplicationContext
从XML文件中加载Beans的定义,需提供XML文件的全路径名;
-
XmlWebApplicationContext
为Web工程定制。
2.2 容器的初始化和依赖注入
Bean的初始化分为3步:
-
Resource定位,SpringIoC容器根据开发者的配置,进行资源定位,如XML或者注解配置;
-
BeanDefinition的载入,Spring根据开发者配置的内容获取对应的POJO,用以生成对应的实例;
-
BeanDefinition的注册,将步骤2载入的POJO向SpringIoC容器中进行注册,这样开发者就可以通过描述得到容器中的Bean了
依赖注入:
lazy-init——是否初始化Bean,默认为false,也就是容器默认会自动初始化Bean,如果将其设置为true,那么只有当我们调用getBean()方法时才会进行初始化,完成依赖注入。
<bean id="testBean" class="com.fhx.TestBean" lazy-init="true">
2.3 Spring Bean的生命周期
Bean的生命周期如图所示:
-
Spring对Bean进行实例化(相当于程序中的new Xx());
-
Spring将值和Bean的引用注入进Bean对应的属性中;
-
如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()方法(实现BeanNameAware主要是为了通过Bean的引用来获得Bean的ID,一般业务中是很少有用到Bean的ID的);
-
如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanDactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入。(实现BeanFactoryAware 主要目的是为了获取Spring容器,如Bean通过Spring容器发布事件等);
-
如果Bean实现了ApplicationContextAwaer接口,Spring容器将调用setApplicationContext(ApplicationContext ctx)方法,把应用上下文作为参数传入(作用与BeanFactory类似都是为了获取Spring容器,不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入,而Spring容器在调用setBeanDactory前需要程序员自己指定(注入)setBeanDactory里的参数BeanFactory );
-
如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessBeforeInitialization(预初始化)方法(作用是在Bean实例创建成功后对进行增强处理,如对Bean进行修改,增加某个功能);
-
如果Bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet方法,作用与在配置文件中对Bean使用init-method声明初始化的作用一样,都是在Bean的全部属性设置成功后执行的初始化方法;
-
如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessAfterInitialization(后初始化)方法(作用与6的一样,只不过6是在Bean初始化前执行的,而这个是在Bean初始化后执行的,时机不同 );
-
经过以上的工作后,Bean将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁
-
如果Bean实现了DispostbleBean接口,Spring将调用它的destory方法,作用与在配置文件中对Bean使用destory-method属性的作用一样,都是在Bean实例销毁前执行的方法。
参考:
https://www.jianshu.com/p/1dec08d290c1
https://www.zhihu.com/question/38597960
3 装配Spring Bean
3.1 依赖注入的三种方式
3.1.1 构造器注入
构造器注入依赖于构造方法实现,如以下实体类Role构造方法:
public class Role {
private Long id;
private String roleName;
private String note;
public Role(String roleName, String note) {
this.roleName = roleName;
this.note = note;
}
}
对应的XML配置文件:
<bean id="role" class="com.ssm.pojo.Role">
<constructor-arg index="0" value="总经理"/>
<constructor-arg index="1" value="管理人员"/>
</bean>
constructor-arg标签用于定义构造方法的参数,index用于定义参数的位置,value设置对应的值。
3.1.2 使用setter注入
利用Java Bean规范所定义的setter方法完成注入,优点是灵活且可读性高,允许将构造方法生命为无参的,然后通过反射对应的setter方法注入相应的值。
<bean id="role2" class="com.ssm.pojo.Role">
<property name="roleName" value="总经理"/>
<property name="note" value="管理人员"/>
</bean>
3.1.3 接口注入
该方式主要是获取系统外的资源,如通过JNDI获取Tomcat配置的数据源:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactroyBena">
<property name="jndiName">
<value>java:comp/env/jdbc/ssm</value>
</property>
</bean>
3.2 通过XML配置装配Bean
通过XML装配Bean需要定义对应的XML文件,即引入对应的XML模式(XSD)文件:
<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-3.0.xsd">
<!--Spring Bean配置代码-->
</beans>
- 装配简易值
<bean id="source" class="com.ssm.pojo.Source">
<property name="fruit" value="橙汁"/>
<property name="sugar" value="无糖"/>
<property name="size" value="小杯"/>
</bean>
<bean id="juiceMaker" class="com.ssm.pojo.JuiceMaker">
<property name="beverageShop" value="蜜雪冰城"/>
<property name="source" ref="source"/>
</bean>
- id:容器中该Bean对应的编号,非必需属性,如果没有生命,Spring默认采用“全限定名#{num}”的格式生成编号,如“com.ssm.pojo.Source#0”;
- class:类的全限定名;
- property:定义类的属性,name对应属性名,value对应属性值;
- ref:当某属性是一个对象时,可以使用ref结合对象的id属性进行定义。
- 装配集合
- 装配简单集合
实体类代码:
public class ComplexAssembly{
private Long id;
private List<String> list;
private Map<String,String> map;
private Properties props;
private Set<String> set;
private String[] array;
/**省略getter和setter**/
}
对应的XML配置代码:
<bean id="complexAssembly" class="com.ssm.pojo.ComplexAssembly">
<property name="id" value="1"/>
<property name="list">
<list>
<value>value-list-1</value>
<value>value-list-2</value>
<value>value-list-3</value>
</list>
</property>
<property name="map">
<map>
<entry key="key1" value="value-key-1"/>
<entry key="key2" value="value-key-2"/>
<entry key="key3" value="value-key-3"/>
</map>
</property>
<property name="props">
<props>
<prop key="prop1">value-prop-1</prop>
<prop key="prop2">value-prop-2</prop>
<prop key="prop3">value-prop-3</prop>
</props>
</property>
<property name="set">
<set>
<value>value-set-1</value>
<value>value-set-2</value>
<value>value-set-3</value>
</set>
</property>
<property name="array">
<array>
<value>value-array-1</value>
<value>value-array-2</value>
<value>value-array-3</value>
</array>
</property>
</bean>
- 装配对象集合
实体类代码:
public class Role{
private Long id;
private String roleName;
private String note;
/**省略getter和setter**/
}
public class User{
private Long id;
private String userName;
private String note;
/**省略getter和setter**/
}
public class UserRoleAssembly{
private Long id;
private List<Role> list;
private Map<Role,User> map;
private Set<Role> set;
/**省略getter和setter**/
}
对应的XML配置代码:
<bean id="role1" class="com.ssm.pojo.Role">
<property name="id" value="1"/>
<property name="roleName" value="roleName1"/>
<property name="note" value="roleNote1"/>
</bean>
<bean id="role2" class="com.ssm.pojo.Role">
<property name="id" value="2"/>
<property name="roleName" value="roleName2"/>
<property name="note" value="roleNote2"/>
</bean>
<bean id="user1" class="com.ssm.pojo.User">
<property name="id" value="1"/>
<property name="userName" value="userName1"/>
<property name="note" value="userNote1"/>
</bean>
<bean id="user2" class="com.ssm.pojo.User">
<property name="id" value="2"/>
<property name="userName" value="userName2"/>
<property name="note" value="userNote2"/>
</bean>
<bean id="userRoleAssembly" class="com.ssm.pojo.UserRoleAssembly">
<property name="id" value="1"/>
<property name="list">
<list>
<ref bean="role1"/>
<ref bean="role2"/>
</list>
</property>
<property name="map">
<map>
<entry key-ref="role1" value-ref="user1"/>
<entry key-ref="role2" value-ref="user2"/>
</map>
</property>
<property name="set">
<set>
<ref bean="role1"/>
<ref bean="role2"/>
</set>
</property>
</bean>
3.3基于XML的自动装配
Spring的自动装配有三种模式:byTpye(根据类型),byName(根据名称)、constructor(根据构造函数)。
在byTpye模式中,Spring容器会基于反射查看bean定义的类,然后找到与依赖类型相同的bean注入到另外的bean中,这个过程需要借助setter注入来完成,因此必须存在set方法,否则注入失败。
//dao层
public class UserDaoImpl implements UserDao{
//.......
@Override
public void done(){
System.out.println("UserDaoImpl.invoke......");
}
}
//service层
public class UserServiceImpl implements UserService {
//需要注入的依赖
private UserDao userDao;
/**
* set方法
* @param userDao
*/
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void done(){
userDao.done();
}
}
通过使用<bean>的autowire属性启动名称为userService的自动装配功能。
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<!-- byType 根据类型自动装配userDao-->
<bean id="userService" autowire="byType" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
实上byType模式可能存一种注入失败的情况,由于是基于类型的注入,因此当xml文件中存在多个相同类型名称不同的实例Bean时,Spring容器依赖注入仍然会失败,因为存在多种适合的选项,Spring容器无法知道该注入那种,此时我们需要为Spring容器提供帮助,指定注入那个Bean实例。可以通过<bean>标签的autowire-candidate设置为false来过滤那些不需要注入的实例Bean。
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<!-- autowire-candidate="false" 过滤该类型 -->
<bean id="userDao2" autowire-candidate="false" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<!-- byType 根据类型自动装配userDao-->
<bean id="userService" autowire="byType" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
除了上述的解决方案外,还可采用byName模式的自动装配,此时Spring只会尝试将属性名与bean名称进行匹配,如果找到则注入依赖bean。
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<!-- byName 根据名称自动装配,找到UserServiceImpl名为 userDao属性并注入-->
<bean id="userService" autowire="byName" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
需要了解的是如果Spring容器中没有找到可以注入的实例bean时,将不会向依赖属性值注入任何bean,这时依赖bean的属性可能为null,因此我们需要小心处理这种情况,避免不必要的奔溃。对于constructor模式,在该模式下Spring容器同样会尝试找到那些类型与构造函数相同匹配的bean然后注入。
public class UserServiceImpl implements UserService {
private UserDao userDao;
//constructor模式
public UserServiceImpl(UserDao userDao){
this.userDao=userDao;
}
// /**
// * set方法
// * @param userDao
// */
// public void setUserDao(UserDao userDao) {
// this.userDao = userDao;
// }
@Override
public void done(){
userDao.done();
}
}
基于xml配置:
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<!-- constructor自动装配userDao-->
<bean id="userService" autowire="constructor" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
在实际测试中发现当出现多个bean实例时,如果含有名称与类中声明一样时仍然能正确找到对应类,如userDao、userDao2同时出现,而userService实现类中存在同名属性userDao,此时无需过滤也能正确注入:
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<!--没有过滤userDao2-->
<bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userService" autowire="constructor" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
当只存在userDao2时,仍然能正常注入该类,如下形式
<!--只存在userDao2,仍然能正确注入-->
<bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userService" autowire="constructor" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
但是当不存在userDao,只存在userDao2和userDao3时就不能注入了此时必须使用autowire-candidate=”false” 过滤该类型了。
<!--只存在userDao2,userDao3 无法成功注入-->
<bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userDao3" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userService" autowire="constructor" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
因此得出如下结论:在constructor模式下,存在单个实例则优先按类型进行参数匹配(无论名称是否匹配),当存在多个类型相同实例时,按名称优先匹配,如果没有找到对应名称,则注入失败,此时可以使用autowire-candidate=”false” 过滤来解决。
3.4 通过注解装配Bean
3.4.1 使用@Component注册Bean
示例代码:
@Component(value = "role")
public class Role{
@Value("1")
private long id;
@Value("role_name_1")
private String roleName;
}
- 注解@Component代表这个类会被扫描成Bean实例,value属性为这个实例的id,可以简写为@Component(“role”),也可以省略不写,默认值为类名首字母小写;
- 注解@Value用于基本类型或字符串属性的注入。
该注解生效的前提是,指定扫描路径,让Spring知道去哪里进行扫描,有两种实现方式:
- 配置XML文件
<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.cc.study.annotation"></context:component-scan>
</beans>
2. 使用@ComponentScan注解
@ComponentScan(basePackages={"com.ssm.pojo","com.ssm.service"})
public class ApplicationConfig{
/**省略**/
}
通过上述配置类获取Bean
public static void testBean(){
AnnotationConfigApplicationContext cx = new AnnotationConfigApplicationContext(ApplicationConfig.class);
Role role = cx.getBean(Role.class);
/**do something**/
}
- 注解@ComponentScan有两个配置项,1个是basePackages,可以配置一个包路径的数组,Spring会扫名对应的包及其子包;2是basePackagesClasses,可以配置多个类,Spring会扫名配置类所在的包及其子包;
- 如果多个@ComponentScan扫描重复的类,那么将会生成多个Bean实例,所以一般不要使用多个@ComponentScan注解进行配置。
- @Component的衍生注解
- @Repository ——注解在Dao层
- @Service——注解在服务层
- @Controller——注解在控制器
3.4.2 基于@Autowired实现自动装配
- @Autowired的3种使用方式
- 注解在属性上:
public class RoleServiceImpl implements RoleService{
@Autowired
private Role role;
/**do something**/
}
- 注解在方法上:
public class RoleServiceImpl implements RoleService{
private Role role;
@Autowired
public void setRole(Role role){
this.role = role;
}
/**do something**/
}
- 注解在参数上:
public class RoleServiceImpl implements RoleService{
private Role role;
public RoleServiceImpl(@Autowired Role role){
this.role = role;
}
/**do something**/
}
2.注入失败的解决措施
@Autowired注解会根据目标属性的类型(byType)去容器中获取目标实例实现注入,当根据类型找不到实例或找到多个实例时,都会发生注入失败的情况。
- required属性——默认为true,设置为false则允许找不到目标对象不注入;
- @Primary注解 ——实现目标对象的优先级,告诉Spring优先使用哪个类进行注入;
@Component("roleService1")
public class RoleServiceImpl1 implements RoleService{
/**do something**/
}
@Component("roleService2")
@Primary
public class RoleServiceImpl2 implements RoleService{
/**do something**/
}
@Component
public class RoleController{
@Autowired
private RoleService roleService;//注入的实例为RoleService2
/**do something**/
}
- @Qualifier——使用byName的方式消除歧义性
@Component("roleService1")
public class RoleServiceImpl1 implements RoleService{
/**do something**/
}
@Component("roleService2")
public class RoleServiceImpl2 implements RoleService{
/**do something**/
}
@Component
public class RoleController{
@Autowired
@Qualifier("roleService1")
private RoleService roleService;//注入的实例为RoleService1
/**do something**/
}
3.4.3 基于@Resource实现自动装配
与@Autowried具备相同功效的还有@Resource,默认按 byName模式 自动注入,由J2EE提供,需导入Package: javax.annotation.Resource,可以标注在成员变量和set方法上,但无法标注构造函数。@Resource有两个中重要的属性:name和type。Spring容器对于@Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。因此使用name属性,则按byName模式的自动注入策略,如果使用type属性则按 byType模式自动注入策略。倘若既不指定name也不指定type属性,Spring容器将通过反射技术默认按byName模式注入。
//@Autowired标注成员变量
@Autowired
@Qualifier("userDao")
private UserDao userDao;
//上述代码等价于@Resource
@Resource(name=“userDao”)
private UserDao userDao;//用于成员变量
//也可以用于set方法标注
@Resource(name=“userDao”)
public void setUserDao(UserDao userDao) {
this.userDao= userDao;
}
3.4.4使用@Bean装配Bean
@Component只能注解在类上,不能注解在方法上,当需要引入第三方库中的组件时,无法将@Component注解在相应的类上,这时候Spring提供了@Bean注解来解决这一问题。
例如装配数据源Bean:
@Bean(name="dataSource")
public DataSource getDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/sys");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
这样就实现了DataSource实例被Spring容器管理,此外,@Bean注解的initMethod和destoryMethod属性可自定义实例的初始化方法和销毁方法。
@Bean(name="dataSource",initMethod="init",destoryMethod="destory")
3.4.5 其他常用注解
@Import
@ImportSource
@Resource
@Configuration
3.4 Profile的使用
分别配置一个测试和开发环境的数据库连接池
public class ProfileDataSource {
@Bean(name = "devDataSource")
@Profile("dev")
public DataSource getDevDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/sys1");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
@Bean(name = "testDataSource")
@Profile("test")
public DataSource getTestDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/sys2");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
配置了Profile后发现这两个Bean都不会被加载到Spring IoC容器中,需要自行激活Profile,可采取下面几种激活方法:
- 采用@ActiveProfile注解
@ActiveProfile("dev")
public class ProfileTest{
@Autowired
private DataSource dataSource;
@Test
public void test() {
System.out.println(dataSource.getClass().getName());
}
}
- 配置JVM参数
- spring.profile.default:默认启动的Profile,如果系统为配置关于Profile的参数,将默认启动它。
- spring.profile.active:激活的Profile,如果配置了它,default将失效
比如想启动test对应的Profile,可如下配置:
JAVA_OPTS="-Dspring.profiles.active=test"
在IDEA中可如下图所示配置:
- 在web.xml中配置
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>test</param-value>
</context-param>
3.5 加载properties文件
使用properties配置文件可以减少硬编码,提高配置属性修改的便利性,如下所示的数据库配置文件database-config.properties:
jdbc.database.driver=com.mysql.jdbc.Driver
jdbc.database.url=jdbc:myqsl://localhost:3306/sys
jdbc.database.username=root
jdbc.database.password=123456
3.5.1 使用注解的方式加载属性文件
使用@PropertySource注解加载配置文件
@Configuration
@PropertySource(value={"classpath:database-config.properties"},ignoreResourceNotFound=true)
public class ApplicationConfig{
/**do something**/
}
- value:字符串数组,可以配置多个配置文件
- ignoreResourceNotFound:如果找不到对应的属性文件是否进行忽略处理,默认false
测试加载属性
public class AnnotationMain {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
String url = context.getEnvironment().getProperty("jdbc.database.url");
System.out.println(url);
}
}
PropertySourcesPlaceholderConfigurer解析属性文件
Spring推荐使用一个属性文件解析类进行解析属性占位符,使用它可以解析对应的属性文件,并通过占位符去引用对应的位置
例如解析一个数据库配置文件:
@Configuration
@PropertySource(value={"classpath:database-config.properties"},ignoreResourceNotFound=true)
public class ApplicationConfig{
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
/**do something**/
}
使用@Value注解和占位符引用定义好的配置:
public class DataSourceBean {
@Value("${jdbc.database.driver}")
private String driver;
@Value("${jdbc.database.url}")
private String url;
@Value("${jdbc.database.username}")
private String username;
@Value("${jdbc.database.password}")
private String password;
@Bean
public DataSource getDataSource() {
Properties props = new Properties();
props.setProperty("driver", driver);
props.setProperty("url", url);
props.setProperty("username", username);
props.setProperty("password", password);
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
3.5.2 使用XML的方式加载属性文件
- 配置单个属性文件
<context:property-placeholder ignore-resource-not-found="true" location="classpath:database-config.properties"/>
属性location配置属性文件的路径,如果是多个文件需要用逗号隔开
2. 配置多个属性文件
当存在多个属性文件时,以下这种方式可读性更高:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<array>
<value>classpath:database-config.properties</value>
<value>classpath:log4j.properties</value>
</array>
</property>
<property name="ignoreResourceNotFound" value="true"/>
</bean>
3.6 条件化装配Bean
某些条件下可能不需要装配Bean,如不存在database-config.properties文件中配置的属性时,就不需要根据这个属性文件装配数据源,此时就需要条件化装配Bean。
@Conditional注解,数据源配置代码如下所示:
@Conditional({DataSourceCondition.class})
public class DataSourceBean {
@Value("${jdbc.database.driver}")
private String driver;
@Value("${jdbc.database.url}")
private String url;
@Value("${jdbc.database.username}")
private String username;
@Value("${jdbc.database.password}")
private String password;
@Bean
public DataSource getDataSource() {
Properties props = new Properties();
props.setProperty("driver", driver);
props.setProperty("url", url);
props.setProperty("username", username);
props.setProperty("password", password);
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
条件类DataSourceCondition需要实现Condition接口,并实现接口的matches方法:
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class DataSourceCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取上下文环境
Environment env = context.getEnvironment();
//判断是否存在关于数据源的基础配置
return env.containsProperty("jdbc.database.drier")
&& env.containsProperty("jdbc.database.url")
&& env.containsProperty("jdbc.database.username")
&& env.containsProperty("jdbc.database.password");
}
}
该方法有两个参数,通过ConditionContext可以获取Spring的运行环境,通过AnnotatedTypeMetadata可以获得关于Bean的注解信息,如果该方法返回true,则Spring才会创建对应的Bean。
3.7 Bean的作用域
Spring使用@Scope注解提供了4种作用域:
- singleton——单例,默认的选项,在整个应用中,Spring只生成一个Bean实例;
- prototype——原型,每次注入Spring都会为它创建一个新的实例;
- session——会话,在Web应用中使用,在会话过程中Spring只创建一个实例;
- request——请求,在Web应用中使用,在一次请求中Spring只创建一个实例,不同的请求会创建不同的实例。
参考
https://blog.csdn.net/javazejian/article/details/54561302