spring bean overview
spring Ioc容器管理bean,先看下一个通用的bean xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 自动扫描web包 ,将带有注解的类 纳入spring容器管理 --></span>
<context:component-scan base-package="com.eduoinfo.finances.bank.web"></context:component-scan>
<!-- 引入jdbc配置文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:jdbc.properties</value>
</list>
</property>
</bean>
<!-- dataSource 配置 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="20" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="false" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
</bean>
<!-- mybatis文件配置,扫描所有mapper文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="classpath:mybatis-config.xml" p:mapperLocations="classpath:com/eduoinfo/finances/bank/web/dao/*.xml" />
<!-- spring与mybatis整合配置,扫描所有dao --></span>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:basePackage="com.eduoinfo.finances.bank.web.dao" p:sqlSessionFactoryBeanName="sqlSessionFactory" />
<!-- 对dataSource 数据源进行事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource" />
<!-- 配置使Spring采用CGLIB代理 --></span>
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- 启用对事务注解的支持 --></span>
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Cache配置 --></span>
<cache:annotation-driven cache-manager="cacheManager" />
<bean id="ehCacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:configLocation="classpath:ehcache.xml" />
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cacheManager-ref="ehCacheManagerFactory" />
</beans>
一个基本的bean定义包含如下元素
class、name、scope、constructor arguments、properties、autowiring mode、lazy-initialization mode、initialization method、destruction method。
对于xml配置文件,通过id/name来唯一指定一个bean,如果不指定id/name,spring容器会自动生成一个唯一的name,通过ref标签可以引用指定名称的bean,alias标签可以指定bean的别名,可以为一个bean指定多个别名
<alias name="fromName" alias="toName"/>
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
实例化bean,通常有如下几种实例化bean的配置
- 通过constructor(默认)
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
- 通过static factory method,方法必须是static
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
- 通过instance factory method,method所在的方法也是一个bean,这个才能调用方法实例化,通过factory-bean指定工厂bean
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
依赖注入Dependency injection (DI)是一个对象定义其依赖关系的过程。也叫控制反转Inversion of Control (IoC),当实例化一个bean的时候,spring容器会将它们的依赖关系注入进来,这就是控制反转的意义。
依赖注入的几种方式
- 构造方法注入
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
spring配置文件如下
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
对于常用的数据类型需要指定index或者type,这样spring 容器才能识别,eg
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;
}
}
spring配置文件如下
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
或者
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
也可以通过指定构造方法的参数名来传入参数,eg
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
- setter方法注入
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
关于构造方法与setter方法两种注入,你都可以使用,也可以混合使用。
setter方法注入可以解决循环依赖的问题
spring依赖解析过程
- 根据配置文件创建
ApplicationContext
- 通过构造方法或者工厂方法实例化bean,当bean被需要创建的时候。
- 注入bean的依赖
单例模式的bean在容器实例化的时候就被创建,bean属性设置为pre-instantiated也会在容器被创建的时候创建,其他的bean只有在被需要的时候创建。
关于依赖配置的一些细节
可以通过<property/>标签指定bean的成员变量,eg
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
或者通过p-namespace指定bean成员变量,eg
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
</beans>
或者通过java.util.Properties
指定成员变量,eg
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
idref标签可以在开发时校验bean是否存在,eg
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
等价于
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
spring4.0通过idref local替代idref
与ref标签等价的有local、parent标签,parent标签用于指定依赖于parent context的bean,eg
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
p-namespace标签可以替代<property/>元素,eg
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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 name="classic" class="com.example.ExampleBean">
<property name="email" value="foo@bar.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="foo@bar.com"/>
</beans>
c-namespace标签可以替代constructor-arg元素,eg
<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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
<!-- traditional declaration -->
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
<constructor-arg value="foo@bar.com"/>
</bean>
<!-- c-namespace declaration -->
<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
</beans>
或者
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
lazy-init可以指定单例模式的bean不需要再容器创建的时候初始化,eg
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
指定所有bean延迟加载
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
自动装配Autowiring
自动装配有4种模式
mode | 说明 |
no | (Default) No autowiring. Bean references must be defined via a ref element. Changing the default setting is not recommended for larger deployments, because specifying collaborators explicitly gives greater control and clarity. To some extent, it documents the structure of a system. |
byName | Autowiring by property name. Spring looks for a bean with the same name as the property that needs to be autowired. For example, if a bean definition is set to autowire by name, and it contains a masterproperty (that is, it has a setMaster(..) method), Spring looks for a bean definition named master , and uses it to set the property. |
byType | Allows a property to be autowired if exactly one bean of the property type exists in the container. If more than one exists, a fatal exception is thrown, which indicates that you may not use byType autowiring for that bean. If there are no matching beans, nothing happens; the property is not set. |
constructor | Analogous to byType, but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised. |
spring Method injection
有时我们需要在一个bean A中调用另一个bean B的方法,通常我们会添加一个字段,然后使用依赖注入把bean B的实例注入到这个字段上。这种情况下在bean A 和 bean B都是singleton时没问题,但是在 bean A是singleton和bean B是非singleton时就可能出现问题。因为bean B为非singleton , 那么bean B是希望他的使用者在一些情况下创建一个新实例,而bean A使用字段把bean B的一个实例缓存了下来,每次都使用的是同一个实例。
我们假设bean B是一个prototype
一种解决办法是不使用字段依赖注入,每次使用bean B的时候都去bean容器中重新获取,eg
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Lookup method injection
Spring提供了一个Lookup method inject机制,它可以改变方法的返回值,来达到方法注入的效果。对应的有annotation和xml两种使用方式。
spring的Lookuo method injection实现原理是通过CGLib库动态生成字节码去继承需要返回的实例。
annotation的使用方式@Lookup,把@Lookup加到你要改变方法返回值的方法上,eg
public abstract class CommandManager{
public Object process(Map commandState) {
// 每次使用都调用createCommand去获取一个新实例
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
/**
* Loopup的注释中的写明了需要返回的bean名字,如果没有写bean name,那么会根据createCommand的函数返回值类型去查找对应的bean
* @return
*/
@Lookup("command")
protected abstract Command createCommand();
}
Spring的Lookup method inject实现原理的是使用CGLIB动态生成一个类去继承CommandManager
,重写createCommand
方法。然后根据@Lookup中指定的bean Name或者createCommand
方法的返回类型判断需要返回的bean。createCommand
可以是abstract和可以不是。因为使用的是继承,所以CommandManager
类和createCommand
方法都不能是final的。
createCommand
方法的签名需要满足如下要求
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
对应实现的XML配置
<bean id="command" class="AsyncCommand" scope="prototype">
</bean>
<bean id="commandManager" class="CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>
Arbitrary method replacement
Lookup method inject只是改变了方法的返回值,但是method replacement可以替换bean 容器里任意方法的实现,达到方法的完全注入,一般情况下不要这个使用特性!
此特性,只能基于XML配置实现。假如我们要替换如下类的computeValue
方法
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
第一步,我们要现实org.springframework.beans.factory.support.MethodReplacer接口
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
第二步,在XML中,使用replaced-method元素进行配置.
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
在上面的xml中,在元素replaced-method中使用了arg-type。它的作用是在有多个方法重载时,根据arg-type中指定的参数class名字来确定具体替换哪一个方法。arg-type中的值可以是类全路径的一个子串,如下面所有的值都可以匹配java.lang.String
java.lang.String
String
Str
关于spring bean的一些细节先先到这边。