通过DI,对象的依赖关系将由系统中负责协调各个对象的第三方组件在创建对象的时候进行设定。对象无需自行创建或管理他们的依赖关系。
构造器注入:在构造的时候把对象作为构造参数传入
class A{ private B b; class A(B b){ this.b = b; } }
创建应用组件之间协作的行为通常称为装配(wiring)。
采用XML对A进行装配
<?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 id="a" class="com.springinaction.di.A"> <constructor-arg ref="b" /> </bean> <bean id="b" class="com.springinaction.di.B" /> </beans>
Spring通过应用上下文装载bean的定义并把它们组装起来。Spring应用上下文全权负责对象的创建和组装。
Spring主要提供了三种主要的装配机制:
在XML中进行显式配置
在Java中进行显式配置
隐式的bean发现机制和自动装配
Bean配置信息是Bean的元数据信息,它由4个方面组成:Bean的实现类、Bean的属性信息、Bean的依赖关系和Bean的行为配置。
Bean元数据信息在Spring容器中的内部对应是由一个个BeanDefinition形成的Bean注册表,Spring实现了Bean元数据信息内部表示和外部表示的解耦。Spring支持多种形式Bean的配置方式。Bean配置信息定义了Bean的实现及依赖关系,Sprign容器根据各种形式的Bean配置信息在容器内部建立Bean定义注册表,然后根据注册表加载、实例化Bean,并建立Bean和Bean的依赖关系,最后讲这些准备就绪的Bean放到Bean缓存池中,以供外层的应用程序进行调用。
自动化装配bean
Spring从两个角度来实现自动化装配
组件扫描(component scanning):Spirng会自动发现应用上下文中所创建的bean
自动装配(autowiring):Spring自动满足bean之间的依赖
import org.springframework.stereotype.Component;
// @Component会告诉Spring要为这个类创建bean
@Component
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts lubb Band";
private String artist = "The Beatles";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
//@ComponentScan会默认扫描与配置类相同的包及其子包,查找带有@Component注解的类
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
也可用Spring context的命名空间的<context:component-scan>元素启动组件扫描
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xis="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="xxx" />
</beans>
@Component默认将类名的首字母小写作为这个bean的ID;也可@Component("beanId")显式命名这个beanID。也可使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named注解来为bean设置ID。
@ComponentScan("packagename")指定要扫描的包的名称;@Component(basePackages={"packageA", "packageB"})扫描多个基础包A和B;@ComponentScan(basePackageClasses={A.class, B.class})指定包中所含的类或接口。
@Autowired实现了Spring的自动装配。而且它可以用在类的任何方法上。若没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常,为了避免这个异常,可以将@Autowired的属性设置为false,此时若没有进行null检查的话,处于未装配状态的属性可能会抛空指针异常。若有很多个bean都满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定选择哪个bean进行自动装配
@Inject也可用于自动装配,它来源于Java依赖注入规范,该规范同时也为我们定义了@Name注解。
@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节
@Bean注解表明该类要注册为Spring应用上下文中的bean,它默认注解名和方法名是一致的。@Bean(name="name")对bean的名字进行设置
基于XML的配置
采用基于Schema配置格式,文件头的声明会复杂一些。
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
</beans>
Schema在文档根节点中通过xmlns对文档中的命名空间进行声明。我们在上面的代码中定义了3个命名空间:
1.默认命名空间:没有空间名,用于Spring Bean的定义
2.xsi命名空间:这个命名空间用于为每个文档中命名空间指定相应的Schema样式文件,是标准组织定义的标准命名空间
3.aop命名空间:这个命名空间是Spring配置AOP的命名空间,是用户自定义的命名空间
命名空间的定义分为两个步骤:第一步指定命名空间的名称,第二步指定命名空间的Schema文档样式文件的位置,用空格或回车换行进行分隔
若命名空间的别名为空,则表示该命名空间为文档默认命名空间,文档中无命名空间前缀的元素都属于默认命名空间。
命名空间使用全限定名,每个组织机构在发布Schema文件后,都会为该Schema文件提供一个引用的URL地址,一般使用这个URL地址指定命名空间对应的Schema文件。命名空间名称和对应的Schema文件地址之间使用空格或回车分隔,不同的命名空间之间也使用这种分隔方法。
Bean的命名:在配置一个Bean时,需要为其指定一个id属性作为Bean的名称。id在IoC容器中必须是唯一的,此外id的命名需要满足XML对id的命名规范:必须以字母开始、后面可以是字母、数字、连字符、下划线、句号、冒号等完整结束符,逗号和空格这些非完整结束符是非法的。
Spring配置文件不允许出现两个相同id的<bean>,但却可以出现两个相同name的<bean>,若出现多个相同name的<bean>,通过getBean(beanName)时,返回最后声明的那个Bean。
属性注入要求bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。Spring先调用bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。若在类中显式定义了一个带参的构造函数,则需要同时提供一个默认的构造函数。Spring只会检查Bean中是否有对应的setter方法,只有Bean中是否有对应的属性变量则不做要求。
JavaBean关于属性命名的特殊规范:JavaBean的属性变量名都以小写字母起头,但也允许大写字母开头的属性变量名,但必须满足变量的前两个字母要么全大写,要么全小写。
使用构造函数注入的前提是Bean必须提供带参的构造函数。
//通过类型匹配参数
public class Car{
public Car(String brand,double price){
this.brand=brand;
this.price=price;
}
}
<bean id="car" class="com.Car">
<constructor-arg type="java.lang.String">
<value>CA72</value>
</construtor-arg>
<constructor-arg type="double">
<value>20000</value>
</construtor-arg>
</bean>
//通过索引匹配参数
public class Car{
public Car(String brand,String corp,double price){
this.brand=brand;
this.corp=corp;
this.price=price;
}
}
<bean id="car" class="com.Car">
<constructor-arg index="0" value="CA72" />
<constructor-arg index="1" value="CHINA" />
<constructor-arg index="2" value=20000 />
</bean>
//通过使用索引和类型匹配参数
public class Car{
public Car(String brand,String corp,double price){
this.brand=brand;
this.corp=corp;
this.price=price;
}
}
public class Car{
public Car(String brand,String corp,int price){
this.brand=brand;
this.corp=corp;
this.price=price;
}
}
<bean id="car" class="com.Car">
<constructor-arg index="0" type="java.lang.String">
<value>CA72</value>
</construtor-arg>
<constructor-arg index="1" type="java.lang.String">
<value>CHINA</value>
</construtor-arg>
<constructor-arg index="2" type="double">
<value>20000</value>
</construtor-arg>
</bean>
通过自身类型反射匹配入参:若Bean构造函数入参的类型是可辨别的,即使构造函数注入配置不提供类型和索引信息,Spring依旧可以正确地完成构造函数的注入工作。
XML共有5个特殊的字符,若配置文件中的注入值包括这些特殊字符,需要进行特别处理。
XML特殊实体符号
特殊符号 转义序列 特殊符号 转义序列
< < " "
> > ' &apos
& &
<ref>可以通过一下三个属性引用容器中其他Bean
bean:通过该属性可以引用同一容器或父容器的Bean
local:通过该属性只能引用同一配置文件中定义的Bean,它可以利用XML解析器自动检验引用的合法性,以便在开发编写配置时能够及时发现并纠正配置的错误。
parent:引用父容器中的Bean
Spring使用跟一个专用的<null/>元素标签作为null值。
<list>
<value></value>
<value></value>
</list>
<set>
<value></value>
<value></value>
</set>
<map>
<entry>
<key><value></value></key>
<value></value>
</entry>
<entry>
<key><value></value></key>
<value></value>
</entry>
</map>
<property>
<props>
<prop key=""></pro>
<prop key=""></pro>
</props>
</property>
集合合并:允许子<bean>继承父<bean>的同名属性集合元素,并将子<bean>中配置的结合属性值和父<bean>中配置的同名属性值合并起来作为最终的bean值。
<bean id="parentBoss" abstract-"true" class="Boss">
<property name="favorites">
<set>
<value>Read</value>
<value>Run</value>
</set>
</property>
</bean>
<bean id="childBoss" parent="parentBoss">
<property name="favorites">
<set merge="true">
<value>Swim</value>
<value>Sing</value>
</set>
</property>
</bean>
若需配置一个集合类型的Bean,为非一个集合类型的属性,可以通过util命名空间进行配置。
<util:list id="" list-class="">
<value>Swim</value>
<value>Sing</value>
</util:list>
为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。
例:使用p明明空间
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="car" class=""
p:brand=""
p:maxSpeed=""
/>
</beans>
<bean>元素提供改了一个指定自动装配类型的属性:autowire="<自动装配类型>"。Spring提供了4种自动装配类型。
byName:根据名称进行自动匹配
byType:根据类型进行自动匹配
constructor:与ByType类似,只不是针对构造函数注入而言
autodetect:根据Bean的自省机制决定采用byType还是constructor进行自动装配
lookup方法注入
public interface MagicBoss{
Car getCar();
}
<bean id="car" class="Car"
p:brand=""
p:price=""
scope="prototype"
/>
<bean id="magicBoss" class="MagicBoss">
<lookup-method name="getCar" bean="car" />
</bean>
方法替换
用于替换他人的bean必须实现MethodReplacer接口,Spring将利用该接口方法去替换目标Bean的方法。
public class Boss1{
public Car getCar(){
Car car = new Car();
car.setBrand("BMWZ4");
return car;
}
}
public class Boss2 implements MethodReplacer{
public Object reimplement(Object arg0,Method arg1,Object[] arg2) throws Throwable{
Car car = new Car();
car.setBrand("BENZ");
return car;
}
}
<bean id="boss1" class="Boss1">
<replace-method name="getCar" replacer="boss2" />
</bean>
<bean id="boss2" class="Boss2" />
bean之间的关系有继承、依赖和引用。
继承:父<bean>的主要功能是简化子<bean>的配置。一般都将父<bean>设为abstract类,表示为这个<bean>不实例化为对应的Bean。
<bean id="abstractCar" class="Car"
p:brand="BMWZ4"
p:maxSpeed=250
abstract="true"
/>
<bean id="Car1" parent="abstractCar" p:color="red" />
<bean id="Car2" parent="abstractCar" p:color="black" />
依赖:使用<ref>元素标签建立对其他bean的依赖关系,Spring负责管理这些Bean的关系,当实例化一个Bean时,Spring保证该Bean所依赖的其他Bean已经初始化。Spring允许用户通过depends-on属性指定Bean前置依赖的Bean,前置依赖的Bean会在本Bean实例化之前创建好。
public class SystemSettings{
public static int SESSION_TIMEOUT=30;
public static int REFRESH_CYCLE=60;
}
public class SysInit{
public SysInit(){
SystemSettings.SESSION_TIMEOUT=10;
SystemSettings.REFRESH_CYCLE=100;
}
}
public class CacheManager{
public CacheManager(){
Timer timer = new Timer();
TimerTash cacheTask = new CacheTask();
timer.schedule(cacheTask,0,SystemSettings.REFRESH?_CYCLE);
}
}
<bean id="manager" class="CacheManager" depends-on="sysInit" />
<bean id="sysInit" class="SysInit" />
引用:Spring提供了一个<idref>标签,通过<idref>引用跟另一个<bean>的名字
<bean id="car" class="Car">
<bean id="boss" class="Boss">
<property name="carId">
<idref bean="car" />
</property>
</bean>
整合多个配置文件:Spring允许我们通过<import>将多个配置文件引入到一个文件中,进行配置文件的集成。
<import resource="classpath:com/beans1.xml">
在默认情况下,Spring应用上下文中所有bean都是作为单例的形式创建的。即不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。
Bean的作用域类型
类别 说明
singleton 在Sprign IoC容器中仅存在一个Bean实例,Bean以单实例的方式存在
prototype 每次从容器中调用Bean时,都返回一个新的实例
request 每次HTTP Session请求都会创建一个新的Bean,仅适用于WebApplicationContxt环境
session 同一个HTTP Session共享一个Bean,不同HTTP Session使用不同的Bean,仅适用于WebApplicationContxt环境
globalSession 同一个全局Session共享一个Bean,一般用于porlet应用环境,仅适用于WebApplicationContxt环境
Spring还允许用户自定义Bean的作用域,可以通过org.springframework.beans.factory.config.Scope接口定义新的作用域,再通过org.springframeworl.beans.factory.config.CustomScopeConfigure总金额个BeanFactoryPostProcessor注册自定义的Bean作用域。
@Scope可以和@Component或@Bean一起使用。
@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class Test{ } @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class AnotherClass{ }
<bean id="" class="" scopr="prototype" />
singleton作用域
无状态或状态不可变的类适合使用单实例模式。在Spring环境下,对于所有的Dao类都可以采用单实例模式。在默认情况下,Spring的ApplicationContext容器在启动时,自动实例化所有singleton的Bean并缓存于容器中。虽然在启动时会花费一些时间,但带来两个好处:首先对Bean提前的实例化操作会及早发现一些潜在的配置问题;其次Bean以缓存的方式保存,当运行期使用到该Bean时就无须再实例化了,加快了运行效率。若用户不希望在容器启动时提前实例化singleton的Bean,可以通过lazy-init属性进行控制:
<bean id="boss1" class="Boss" lazy-init="true" />
prototype作用域:采用scope="prototype"指定非单实例作用域的Bean。在默认的情况下,Spring容器在启动时不实例化prototype的Bean。此外,Spring容器将prototype的Bean交给调用者,就不再管理它的生命周期了。
Web应用环境相关的Bean作用域
若用户使用Spring的WebApplicationContext,则还可使用另外3种Bean的作用域:request、session和globalSession。不过使用这些作用域之前,首先必须在Web容器中进行一些额外的配置。
在Web容器中进行额外的配置
在低版本的Web容器中,用户可以使用HTTP请求过滤器进行配置
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
在高版本的Web容器中,则可以利用HTTP请求监听器进行配置
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
<bean id="" class="" scope="session">
<aop:scoped-proxy />//默认情况下,它会使用CGLib创建目标类的代理
</bean>
<bean id="" class="" scope="session">
<aop:scoped-proxy proxy-target-class="false" /> //生成一个基于接口的代理
</bean>
@Component @Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES) public ShoppingCart cart(){ }
@Component public class StoreService{ private ShoppingCart shoppingCart; @Autowired public void setShoppingCart(ShoppingCart shoppingCart){ this.shoppingCart = shoppingCart; } }
Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。此例子中proxyMode被设置成ScopedProxyMode.INTERFACES。若ShoppingCart是类的话,我们需要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来表明要生成目标类扩展的方式创建代理。
在JavaConfig中引用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" 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="compactDisc" class="test.cherry.springinaction.di.BlankDisc"
c:_0="Sgt. Pepper's Lonely Heart Club Band"
c:_1="The Beatles"> <contructor-arg>
<list>
<value>Sge. Pepper's Lonely Heart Club Band</value>
<value>With a Little Help from My Friends</value>
</list>
</constructor-arg>
</bean>
</beans>
@Configuration public class CDConfig{ @Bean public CompactDisc compactDisc(){ return new SgtPeppers(); } }
class SgtPeppers{ private String title; private String artist; private List<String> tracks; public SgtPeppers(String title, String artist, List<String> tracks){ this.title = title; this.artist = artist; this.tracks = tracks; } }
@Configuration @Import(CDConfig.class) @ImportResource("classpaht:xx.xml") public class ConfigMix(){ }
Spring在3.1引入了bean profile的功能。要使用profile,首先要酱所有不同的bean定义整理到一个或多个profile中,将应用部署到每个环境时,确保对应的profile出于激活的状态。@Profilez注解应用在了类级别上,它会告诉Spring这个配置类中的bean只有在profile激活时才会创建,如果profile没被激活,那么带有@Bean注解的方法会被忽视掉。同样也可以在XML中配置profile。
@Configuration public class DataSourceConfig{ @Bean @Profile("dev") public DataSource embeddedDataSource(){ return new EmbeddedDataSourceBuilder().setType(EmbeddedDatabaseType.H2).addScript("").build(); } @Bean @Profile("prod") public DataSource jndiDataSource(){ JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean(); jndiObjectFactoryBean.setJndiName(""); jndiObjectFactoryBean.setResourceRef(true); jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class); return (DataSource) jndiObjectFactoryBean.getObject(); } }
<?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:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework/schema/jee http://www.springframework/schema/jee/spring-jee.xsd http://www.springframework/schema/jdbc http://www.springframework/schema/jdbc/spring-jdbc.xsd http://www.springframework/schema/beans http://www.springframework/schema/beans/spring-beans.xsd"> <beans profile="dev"> <jdbc:embedded-database id=""> <jdbc:script loacation="" /> <jdbc:script loacation="" /> </jdbc:embedded-database> </beans> <beans profile="prod"> <jee:jndi-loojup id="dataSource" jndi-name="jdbc/myDatabase" resource-ref="true" proxy-interface="javax.sql.DataSource" /> </beans>
</beans>
Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。
可以通过以下几种方式来设置这两个属性:
作为DispatcherServlet的初始化参数
作为Web应用的上下文参数
作为JNDI条目
作为环境变量
作为JVM的系统属性
在集成测试类上,使用@ActiveProfiles注解设置
在web应用的web.xml文件中设置默认的profile
<?xml version="1.0" encoding="UTF-8" ?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" > <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param> <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>appServlet</servlet-name> <servlet-class> org.springframework.werb.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={Test.class}) @ActiveProfiles("dev") public class Test{ }
Spring4引入了@Conditional注解,他可以用到带有@Bean注解的方法中,如果给的的条件计算结果为true,就会创建这个bean,否则这个bean会被忽略
@Bean @Conditional(MagicExistsCondition.class) public MagicBean magicBean(){ return new MagicBean(); }
public class MagicExistsCondition implements Condition{ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){ Environment env = context.getEnvironment(); return env.containsProperty("magic"); } }
ConditionContext是一个接口,大致包括
BeanDefinitionRegistry getRegistry(); //返回BeanDefinitionRegistry检查bean定义
ConfigurableListableBeanFactory getBeanFactory(); //返回ConfigurableListableBeanFactory检查bean是否存在,还可以探查bean的属性
Environment getEnvironment(); //返回的Environment检查环境变量是否存在以及它的值是什么
ResourceLoader getResourceLoader(); //返回ResourceLoader所加载的资源
ClassLoader getClassLoader(); //返回ClassLoader加载并检查类是否存在
AnnotatedTypeMetadata也是一个接口,大致为:
boolean isAnnotated(String annotationType);
Map<String, Object> getAnnotationAttributes(String annotationType);
Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString);
处理自动装配的歧义性
在声明bean的时候,通过@Primary可以将某个bean设置为首先。当遇到歧义性的时候,Spring会使用这个bean。
在自动化装配装配上@Component @Primary
在Java配置上@Bean @Primary
在XML中 <bean id="" class="" primary="true" />
当同一类型设置了两个@Primary时,会抛出NoUniqueBeanDefinitionException
@Qualifier能在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限定条件。
@Autowired @Qualifier("iceCream") //限定符与要注入的bean的名称是紧耦合的,对类名的任意改动会导致限定符失效 public void setDessert(Dessert dessert){ this.dessert = dessert; } @Componet public class Cake implements Dessert {...} @Component public class Cookies implements Dessert {...} @Component public class IceCream implements Dessert {...}
当@Qualifier与@Component或@Bean组合使用时,意味着创建自定义的限定符,而不依赖于将bean ID作为限定符
@Component @Qualifier("cold") public class IceCream implements Dessert{...} @Bean @Qualifier("cold") public Dessert iceCream(){ return new IceCream(); } @Autowired @Qualifier("cold") public void setDessert(Dessert dessert){ this.dessert = dessert; }
@Component @Qualifier("cold") @Qualifier("creamy") public class IceCream implements Dessert{...} @Component @Qualifier("cold") @Qualifier("fruity") public class Popsicleimplements Dessert{...} @Bean @Qualifier("cold") @Qualifier("fruity") public Dessert iceCream(){ return new IceCream(); } @Autowired @Qualifier("cold") @Qualifier("creamy") public void setDessert(Dessert dessert){ this.dessert = dessert; }
Java不允许在同一个条目上重复出现相同类型的多个注解,编译器会报错。这时,对于上述情况,我们可以创建自定义的限定符注解。
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Cold{} @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Creamy{} @Component @Cold @Creamy public class IceCream implements Dessert {...} @Component @Cold @Fruity public class Popsicle implements Dessert {...} @Autowired @Cold @Creamy public void setDessert(Dessert dessert){ this.dessert = dessert; }
Spring提供了两种在运行时求值的方式:
属性占位符(Property placeholder)
Spring表达式语言(SpEL)
在Spring中,处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索属性
@Configuration @PropertySource("classpath:/.../.../app.properties") public class ExpressiveConfig{ @Autowired Environment env; @Bean public BlankDisc disc(){ return new BlankDisc(env.getProperty("disc.title", "defaultValue"), env.getProperty("disc.artist", "defaultValue")); } }
Spring Environment的getProperty()方法有四种重载:
String getProperty(String key);
String getProperty(String key, String defaultValue);
T getProperty(String key, Class<T> type);
T getProperty(String key, Class<T> type, T defaultValue);
当某种属性必须要定义时,可以使用getRequiredProperty()方法,若属性不存在,则会跑出IllegalStateException。
当想检验某种属性是否存在的话,可以使用containsProperty()方法
当想将属性解析为类的话,可以使用getPropertyAsClass()方法
Environment还提供了一些方法来检查哪些profile出于激活状态
String[] getActiveProfiles(); //返回激活profile名称的数组
Strring[] getDefaultProfiles(); // 返回默认profile名称的数组
boolean acceptsProfiles(String... profiles); //若environment支持给定的profile,返回true
Spring支持将属性定义到外部属性的文件中,并使用占位符值将其插入到Spring bean中。在Spring装配中,占位符的形式以实用${..}包装的属性名称
<bean id="" class="" c:_title="${disc.title}" c:_artist="${disc.artist}" />
public BlankDisc(@Value("${disc.title}") String title, @Value("${disc.artist}") String artist){ this.title = title; this.artist = artist; }
为了实用占位符,我们必须配置一个PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。从Spring3.1开始,推荐使用PropertySourcesPlaceholderConfigurer,它能够基于Spring Environment及其属性源来解析占位符
@Bean public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){ return new PropertySourcesPlaceholderConfigurer(); }
或在xml中配置<context:property-placeholder />
Spring 3引入了Spring表达式语言(Spring Expression Language, SpEL)
SpEL拥有很多特性:
实用bean的ID来引用bean
调用方法和访问对象的属性
对值进行算术、关系和逻辑运算
正则表达式匹配
集合操作
SpEL表达式要放到#{..}之中。T()表达式将T()内中视为Java中对应的类型。如 #{T(System).currentTimeMillis()};#{sgtPeppers.artist}//引用bean的属性;还可以通过systemProperties对象引用系统属性#{systemProperties['disc.title']}
public BlankDisc(@Value("#{systemProperties['disc.title']}" String title), @Value("#{system.properties['disc.artist']}" String artist) ){ this.title = title; this.artist = artist; }
xml配置
<bean id="" class="" c:_title="${systemProperties['disc.title']}" c:_artist="#{systemProperties['disc.artist']}" />
SpEL可以表整数、浮点数,String值和Boolean值
#{3} #{3.14159} #{'Hellp'} #{false}
SpEL可以引用Bean及其属性和方法
#{beanId} #{beanId.attribute} #{beanId.method()} #{beanId.method().?method()} (?可以在访问它右边内容之前,确保他多对应的元素不是null)
在SpEL中访问类作用域的方法和常量的话,要依赖T()关键运算符
T(java.lang.Math) T{java.lang.Math}.PI T(java.lang.Math).random()
SpEL支持一下运算符
算术运算符 +,-,*,/,%,^
比较运算符 <,>,==,<=,>=,lt,gt,eq,le,ge
扩及运算符 and,or,not,|
条件运算符 ?:
正则表达式 matches
SpEL还提供了三元运算符
#{scoreboard.score > 1000 ? "Winner" : "Loser"}
#{disc.title ?: 'Rattle and Hum'}
SpEL正则表达式
#{email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}
[]用来从集合或数组中按照索引来获取元素
#{'This is a test'[3]} //s
SpEL还提供了查询运算符.?[]。他会用来对集合进行过滤,得到集合的一个子集
#{list.?[attribute eq 'test']} //返回list中所有item属性为test值的元素
SpEL还提供了投影运算符.![],他会从集合的每个成员中选定特定的属性放到另一个集合中
#{list.![attribute]} //返回attribute的新list
#{list.?[attribute eq 'test '].![anotherAttribute]}