第三章 高级装配
3.1 环境与profile 3.1.1配置profile bean
在我们开发的时候可能会使用相对简单的环境,但是在生产时,因客户端的差异性,我们需要使用另外一种环境,若是重构环境,可能会发生不必要的bug以及问题,Spring中提供了解决办法@Profile
在spring3.1以前@Profile只能适用在类上面,现在此标签可适用于方法上
在JavaConfig中配置
@Configuration public class DevelopmentProfileConfig { /** * 此方法会搭建一个Hypersonic的嵌入式数据库,模式(schema)定义在schema.sql中 * ,测试数据通过test-data.sql加载的,此模式适合开发环境 * @return */ @Bean(destroyMethod="shutdown") @Profile("dev") public DataSource embeddedDataSource() { return new EmbeddedDatabaseBuilder().addScript("classpath:schema.sql") .addScript("classpath:test-data.sql") .build(); } /** * 此环境适用于生产环境 * @return */ @Bean @Profile("prod") public DataSource jndiDataSource() { JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean(); jndiObjectFactoryBean.setJndiName("jdbc/myDS"); jndiObjectFactoryBean.setResourceRef(true); jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class); return (DataSource) jndiObjectFactoryBean.getObject(); } }
只有在规定的profile激活时,相应的bean才会被创建,未被profile标记的bean默认都是会被创建的
在xml中配置profile
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd" profile="dev"> <!-- 中间代码省略 --> </beans>
在底部profile中设置了此xml的环境,其余环境同理
也可以使用同一个xml来定义
<beans profile="dev"> <bean id="" class=""></bean> <bean id="" class=""></bean> </beans> <!-- 中间代码与之前的bean注入没有什么区别 --> <beans profile="prod"> <bean id="" class=""></bean> <bean id="" class=""></bean> </beans>
3.1.2激活profile
激活profile,需要依赖两个独立属性,spring.profiles.default和spring.profiles.active
spring.profiles.default:默认的环境值 spring.profiles.active激活指定的环境
若是两者都没有设定,则只会创建没有定义在profile中的bean
设置这两种属性的方法
-
作为DispatcherServlet的初始化参数
-
作为Web应用的上下文参数
-
作为JNDI条目
-
作为环境变量
-
作为JVM的系统属性
-
在继承测试类上,使用@ActiveProfiles注解设置
在Web应用的web.xml文件设置默认的profile
<!-- 为上下文设置默认的profile --> <context-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </context-param>
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/springmvc-context.xml</param-value> </init-param> <!-- 为Servlet设置默认的profile --> <init-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </init-param> </servlet>
相对而言我更加喜欢JavaConfig
@Configuration @ActiveProfiles("dev") public class DevelopmentProfileConfi { //中间省略 }
注意:在上面的**spring.profiles.default和spring.profiles.active**两个属性中,profiles是复数的,所以我们可以同时激活多个profiles
3.2条件化的bean
spring中提供了一个或多个bean在某种特定的条件下才会被创建的标签,这是在spring4后面引入的
@Conditional标签
此次例子我们希望环境时magic环境时才会创建的bean
我们需要创建一个实现Conditiion接口的类
public class MagicExistsCondition implements Condition{ /** * 实现matches方法,若方法返回true,则@Condition所在类会被创建,反之亦然 */ @Override public boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadat) { // TODO Auto-generated method stub return false; } }
此中有两个参数
ConditionContext ctxt: ConditionContext是一个接口
public interface ConditionContext { //返回的BeanDefinitionRegistry检查bean定义 BeanDefinitionRegistry getRegistry(); //返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性 ConfigurableListableBeanFactory getBeanFactory(); //返回的Environment检查环境变量是否存在以及他的值是什么 Environment getEnvironment(); //返回的ResourceLoadert所加载的资源 ResourceLoader getResourceLoader(); //返回的ClassLoader加载并检查类是否存在 ClassLoader getClassLoader(); }
AnnotatedTypeMetadata metedate: AnnotatedTypeMetadata也是一个接口
public interface AnnotatedTypeMetadata { //能够判断带有anntotaionname注解 boolean isAnnotated(String annotationName); //通过annotationName获取属性的值,以key-value形式返回 Map<String, Object> getAnnotationAttributes(String annotationName); //自行解决~ 看源码 Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString); //检索给定类型的所有注释的所有属性 MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName); //自行解决~ 看源码 MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString); }
JavaConfig中
@Bean @Conditional(MagicExistsCondition.class)//MagicExistsCondition中的matches()返回true则创建 public MagicBean magincBean() { return new MagicBean(); }
使用方法就如上了, 现在我们来看下@Profile中的源码,因为可以发现@Profile也是一个条件标签,即很有可能实现了@Conditional标签
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(ProfileCondition.class) public @interface Profile { /** * The set of profiles for which the annotated component should be registered. */ String[] value(); }
@Profile引用了ProfileCondition作为Condition实现,ProfileCondition实现了Condtion接口,并且做出决策考虑到了AnnotatedTypeMetadata 和ConditionContext 中的因素
class ProfileCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { if (context.getEnvironment() != null) { MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles(((String[]) value))) { return true; } } return false; } } return true; } }
处理自动装配的歧义性
这里有三个实现类同一接口
@Component public class IceCream implements Dessert{ } @Component public class Cookies implements Dessert{ } @Component public class Cake implements Dessert{ }
//JavaConfig类,当我们想设置dessert值得时候会产生异常 @Configuration public class JavaConfig { private Dessert dessert; @Autowired public void setDessert(Dessert dessert) { this.dessert = dessert; } }
在JavaConfig中,这种情况Spring无法做出选择,会抛出NoUniqueBeanDefinitionException异常,这种情况我们有三种解决方案
3.3.1标示首选得bean(第一种)
在实现类中贴上标签@Primary,此标签设置了Spring在设值时首选使用IceCream实现类
@Component @Primary public class IceCream implements Dessert{ }
在xml中配置
<!-- 将primary设置成为true值 --> <bean id="ice" class="com.myapp.IceCream" primary="true"></bean>
3.3.2 限定自动装配的bean
在上述中的setter方法上加上@Qualifier标签
@Autowired @Qualifier("iceCream")//此标签说明了引入IOC容器中id为iceCream的bean public void setDessert(Dessert dessert) { this.dessert = dessert; }
使用此标签所引用的bean要具有类型的"iceCream"作为限定符,若是没有,则会默认以方法名指定
我们也可以自定义的创建限定符
@Component @Qualifier("cold") public class IceCream implements Dessert{ } @Configuration public class JavaConfig { private Dessert dessert; @Autowired @Qualifier("cold") public void setDessert(Dessert dessert) { this.dessert = dessert; } }
这种方法就是往类上声明一个id名
但是有种情况,SPringl又会懵逼
@Component @Qualifier("cold") public class Popsicle implements Dessert{ } @Component @Qualifier("cold") public class IceCream implements Dessert{ } @Configuration public class JavaConfig { private Dessert dessert; @Autowired @Qualifier("cold") public void setDessert(Dessert dessert) { this.dessert = dessert; } }
可以看到,Spring想找id为cold的bean,可是发现有两个,也会报错
所以我们需要缩小范围,但是Java中不允许同样的注解标签,这时候我们可以使用自定义的限定符注解
即创建一个@Cold标签
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Creamy { } @Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Cold { }
//以上两个类中又@Qualifier标签,具有了其特性//现在我们的JavaConfig中就不会拥有相同的标签了
@Component @Cold @Fruit public class Popsicle implements Dessert{ } @Component @Cold @Creamy public class Cake implements Dessert{ } @Configuration public class JavaConfig { private Dessert dessert; @Autowired @Cold @Creamy public void setDessert(Dessert dessert) { this.dessert = dessert; } }
3.4 bean的作用域
不同的情况下,我们应该需要有不同的bean作用域,有时我们只需要单一的bean,有时却需要多个相同bean
Spring定义了多种作用域
-
单例(Singleton):整个应用中,只创建一个bean
-
原型(Protptype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例
-
会话(Session):在Web应用中,为每个会话创建一个bean实例
-
请求(Request):在Web应用中,为每个请求创建一个bean实例
在Bean中可以加标签@Scope
@Component @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) //@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)两者都可以,不能同时使用 public class Cookies implements Dessert{ }
在xml中,可以以这种形式存在
<bean id="ice" class="com.myapp.IceCream" scope="prototype"></bean>
3.4.1 使用会话和请求作用域
在Web工程的商城购物车中,使用Session作用域的bean是非常好的事情,因为一人一个购物车
若是单例的bean,那每个人都得用同一个bean,将会不堪设想.
@Component //WebApplicationContext此元素位于org.springframework.web.context.WebApplicationContext //也可以自己观察源码 @Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES) public Interface ShoppingCart { } //将ShoppingCart注入Services中 @Component public class StoreServices { private ShoppingCart shoppingcart; @Autowired public void setShoppingcart(ShoppingCart shoppingcart) { this.shoppingcart = shoppingcart; } }
只有当ShoppingCart bean是会话作用域得,只有创建了会话后,才会出现ShoppingCart实例.
在当中有一个问题:
ShoppingCart 是多例得,而StoreServices是单例得,对于StoreServices处理购物车功能时,我们不会希望固定的一个bean,而时所用ShoppingCart实例恰好是当前会话所对应的那一个,所以Spring会将ShoppingCart 注入一个到ShoppingCart的代理对象中,其会进行兰姐系并将调用委托个会话作用域内真正的ShoppingCart bean.
proxyMode属性被设置成了ScopedProxyMode.INTERFACES,表明代理类需要实现此接口,并将调用委托给实现bean
proxyMode属性被设置成了ScopedProxyMode.TARGET_CLASS,表明代理类要以生成目标类扩展的方式创建代理,并将调用委托给实现bean
如果ShoppingCart是类的话就需要使用CGLib来生成代理
如果ShoppingCart是接口的话就需要使用jdk动态代理来生成代理
3.4.2 在XML中声明作用域代理
<!-- 默认使用了CGlib方式创建代理类 --> <bean id="cart" class="com.myapp.ShoppingCart" scope="session"> <aop:scoped-proxy/> </bean> <!-- 使用jdk动态代理, ShoppingCart应该是个接口才可以使用--> <bean id="cart" class="com.myapp.ShoppingCart" scope="session"> <aop:scoped-proxy proxy-target-class="false"/> </bean>
3.5运行时值注入
在许多时候,我们会希望自己的xml文件中避免硬编码,想要实现值在运行时才确定有两种方法
3.5.1 注入外部的值
@Configuration //加载类路径下的/config/db.properties资源 @PropertySource("classpath:/config/db.properties") public class JavaConfig { @Autowired Environment ev; public void PropertySource(Environment ev) { //取出资源中属性名为jdbc.url的值 System.out.println(ev.getProperty("jdbc.url")); System.out.println(ev.getProperty("jdbc.password")); } }
至于Environment类如何获取值得方法,可以查询Environment和PropertyResolver接口中得源码
解析属性占位符
属性得占位符可以是 ,${}, #{}
但是在JavaConfig我们需要配置一个类
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); }
或者在xml中配置
<context:property-placeholder/>
当二者选一后,我们才能使用占位符
3.5.2 使用SPring表达式语言进行装配
-
使用bean得ID来引用bean
-
调用方法和访问对象得属性
-
对值进行算数,关系,和逻辑运算
-
正则表达式匹配
-
集合操作
样例可自行查看官网