@zhuanxu
2017-11-09T09:53:41.000000Z
字数 7301
阅读 2246
JavaEE开发的颠覆者SpringBoot实战
spring中声明Bean的属性和构造函数参数有两种方法:
<property/>元素<constructor-arg/>元素另外在声明具体的值上,我们可以是 Straight values(primitives, Strings),也可以使idref元素,或者是对其他bean的指向,下面分别举例子:
<property/>元素的value属性可以直接是一个字符串,然后通过 Spring 的conversion service 进行转换,看例子:
<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>
另外除了能将String转换原始类型,还可以转换 properties 值,看下面的例子的:
<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.Driverjdbc.url=jdbc:mysql://localhost:3306/mydb</value></property></bean>
value中的值会转化为java.util.Properties。
看使用方式:
<bean id="theTargetBean" class="..."/><bean id="theClientBean" class="..."><property name="targetName"><idref bean="theTargetBean"/></property></bean>
这种通过<idref/>指定的方式,可以方便IoC容器在部署的时候就去检查所依赖的Bean是否存在的,上面的方式和下面的声明是一样的功能,缺点就是不能检查value值是否存在,只能在运行时看的。
<bean id="theTargetBean" class="..." /><bean id="client" class="..."><property name="targetName" value="theTargetBean"/></bean>
ref vs idref 区别是啥呢?
idref 只是一个字符串,而 ref 则是注入对象,看示例:
<bean id="a" class="com.wisely.ref.A"/><bean id="b" class="com.wisely.ref.B"/><bean id="hello" class="com.wisely.ref.Hello"><constructor-arg ref="a"/><constructor-arg><idref bean="b"/></constructor-arg><property name="name" value="Hello" /><property name="age" value="12"/></bean>
此处构造函数一个是 ref,一个是 idref,再看代码:
public class Hello {private String name;private int age;private String b;private A a;public Hello(A a, String b) {this.a = a;this.b = b;}}
此处b传入的就只是一个字符串"b"。
我们可以在<property/>,<constructor-arg/>中通过<bean/>标签来创建内部的bean,看例子:
<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/>, <props/>,分别代表:List, Set, Map, Properties。来看一个具体的例子:
<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>
我们可以在xml中通过""和null来表示空字符串和null值。
<bean class="ExampleBean"><property name="email" value=""/></bean><bean class="ExampleBean"><property name="email"><null/></property></bean>
IoC对于单例的Beans默认是在创建ApplicationContext中的过程中就初始化了,有时候我们希望只有当请求这个Bean的时候才去初始化,这个时候可以在xml中通过配置lazy-init="true"来实现,如下:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/><bean name="not.lazy" class="com.foo.AnotherBean"/>
但是如果依赖于这个lazy Bean的其他Bean是需要在ApplicationContext中初始化的,则即使设置了lazy-init,仍会在ApplicationContext创建的时候初始化。
另一种设置所有beans延迟加载的方式如下:
<beans default-lazy-init="true"><!-- no beans will be pre-instantiated... --></beans>
自动转配(Autowiring collaborators)机制能有效的减少xml的配置,ApplicationContext会自动帮你将依赖注入。
Autowiring 的模式有4种:
| 模式 | 说明 |
|---|---|
| no | 默认不进行自动装配,这样在大型项目中能更好的对项目进行控制 |
| byName | 属性名字和Bean名字一致 |
| byType | 按属性类型装配,如果存在多个统一类型的Bean,则报错 |
| constructor | 和 byType 一致 |
一般 IoC 容器中管理的Bean都是单例的,如果我们有一个单例的Bean的属性是一个非单例的Bean,那会存在单例Bean只会创建一次,意味着属性也只会注入一次,但是我们希望每次这个属性Bean都是新的,这个时候怎么办呢?
一个做法是实现ApplicationContextAware接口,我们主动通过getBean方法去获取Bean,代码如下:
// a class that uses a stateful Command-style class to perform some processingpackage fiona.apple;// Spring-API importsimport 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 CommandCommand command = createCommand();// set the state on the (hopefully brand new) Command instancecommand.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;}}
上面代码的问题就是侵入式,所有就有了下面的method injection技术,直接上代码:
package fiona.apple;// no more Spring imports!public abstract class CommandManager {public Object process(Object commandState) {// grab a new instance of the appropriate Command interfaceCommand command = createCommand();// set the state on the (hopefully brand new) Command instancecommand.setState(commandState);return command.execute();}// okay... but where is the implementation of this method?protected abstract Command createCommand();}
配置:
<!-- a stateful bean deployed as a prototype (non-singleton) --><bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"><!-- inject dependencies here as required --></bean><!-- commandProcessor uses statefulCommandHelper --><bean id="commandManager" class="fiona.apple.CommandManager"><lookup-method name="createCommand" bean="myCommand"/></bean>
上面的技术是通过CGLIB来做的,简单说就是动态创建了一个proxy来覆写方法。
目前spring常用的scope有6个,分别介绍下,下面是第一个:singleton scope。
在IoC容器中只保存一个Bean
此处和GoF提的单例模式的区别是:单例模式是一个ClassLoader都只有一个,而此处是每个IoC容器一个Bean。
注意:默认 scope 是 singleton scope
使用 singleton scope 还是 prototype scope 原则是:有状态的bean使用 prototype,无状态的使用 singleton。

每次请求bean都是生成一个新的bean,这就意味着如果一个 singleton scope 的如果依赖于一个 prototype scope 的bean,那这个 prototype 只会生成一次,因此需要用到之前的method injection技术。
这些 scope 只能在 web 应用中使用,IoC容器例如:XmlWebApplicationContext,当不在这种容器中的时候,会抛出异常。
request scope
request scope 定义的bean在每次http请求的时候都会重新创建。
session scope
request scope定义的bean在每个 http session 请求的时候都会重新创建。 ApplicationContext`一个。
**application scope**
作为 ServletContext 的属性存在,类似于 singleton scope,不同在于 singleton scope 是每个
我们不应该将生命周期短的bean注入到生命周期长的bean中,看配置:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/><bean id="userManager" class="com.foo.UserManager"><property name="userPreferences" ref="userPreferences"/></bean>
此时 userManager 是长时间存在的,一旦去访问 userPreferences 的,此时 userPreferences 会销毁掉,会出问题,那解决办法就是通过代理的方式。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"><aop:scoped-proxy/></bean><bean id="userManager" class="com.foo.UserManager"><property name="userPreferences" ref="userPreferences"/></bean>
在整个spring中控制Bean生命周期的方法有:
InitializingBean 和 DisposableBean方法init() 和 destroy()方法@PostConstruct 和 @PreDestroy 注释然后如果这些都存在的话,其执行的一个顺序是:
@PostConstruct的方法afterPropertiesSet()init方法 @PreDestroy的方法DisposableBean的destroy()方法 destroy()方法下一篇将会详细如何使用java注释来代替xml的方式。