装配spring bean
1.依赖注入的3种方式
在实际环境中实现IoC容器的方式主要分为两大类,一类是依赖查找,依赖查找是通过依赖定位,把对应的资源查找回来;另一类是依赖注入,而spring主要使用的是依赖注入。一般而言,依赖注入可以分为3种方式。
-
构造器注入
-
setter注入
-
接口注入
构造器注入和setter注入是主要的方式,而接口注入是从别的地方注入的方式。
1.1构造器注入
构造器注入依赖构造方法的实现
<bean id="departmentService" class="com.wise.tiger.service.impl.DepartmentServiceImpl"
init-method="init" destroy-method="destory"> <!--使用类构造器实例化Bean --> <constructor-arg name="departmentName" value="生产部"/> </bean>
这样注入还是比较简单的,但是缺点也很明显,由于这里的参数比较少,所以可读性还是比较不错的,但是如果参数较多,这种构造方法就比价复杂了,这个时候就要考虑setter注入了
1.2 使用setter注入
setter注入是spring中最主流的注入方式,利用javaBean规范所定义的setter方法来完成注入,灵活且可读性高。它是通过反射技术实现的
<bean id="departmentService" class="com.wise.tiger.service.impl.DepartmentServiceImpl"
init-method="init" destroy-method="destory"> <property name="departmentName" value="生产部"/> </bean>
1.3 接口注入
参考另一篇博文
2.装配Bean概述
如何将Bean装配到IoC容器中,在spring中提供了三种方法进行配置
-
在xml中显式配置
-
在java的接口和类中实现配置
-
隐式Bean的发现机制和自动装配原则
在现实工作中,这三种方式都会用到,并且经常混合使用。建议优先级如下
(1)基于约定优于配置的原则,最优先的应该是隐式Bean的发现机制和自动装配原则。这样的好处是减少程序开发者的决定权,简单又不失灵活。
(2)在没有办法使用自动装配原则的情况下应该优先考虑Java接口和类中实现配置。这样的好处是避免xml配置的泛滥,也更为容易
(3)上述方法都无法使用的情况下,那么只能选择xml去配置spring IoC容器,比如第三方的类库。
通俗来讲,当配置的类是你自身开发的工程,那么应该考虑java配置为主,而java配置又分为自动装配和Bean名称装配。在没有歧义的基础上,优先使用自动装配,这样可以减少大量的xml配置。如果所需配置的类并不是你的工程开发的,那么建议使用xml的方式。
3. 通过xml装配Bean引入对应的xml模式(xsd)文件
<?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"> </beans>
<bean id="departmentService" class="com.wise.tiger.service.impl.DepartmentServiceImpl"> <property name="departmentName" value="生产部"/> </bean>
-
id:spring找到这个Bean的编号,不是一个必须的属性,如果没有指定,spring采用"全限定名#{number}的格式生成编号。
-
class:一个类的全限定名
-
property:定义类中的属性
<bean id="personService" class="com.wise.tiger.service.impl.PersonServiceImpl"> <property name="personName" value="张三丰"/> <property name="hobbys"> <array> <value>看美女</value> <value>看小说</value> <value>看电影</value> </array> </property> <property name="hobbyList"> <list> <value>hobby-list-1</value> <value>hobby-list-2</value> <value>hobby-list-3</value> <value>hobby-list-4</value> </list> </property> <property name="hobbySet"> <set> <value>hobby-set-1</value> <value>hobby-set-2</value> <value>hobby-set-3</value> </set> </property> <property name="intro"> <map> <entry key="name" value="peppa"/> <entry key="age" value="5"/> <entry key="hobby" value="我喜欢乒,我喜欢乓,我喜欢suzy和我一起跳"/> </map> </property> <property name="prop"> <props> <prop key="prop1">prop-value-1</prop> <prop key="prop2">prop-value-2</prop> </props> </property> </bean>
先引入对应的命名空间和xml模式(xsd)文件
<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" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="personService1" class="com.wise.tiger.service.impl.PersonServiceImpl" p:personName="suzy"/> <beans/> <bean id="personService2" class="com.wise.tiger.service.impl.PersonServiceImpl" c:personName="suzy"/> <beans/>
p代表引用属性,p:personName="suzy"以suzy为值,使用setPersonName方法设置。
<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> <util:list id="emails"> <value>pechorin@hero.org</value> <value>raskolnikov@slums.org</value> <value>stavrogin@gov.org</value> <value>porfiry@gov.org</value> </util:list> <util:map id="emails"> <entry key="pechorin" value="pechorin@hero.org"/> <entry key="raskolnikov" value="raskolnikov@slums.org"/> <entry key="stavrogin" value="stavrogin@gov.org"/> <entry key="porfiry" value="porfiry@gov.org"/> </util:map> <util:set id="emails"> <value>pechorin@hero.org</value> <value>raskolnikov@slums.org</value> <value>stavrogin@gov.org</value> <value>porfiry@gov.org</value> </util:set> <util:properties id="jdbcConfiguration" location="classpath:jdbc-production.properties"/> </beans>
4.通过注解装配Bean
更多的时候已经不再推荐使用xml的方式去装配Bean,更多的时候会考虑使用注解(annotation)的方式去装配Bean。使用注解可以减少xml的配置,注解功能更为强大,它既能实现xml的功能,也提供了自动装配的功能,程序猿所需要做的决断就减少了,更加有利于对程序的开发,这就是“约定优于配置”的开发原则。在Spring中,提供了两种方式来让 Spring IoC容器发现Bean。
- 组件扫描:通过定义资源的方式,让Spring IoC容器扫描对应的包,从而把Bean装配进来。
- 自动装配:通过注解定义,使得一些依赖关系可以通过注解完成
4.1.使用@Component装配Bean
@Component public class EmilSender { }
注解@Component代表Spring IoC容器会把这个类扫描成Bean实例。而其中的value属性代表这个实例在Spring中的id,相当于xml方式定义的Bean的id,也可以简写成@Component(""),也可以直接写成@Component,id即为类的简单名称首字母小写。
spring主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
-
@Component:可以用于注册所有bean
-
@Repository:主要用于注册dao层的bean
-
@Controller:主要用于注册控制层的bean
-
@Service:主要用于注册服务层的bean
4.2.自动装配@Autowired
通过前面Spring IoC容器的学习,我们知道spring是先完成Bean的定义和生成,然后寻找需要注入的资源。也就是当spring生成所有的bean后,如果发现这个注解,它就会在IoC容器中进行查找到对应的类型,将其注入进来,这样就完成了依赖注入。所谓自动装配是一种由spring自己发现对应的bean,自动完成装配工作的方式,它会应用到一个十分常用的注解@Autowired,这个时候spring会根据类型去寻找定义的bean然后将其注入。
当Spring装配Bean属性时,有时候非常明确,就是需要将某个Bean的引用装配给指定属性。比如,如果我们的应用上下文中只有一个org.mybatis.spring.SqlSessionFactoryBean类型的Bean,那么任意一个依赖SqlSessionFactoryBean的其他Bean就是需要这个Bean。毕竟这里只有一个SqlSessionFactoryBean的Bean。为了应对这种明确的装配场景,Spring提供了自动装配(autowiring)。与其显式的装配Bean属性,为何不让Spring识别出可以自动装配的场景。
当涉及到自动装配Bean的依赖关系时,Spring有多种处理方式。因此,Spring提供了4种自动装配策略。
<bean id="" class="" autowire="no|byName|byType|constructor|autodetect"></bean>
- no:不进行自动装配,手动设置Bean的依赖关系
- byName:根据Bean的名字进行自动装配
- byType:根据Bean的类型进行自动装配
- constructor:类似于byType,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误
- autodetect:如果有默认的构造器,则通过constructor的方式进行自动装配,否则使用byType的方式进行自动装配
Spring 2.5 引入了 @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的使用来消除 set ,get方法。当 Spring 容器启动时,AutowiredAnnotationBeanPostProcessor 将扫描 Spring 容器中所有 Bean,当发现 Bean 中拥有 @Autowired 注释时就找到和其匹配(默认按类型匹配)的 Bean,并注入到对应的地方中去。IoC容器有时候会寻找失败,在默认情况下失败就会抛出异常,可以通过配置项required来改变它,比如:@Autowired(required=false)
@Service public class PersonServiceImpl implements PersonSerivce { @Autowired private PersonDao dao; }
4.3.自动装配的歧义性
通常bean的自动装配能够给我们提供很大的方便,它会减少装配应用程序组件时所需要的显式配置的麻烦。不过,仅有一个bean匹配所需的结果时,自动装配才是有效的。如果符合条件的bean不只一个,这时就会阻碍Spring自动装配属性、构造器参数或方法参数。
为了消除歧义性,Spring提供了两个注解@Primary和@Qualifier。
标识首选的bean,某个接口有多个实现类,可以在某个实现类上标注@Primary,当出现歧义时,Spring会使用首选的bean,其他的会忽略,但是如果这个接口有两个实现类都标注了@Primary的话,那么又出现歧义了,所以@Primary只能标注在一个接口的一个实现类上
@Component @Primary public class PersonServiceImpl1 implements PersonService{ } -------------------------------------------------------- <bean id="personService1" class="com.wise.serivce.impl.PersonServiceImpl1 " primary="true" /> -------------------------------------------------------- @Bean @Primary public PersonService getPersonService(){ return new PersonServiceImpl1(); }
可以使用@Qualifier("beanName")明确指定要注入的是哪个bean
4.4.装配带有参数的构造方法类
@Service public class PersonServiceImpl { private PersonDao dao; public PersonServiceImpl(@Autowired PersonDao dao) { this.dao = dao; } }
4.5.使用@Bean装配Bean
以上大部分都是通过@Component装配Bean,但是@Component只能注解在类上,不能注解到方法上,@Bean可以注解到方法上,将方法返回的对象作为Spring的Bean存放在IoC容器中,如没指定name,bean的id默认为方法名。
@Bean(name="", initMethod="init",destroyMethod="destroy")
- name:字符串数组,允许配置多个BeanName,没有配置默认为方法名。
- autowire:标志是否是一个引用的Bean对象,默认值是Autowire.NO
- initMethod:自定义初始化方法
- destroyMethod:自定义销毁方法
5.装配的混合使用
spring-data.xml->使用xml配置数据源
<context:property-placeholder location="classpath:jdbc.properties"/> <!--定义数据源:Druid数据源默认是自动提交事务,需要关闭自动提交 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="driverClassName" value="${driverClassName}"/> <property name="url" value="${url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${password}"/> <property name="connectionProperties" value="${connectionProperties}"/> <property name="defaultAutoCommit" value="${defaultAutoCommit}"/> </bean>
这种方式我们不需要去了解第三方的更多细节,也不需要过多的java代码,尤其是不用try...catch...finally...语句去处理它们,相对与@Bean的注入会更好一些,也更为简单,所以对于第三方的包或者其它外部的接口,建议使用xml的方式。
spring同时支持这两种形式的装配,可以自由选择,无论是xml还是注解都是将bean装配到Spring IoC容器中,这样就可以通过spring IoC容器去管理各类资源了,首先使用@ImportResource,引入spring-data.xml所定义的内容。
@Configuration @ComponentScan(basePackages = "com.wise.tiger") @ImportResource(locations = {"classpath:spring-data.xml"}) public class ApplicationConfig { }
6.使用Profile
在软件开发过程中,敏捷开发模式很常见,一种时间控制的迭代式实现和发布软件的方法。那么可能是开发人员使用一套环境,而测试人员使用另外一套环境,而这两套系统的数据库是不一样的,毕竟测试人员也需要花费很多时间去构建测试数据,可不想老是被开发人员修改那些测试数据,这样就有了在不同环境中进行切换的需求了。spring也会对这样的场景进行支持。
6.1.使用@Profile配置
@Bean(name = "devDataSource") @Profile("dev") public DataSource getDataSource() { var dataSource = new BasicDataSource(); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setDriverClassName(driverClassName); dataSource.setPassword(password); dataSource.setMaxTotal(maxTotal); dataSource.setMaxIdle(maxIdle); dataSource.setMaxWaitMillis(maxWaitMillis); dataSource.setConnectionProperties(connectionProperties); dataSource.setAutoCommitOnReturn(defaultAutoCommit); return dataSource; } @Bean(name = "testDataSource") @Profile("test") public DataSource getDataSource1( @Value("${driverClassName}") String driverClassName, @Value("jdbc:mysql:///test") String url, @Value("${username}") String username, @Value("${password}") String password) { var dataSource = new BasicDataSource(); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setDriverClassName(driverClassName); dataSource.setPassword(password); return dataSource; }
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="..."> <!-- other bean definitions --> <beans profile="development"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/> </jdbc:embedded-database> </beans> <beans profile="production"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> </beans>
当启动java配置或者xml配置profile时,Bean并不会被加载到IoC容器中。需要自行激活Profile。激活方法有5种
- 在使用SpringMVC的情况下可以配置Web上下文参数,或者DispatchServlet参数
- 作为JNDI条目
- 配置环境变量
- 配置JVM启动参数
- 在集成测试环境中使用@ActiveProfiles
常用激活:
在测试代码中激活Profile,如果是开发人员进行测试,那么可以使用注解@ActiveProfiles进行定义
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:spring-profile.xml") @ActiveProfiles("dev") public class TestActiveProfile { }
在测试代码中可以加入@ActiveProfiles来指定加载哪个Profile,这样程序就会自己去加载对应的profile了。但是毕竟不是什么时候都是在测试代码中运行,有些时候要在服务器上运行,那么这个时候可以配置java虚拟机的启动项,关于指定profile的参数存在两个。
spring.profiles.active 启动的 spring.profiles.default 默认的
<!-- 配置spring的默认profile --> <context-param> <param-name>spring.profiles.default</param-name> <param-value>test</param-value> </context-param>
7. 加载属性(properties)文件
在开发过程中,配置文件往往就是那些属性(properties)文件,比如:
driverClassName = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/db_book
username = peppa
password = pedro
########## dbcp连接池基本属性 #############
# 初始化连接
initialSize=20
#最大连接数量,设 0 为没有限制
maxTotal = 0
#最大空闲连接
maxIdle = 10
#最小空闲连接
minIdle = 3
#超时等待时间以毫秒为单位
maxWaitMillis = 1000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties = serverTimezone=UTC;useSSL=false;useUnicode=true;characterEncoding=utf-8
#指定由连接池所创建的连接的自动提交(auto-commit)状态。如果指定为false表示关闭自动提交
defaultAutoCommit = false
#driver default 指定由连接池所创建的连接的只读(read-only)状态。默认false
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
#defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
#defaultTransactionIsolation = REPEATABLE_READ
7.1.使用注解方式加载属性文件
Spring提供了@PropertySource来加载属性文件
-
name:字符串,属性配置的名称
-
value:字符串数组,可以配置多个属性文件
-
ignoreResourceNotFound:boolean值,默认值为false:如果属性文件没有找到是否忽略处理。
-
encoding:编码
@Configuration @ComponentScan(basePackages = "com.wise.tiger") @PropertySource(value = "classpath:dbcp-config.properties",ignoreResourceNotFound = true,encoding = "UTF-8") public class ApplicationConfig {}
@Configuration @ComponentScan(basePackages = "com.wise.tiger") @PropertySource(value = "classpath:dbcp-config.properties",ignoreResourceNotFound = true,encoding = "UTF-8") public class ApplicationConfig { @Value("${url}") private String url; @Value("${username}") private String username; @Value("${password}") private String password; @Value("${driverClassName}") private String driverClassName; @Value("${maxTotal}") private int maxTotal; @Value("${maxWaitMillis}") private long maxWaitMillis; @Value("${maxIdle}") private int maxIdle; @Value("${defaultAutoCommit}") private boolean defaultAutoCommit; @Value("${connectionProperties}") private String connectionProperties; @Bean(name = "dataSource") public DataSource getDataSource() { var dataSource = new BasicDataSource(); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setDriverClassName(driverClassName); dataSource.setPassword(password); dataSource.setMaxTotal(maxTotal); dataSource.setMaxIdle(maxIdle); dataSource.setMaxWaitMillis(maxWaitMillis); dataSource.setConnectionProperties(connectionProperties); dataSource.setAutoCommitOnReturn(defaultAutoCommit); return dataSource; } }
7.2.使用xml方式加载属性文件
<context:property-placeholder location="classpath:dbcp-config.properties" ignore-resource-not-fount="true"/>
location是一个配置文件路径的选项,可以配置多个或者单个文件,多个文件之间用,分割。如果系统中存在很多文件,那么属性location就要配置长长的字符串了,不过还有其它的xml方式可以进行配置:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <!-- 字符串数组,可以配置多个属性文件--> <property name="locations"> <array> <value>classpath:jdbc.properties</value> <value>classpath:message.properties</value> </array> </property> <property name="ignoreResourceNotFount" value="true"/> </bean>
在某些条件下不需要去装配Bean,比如当属性文件中没有数据源的基础配置的时候就不要去创建数据源。这时就需要通过条件化去判断。Spring提供了@Conditional去配置,通过配置一个或多个类,只是这些类需要实现接口Condition。
@Bean(name = "dataSource") @Conditional(DataSourceCondition.class) public DataSource getDataSource( @Value("${driverClassName}") String driverClassName, @Value("${url}") String url, @Value("${username}") String username, @Value("${password}") String password) { var dataSource = new BasicDataSource(); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setDriverClassName(driverClassName); dataSource.setPassword(password); return dataSource; }
package com.wise.tiger; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; /** * 条件判断,需要实现Condition接口 */ public class DataSourceCondition implements Condition { /** * 判断属性文件中是否配置了数据源的相关参数 * @param context:通过它可以获取spring的运行环境 * @param metadata:通过它可以获得关于该Bean的注解信息 * @return true:创建对应的Bean,false:不会创建 */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //获取Spring的运行环境 var env = context.getEnvironment(); //判断是否存在关于数据源的基础配置 return env.containsProperty("driverClassName") && env.containsProperty("url") && env.containsProperty("username") && env.containsProperty("password"); } }
9 Bean的作用域
在默认情况下,Spring IoC容器只会对一个Bean创建一个实例。bean可以定义为部署在多个作用域中的一个作用域中。Spring框架支持六个作用域,其中四个作用域仅在使用Web感知的ApplicationContext时可用。还可以创建自定义范围。
singleton | 单例,默认选项,在整个应用中,spring只为其生成一个Bean的实例 |
prototype | 原型,当每次注入或者通过IoC容器获取Bean时,Spring都会为它创建一个新的实例 |
request | 请求,web应用中使用,一次请求创建一个Bean的实例,不同的请求会创建不同的实例 |
session | 会话,web应用中使用,会话过程中创建一个Bean的实例 |
application | servletContext,web应用中使用,单个bean定义范围扩展到servletContext的生命周期 |
webscoket | servletContext,web应用中使用,单个bean定义范围扩展到websocket的生命周期 |
可以采用@Scope注解声明Bean的作用域
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
10 使用SPring表达式(Spring EL)
Spring还提供了更灵活的注入方式,那就是Spring表达式,Spring表达式语言(简称spel)是一种功能强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于统一的EL,但提供了其他特性,最显著的是方法调用和基本的字符串模板功能:
- 使用Bean的id来引用Bean
- 调用指定对象的方法和访问对象的属性
- 进行运算
- 提供正则表达式进行匹配
- 集合配置
10.1 Spring EL相关类
ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'"); String message = (String) exp.getValue();//The value of the message variable is 'Hello World'. exp = parser.parseExpression("'Hello World'.concat('!')"); message = (String) exp.getValue(); //The value of message is now 'Hello World!'.
// Create and set a calendar GregorianCalendar c = new GregorianCalendar(); c.set(1856, 7, 9); // The constructor arguments are name, birthday, and nationality. Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("name"); String name = (String) exp.getValue(tesla); // name == "Nikola Tesla" exp = parser.parseExpression("name == 'Nikola Tesla'"); boolean result = exp.getValue(tesla, Boolean.class); // result == true
10.2 Bean的属性和方法
使用注解的方式需要用到@Value,在属性文件的读取中使用的是$,而在spring el中则使用#。
@Component public class ELBean { @Value("#{person.id}") private Long id; @Value("#{'peppa'}") private String name; @Value("#{person}") private Person person; //setters and getters }
@Value("#{T(Math).PI}") private double pi; @Value("#{T(Math).random() * 100}") private int random; @Value("#{person.getName()?.toString()}") private String note; @Value("#{person.getName()?:'peppa'}") private String defaultnote;