示例中所用的环境
intellij IDEA
jdk 1.8
maven 3.3.9
Spring 4.1.7
junit 4.11
装配
创建应用组件之间协作的行为通常称为装配(wiring)。Spring有多种装配bean的方式,采用XML是很常见的一种装配方式。
<?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">
<!--创建courses bean-->
<bean id="courses" class="com.angeilz.di.courses.ChineseCourse"/>
<bean class="com.angeilz.di.courses.Student" id="student">
<!--通过构造器注入courses bean-->
<constructor-arg name="courses" ref="courses"/>
</bean>
</beans>
观察它如何工作
Spring通过应用上下文(Application Context)装载bean的定义并把它们组装起来。Spring应用上下文全权负责对象的创建和组装。Spring自带了多种应用上下文的实现,它们之间主要的区别仅仅在于如何加载配置。下面例子使用ClassPathXmlApplicationContext类,该类加载位于应用程序类路径下的一个或多个XML配置文件。
public static void main(String[] args) {
//加载Spring应用上下文
ClassPathXmlApplicationContext context=
new ClassPathXmlApplicationContext("spring-courses.xml");
//获取Student bean
Student student=context.getBean(Student.class);
student.study();
}
注意main方法中完全不知道学生学习的是哪门课程,而且完全没有意识到这是由ChineseCourse来执行的。只有courses.xml文件知道学生该学习哪门课程。
容器
在基于Spring的应用中,你的应用对象生存于Spring容器(container)中。如图下图所示,Spring容器负责创建对象,装配它们,配置它们并管中它们的整个生命周期,从生存到死亡(在这里,可能就是new到finalize())。
容器是Spring框架的核心。Spring容器使用DI管理构成应用的组件,它会创建相互协作的组件之间的关联。毫无疑问,这些对象更简单干净,更易于理解,更易于重用并且更易于进行单元测试。
Spring自带了多个容器实现,可以归为两种不同的类型。
bean工厂(由org.springframework. beans.factoryeanFactory接口定义)是最简单的容器,提供基本的DI支持。
应用上下文(由org.springframework.context.ApplicationContext接口定义)基于BeanFactory构建,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布应用事件给感兴趣的事件监听者。
bean工厂对大多数应用来说往往太低级了,因此,应用上下文要比bean工厂更受欢迎。
应用上下文
AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文。
AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文。
ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文定义。
XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。
示例:
ClassPathXmlApplicationContext classPathContext=
new ClassPathXmlApplicationContext("spring-courses.xml");
FileSystemXmlApplicationContext fileContext=
new FileSystemXmlApplicationContext("d:/spring-courses.xml");
AnnotationConfigApplicationContext configContext=
new AnnotationConfigApplicationContext(CoursesConfig.class);
Spring bean的生命周期
在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。
相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。下图展示了bean装载到Spring应用上下文中的一个典型的生命周期过程。
在bean准备就绪之前,bean工厂执行了若干启动步骤:
1.Spring对bean进行实例化;
2.Spring将值和bean的引用注入到bean对应的属性中;
3.如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;
4.如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
5.如果bean实现了ApplicationContextAware接口,Spring将调用
setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
6.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;
7.如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用;
8.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;
9.此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
10.如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。
Spring配置的可选方案
在XML中进行显式配置。
在Java中进行显式配置。
隐式的bean发现机制和自动装配。
显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
自动装配
Spring从两个角度来实现自动化装配:
组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
自动装配(autowiring):Spring自动满足bean之间的依赖。
下面展示基于java的配置:
@Configuration
@ComponentScan(basePackages = {"com.angeilz.di.phonesystem"})
public class PhoneConfig {}
iPhone类实现Phone接口
@Component
public class Iphone implements Phone
{
@Autowired
private Screen screen;
@Override
public void lockScreen() {
System.out.print ("this is iphone ");
screen.interrupt();
}
}
IpsScreen类实现Screen接口
@Component
public class IpsScreen implements Screen {
@Override
public void interrupt() {
System.out.println("IPS screen interrupt...");
}
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = PhoneConfig.class)
public class PhoneTest {
@Autowired
Phone phone;
@Test
public void phoneLocakScreen() {
phone.lockScreen();
}
}
运行结果:
this is iphone IPS screen interrupt...
注解详解
@Component
表明该类会作为组件类,并告知Spring要为这个类创建bean。value 参数为该类的ID,默认值为类名首字母小写。@Named可作为@Component注解的替代方案;
@Autowired
声明为自动装配,Spring会自动满足bean依赖,可用在成员变量、构造器或是任何方法上,不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖;required属性默认为true,当Spring没找到匹配的bean,会抛出异常;required属性设为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态;@Inject可作为@Autowired的替代方案;
@Configuration
表明该类为配置类,该类包含在Spring应用上下文中如何创建bean的细节;
@ComponentScan
告知Spring启动组件扫描,没指定值时默认扫描与配置类相同的包,可选值:basePackages(扫描指定的包,复数形式)、basePackageClasses(扫描指定类所在的包,复数形式);
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"
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.angeilz.di.phonesystem"/>
</beans>
@bean
会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑;默认情况下,bean的ID与带有@Bean注解的方法名是一样的,name属性可谓该bean指定一个不同的ID;
java配置::
public class PhoneConfig {
@Bean(name = "samsung")
public Phone Samsung(Screen OLEDScreen) {
Samsung samsung=new Samsung("Galaxy S9");
samsung.setScreen(OLEDScreen);
return samsung;
}
@Bean(name = "OLEDScreen")
public Screen oledScreen() {
return new OledScreen();
}
}
也可用xml配置实现:
<?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">
<bean class="com.angeilz.di.phonesystem.Samsung">
<!--构造器注入-->
<constructor-arg name="model" value="Galaxy S9"/>
<!--属性注入-->
<property name="screen" ref="oledScreen"/>
</bean>
<bean id="oledScreen" class="com.angeilz.di.phonesystem.OledScreen"/>
</beans>
Samsung类实现Phone接口:
public class Samsung implements Phone {
private Screen screen;
private String model;
public Samsung(String model) {
this.model = model;
}
public void setScreen(Screen screen) {
this.screen = screen;
}
@Override
public void lockScreen() {
System.out.print ("this is samsung "+model);
screen.interrupt();
}
}
OledScreen类实现Screen接口
public class OledScreen implements Screen {
@Override
public void interrupt() {
System.out.println("OLED Screen interrupt...");
}
}
@Import & @ImportResource
当配置类变得臃肿的时候,可使用@Import该注解导入其他配置类;
@ImportResource 导入XML配置文件;
java配置如下:
@Configuration
@ComponentScan(basePackages = {"com.angeilz.di.phonesystem"})
@Import(RootConfig.class)
@ImportResource("classpath:spring-context.xml")
public class PhoneConfig {}
xml配置实现相同效果:
<?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">
<!--加载配置类-->
<bean class="com.angeilz.config.PhoneConfig"/>
<!--加载其他配置文件-->
<import resource="spring-context.xml"/>
</beans>
@Profile
指定配置的bean属于哪一个profile,例如:
@Configuration
@Profile("dev")
public class DevPhoneConfig {
}
@Configuration
@Profile("prod")
public class ProdPhoneConfig {
}
这两个配置类分别对应dev环境和prod环境,当dev被激活的时候,DevPhoneConfig配置类会生效。
xml配置如下:
<?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"
profile="dev">
</beans>
@Profile除了应用在配置类上还可以在方法级别上使用@Profile注解,与@Bean注解一同使用;
@Configuration
@ComponentScan(basePackages = {"com.angeilz.di.phonesystem"})
public class PhoneConfig {
@Bean(name = "OLEDScreen")
@Profile("dev")
public Screen oledScreen() {
return new OledScreen();
}
@Bean(name = "ISPScreen")
@Profile("prod")
public Screen IspScreen() {
return new IpsScreen();
}
}
xml配置实现如下:
<?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 profile="dev">
<bean id="oledScreen" class="com.angeilz.di.phonesystem.OledScreen"/>
</beans>
<beans profile="prod">
<bean id="ipsScreen" class="com.angeilz.di.phonesystem.IpsScreen"/>
</beans>
</beans>
在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态;Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。但如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。如果spring.profiles.active和spring.profiles.default均没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在profile中的bean;
有多种方式来设置这两个属性:
1、 作为DispatcherServlet的初始化参数;
2.、作为Web应用的上下文参数;
3、 作为JNDI条目;
4、作为环境变量;
5、作为JVM的系统属性;
6、在集成测试类上,使用@ActiveProfiles注解设置
web.xml中设置默认的profile:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-context.xml</param-value>
</context-param>
<!--为上下文设置默认的profile-->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--为servlet设置默认的profile-->
<init-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
集成测试中激活profile:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = PhoneConfig.class)
@ActiveProfiles("dev")
public class PhoneTest {}
@Qualifier
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean;
看下面例子:
@Component
public class IpsScreen implements Screen {}
@Component
public class OledScreen implements Screen {}
@Component()
public class TftScreen implements Screen {}
@Component
public class Iphone implements Phone
{
@Autowired
private Screen screen;
@Override
public void lockScreen() {
System.out.print ("this is iphone ");
screen.interrupt();
}
}
Screen的三个实现均使用了@Component注解,在组件扫描的时候,能够发现它们并将其创建为Spring应用上下文里面的bean。然后,当Spring试图自动装配Iphone类中Screen变量时,它并没有唯一、无歧义的可选值。Spring此时别无他法,只好宣告失败并抛出异常。更精确地讲,Spring会抛出NoUniqueBeanDefinitionException;
通过@Qualifier能解决这种歧义性:
@Autowired
@Qualifier("ipsScreen")
private Screen screen;
Qualifier中的vaue属性指定要装配bean的ID;这样Spring就明确知道该装配哪个bean,即Screen的实现;
@Primary
该注解也是用来解决自动装配的歧义性问题,配合@Component一起使用,标识首选的bean;
@Component
@Primary
public class IpsScreen implements Screen {}
也可以配合@bean在方法上标识:
@Bean
@Primary
public Screen oledScreen() {
return new OledScreen();
}
xml中相同的实现如下:
<bean id="oledScreen" class="com.angeilz.di.phonesystem.OledScreen" primary="true"/>
@scope
限定bean的作用域;在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的;在大多数情况下,单例bean是很理想的方案。初始化和垃圾回收对象实例所带来的成本只留给一些小规模任务,在这些任务中,让对象保持无状态并且在应用中反复重用这些对象可能并不合理。
pring定义了多种作用域,可以基于这些作用域创建bean,包括:
单例(Singleton):在整个应用中,只创建bean的一个实例。
原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
会话(Session):在Web应用中,为每个会话创建一个bean实例。
请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
如果选择其他的作用域,要使用@Scope注解,它可以与@Component或@Bean一起使用。
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class IpsScreen implements Screen {}
这里,使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了原型作用域。你当然也可以使用 @Scope(“prototype”),但是使用SCOPE_PROTOTYPE常量更加安全并且不易出错。
或是像下面这么配置:
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Screen IspScreen() {
return new IpsScreen();
}
xml中可以这样配置:
<bean id="oledScreen" class="com.angeilz.di.phonesystem.OledScreen" scope="prototype"/>
会话和请求作用域:
在Web应用中,如果能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事情。例如,在典型的电子商务应用中,可能会有一个bean代表用户的购物车。如果购物车是单例的话,那么将会导致所有的用户都会向同一个购物车中添加商品。另一方面,如果购物车是原型作用域的,那么在应用中某一个地方往购物车中添加商品,在应用的另外一个地方可能就不可用了,因为在这里注入的是另外一个原型作用域的购物车。就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会话作用域,我们可以使用@Scope注解,它的使用方式与指定原型作用域是相同的:
//定义购物车
@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.INTERFACES)
public ShoppingCart cart() {...}
@Scope同时还有一个proxyMode属性,它被设置成了ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。在描述proxyMode属性之前,我们先来看一下proxyMode所解决问题的场景:
@Component
public class StoreService
private ShoppingCart shoppingCart;
//注入购物车依赖
@Autowired
public StoreService(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
}
因为StoreService是一个单例的bean,ShoppingCart是会话作用域的bean,所以当StoreService被创建时,ShoppingCart并不存在。直到某个用户进入系统,创建了会话之后,才会出现ShoppingCart实例。
另外,系统中将会有多个ShoppingCart实例:每个用户一个。我们并不想让Spring注入某个固定的ShoppingCart实例到StoreService中。我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例恰好是当前会话所对应的那一个。
Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理,如图下图所示。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。
如果ShoppingCart是接口而不是类的话,这是可以的(也是最为理想的代理模式)。但如果ShoppingCart是一个具体的类的话,Spring就没有办法创建基于接口的代理了。此时,它必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话,我们必须要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。
尽管我主要关注了会话作用域,但是请求作用域的bean会面临相同的装配问题。因此,请求作用域的bean应该也以作用域代理的方式进行注入。
xml中声明作用域的代理:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="com.angeilz.di.shopingsystem.ShoppingCart" scope="session">
<!--启用作用域代理 并且要求生成基于接口的代理-->
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
</beans>
@PropertySource
用来声明属性源,配合Environment一起使用
@Configuration
@PropertySource("classpath:user.properity")
public class UserConfig {
@Autowired
Environment env;
@Bean
public User user(){
return new User(env.getProperty("user.name"),env.getProperty("user.password"));
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context=
new AnnotationConfigApplicationContext(UserConfig.class);
System.out.println(context.getBean(User.class));
}
}
User类:
public class User {
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
@Override
public String toString() {
return "User{name='"+name+"', password='" + password+"'}";
}
}
user.properity
user.name=zhangsan
user.password=123456
运行结果:
User{name='zhangsan', password='123456'}
深入学习Environment
这个类定义了如下方法:
//是否包含指定的属性
boolean containsProperty(String key);
//根据key查找对应的值,当key未定义时返回null
String getProperty(String key);
//当key未定义时返回默认值
String getProperty(String key, String defaultValue);
//将获取的值转换为指定的类型
<T> T getProperty(String key, Class<T> type);
//同上
<T> T getProperty(String key, Class<T> type, T defaultValue);
//将属性解析为类
<T> Class<T> getPropertyAsClass(String key, Class<T> type);
//获取的属性必须要定义,当key未定义将抛异常
String getRequiredProperty(String key);
<T> T getRequiredProperty(String key, Class<T> var2);
String resolvePlaceholders(String var1);
String resolveRequiredPlaceholders(String var1);
//返回激活profile名称的数组;
String[] getActiveProfiles();
//返回默认profile名称的数组;
String[] getDefaultProfiles();
@Value
如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了。在这种情况下,我们可以使用@Value注解,它的使用方式与@Autowired注解非常相似。
@Configuration
@PropertySource("classpath:user.properity")
@ComponentScan
public class UserConfig {
/* @Autowired
Environment env;*/
@Bean
public User user(@Value("${user.username}") String name,
@Value("${user.password}") String password) {
return new User(name, password);
}
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(UserConfig.class);
System.out.println(context.getBean(User.class));
}
}
为了使用占位符,我们必须要配置一个PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。从Spring3.1开始,推荐使用PropertySourcesPlaceholderConfigurer。
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"
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">
<!--生成PropertySourcesPlaceholderConfigurer bean-->
<context:property-placeholder/>
</beans>
Spring表达式
Spring 3引入了Spring表达式语言(Spring Expression Language,SpEL),它能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值。
SpEL拥有很多特性,包括:
使用bean的ID来引用bean;
调用方法和访问对象的属性;
对值进行算术、关系和逻辑运算;
正则表达式匹配;
集合操作
看下面示例:
@Bean
public User user(@Value("#{systemProperties['user.name']}") String name,
@Value("#{T(System).currentTimeMillis()}") String password) {
return new User(name, password);
}
#{systemProperties['user.name']} //加载系统文件的user属性
#{T(System).currentTimeMillis() //调用System的currentTimeMillis方法
表示字面值
#{3.1432} 表示浮点值
#{9.87E4} 科学计数的方式表示
#{'hello'} String类型的字面值
引用bean属性和方法
#{user} 表示user bean ID 用于将一个bean装配到另一个bean的属性中
#{user.username} 访问user的属性
#{user.getUsername()} 访问user的方法
#{user.username.toUpperCase()} 将user的username转换成大写
#{user.username?.toUppperCase()} 当username为空时不会调用toUpperCase()方法
在表达式中使用类型
#{T(java.long.Math).PI}
#{T(java.long.Math).random}
#{T(System).currentTimeMillis()}
SpEL运算符
算术运算 +、-、* 、/、%、^
比较运算 < 、 > 、 == 、 <= 、 >= 、 lt 、 gt 、 eq 、 le 、 ge
逻辑运算 and 、 or 、 not 、│
条件运算 ?: (ternary) 、 ?: (Elvis)
正则表达式 matches
#{T(java.long.Math).PI * circle.radius^2}
#{user.username + 'qwer'} //这里的+为连接操作符
#{counter.total == 50} //比较两个数字是否相等,返回Boolean类型
#{counter.total eq 50} //也可以使用文本类型的eq运算符
#{counter.total>50 ? 'win' : 'lose'} //三元运算符
#{user.username ? :'fuck me'} //Elvis运算符,如果username为null,fuck me则就会当做username
正则表达式
#{user.password mathces '\d{8,16}'} 匹配一个8到16位的数字
集合
#{box.songs[4].title}//访问集合中的第五个元素的title属性
#{'hello word'[3]}//返回字符串的第四个字符 即'l'
#{box.songs.?[artist eq 'jay chou']} //得到artist为jay chou的所有歌曲
#{box.songs.^[artist eq 'jay chou']} //得到第一个artist为jay chou的歌曲
#{box.songs.$[artist eq 'jay chou']} //得到最后一个artist为jay chou的歌曲
#{box.songs.![title]} //返回一个存放title的String类型的集合
#{box.songs.?[artist eq 'jay chou'].![title]}
在动态注入值到Spring bean时,SpEL是一种很便利和强大的方式。我们有时会忍不住编写很复杂的表达式。但需要注意的是,不要让你的表达式太智能。你的表达式越智能,对它的测试就越重要。SpEL毕竟只是String类型的值,可能测试起来很困难。建议尽可能让表达式保持简洁。