一、Spring profile
1.1、目的
- 软件开发过程一般涉及“开发 -> 测试 -> 部署上线”多个阶段,每个阶段的环境的配置参数会有不同,如数据源,文件路径等。为避免每次切换环境时都要进行参数配置等繁琐的操作,可以通过spring的profile功能来进行配置参数的切换。
1.2、方式
- 项目目录内容,每个环境各有一个包,包中分别包含数据库、日志、路径等配置文件:
- spring的xml中声明这些bean,实现了通过profile标记不同的环境
<description>spring profile配置</description>
<!-- 开发环境配置文件 -->
<beans profile="development">
<context:property-placeholder
location="classpath*:common/*.properties, classpath*:development/*.properties" />
</beans>
<!-- 测试环境配置文件 -->
<beans profile="test">
<context:property-placeholder
location="classpath*:common/*.properties, classpath*:test/*.properties" />
</beans>
<!-- 生产环境配置文件 -->
<beans profile="production">
<context:property-placeholder
location="classpath*:common/*.properties, classpath*:production/*.properties" />
</beans>
-
通过设置spring.profiles.default和spring.profiles.active这两个属性来激活和使用对应的配置文件。设置的方式如下:
- 在集成测试类上,使用@Profile注解配置。这个bean只有在production这个配置类是激活状态的时候才会被创建和生效。
@Bean @Profile("production") public DataSource jndiDataSource(){ JndiObjectFactoryBean jofb=new JndiObjectFactoryBean(); jofb.setJndiName("jndi/iDS"); jofb.setResourceRef(true); jofb.setProxyInterface(xxx.class); return (DataSource) jofb.getObject(); }
- 作为JNDI条目;
- 作为环境变量;
- 作为JVM的系统属性
-Dspring.profiles.active="production"
- 在web.xml中作为web应用的上下文参数context-param;
- 在web.xml中作为DispatcherServlet的初始化参数;
<!-- 在上下文context-param中设置profile.default的默认值 --> <context-param> <param-name>spring.profiles.default</param-name> <param-value>development</param-value> </context-param> <!-- 在上下文context-param中设置profile.active的默认值 --> <!-- 设置active后default失效,web启动时会加载对应的环境信息 --> <context-param> <param-name>spring.profiles.active</param-name> <param-value>development</param-value> </context-param>
二、自动装配的歧义和解决方式
2.1、一个接口多个实现类的情况
- 案例:Dessert接口的三个实现类:Cake、IceCream、Cookies。三个实现类均以@Component注解,那么当注入该Dessert接口的时候,不确定具体使用的是哪个实现类
- 解决办法有几种:
-
(1)、设置实现类为首选项,在实现类中添加@Primary注解。代表注入的时候优先选择。(隐患是无法保证只有一个首选项)
@Bean @Primary public Dessert IceCream(){ return new IceCream }
<bean id = "iceCream" class = "com.xxx.IceCream" primary = "true" />
-
(2)、限定注入接口的实现类的具体名称,利用@Qualifier(“xxx”)注解,参数为具体实现类的beanId。(但是其隐患是,若重构该实现类,修改了其类名,那么beanId也会被修改。那样我们这里写死的注入的beanId名称就会有问题。Qualifier指定的限定符和bean的名称紧耦合,对类名称的任务改动都会导致限定符失效。)
@Autowrired @Qualifier("iceCream") public void setDessert(Dessert dessert){ this.dessert = dessert }
-
(3)、在类定义中,通过注解@Qualifier(“coldxx”)为类创建自定义的限定符,使限定符的名称不强依赖类名称。那样在引用注入的时候,可以使用这个自定义的限定符那指定引用的实现类。(其隐患是,如果多个bean存在相同的特性,想到的解决办法是在每个bean上注解多个自定义的限定符,直到可以区分唯一bean,但是java中不允许同一个条目中重复出现相同类型的注解)
@Bean @Qualifier("cold") public Dessert IceCream(){ return new IceCream } @Autowrired @Qualifier("cold") public void setDessert(Dessert dessert){ this.dessert = dessert } @Bean @Qualifier("cold") @Qualifier("cold2")//这种是不被允许的 public Dessert IceCream(){ return new IceCream }
-
(4)、把限定符声明为注解的方式,那么每个限定符的注解名类目是不一样的。
@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 Cold2{} @Bean @Cold @Cold2 public Dessert IceCream(){ return new IceCream } @Autowrired @Cold @Cold2 public void setDessert(Dessert dessert){ this.dessert = dessert }
-
三、bean的作用域
- 默认情况下,Spring应用上下文所有bean都是作为单例(singleton)的形式创建的。但是某些类是易变有状态的,这种情况下复用单例的bean是有问题的。
3.1、作用域类型
- 单例(Singleton):整个应用中,只创建bean一个实例
- 原型/多例(Propotype):每次的注入或者从Spring应用上下文获取的时候,都会创建一个新的bean。
- 会话(Session):web应用中,为每个会话创建一个bean实例
- 请求(Request):web应用中,未每个请求创建一个bean实例
3.2、多例的bean设置
-
组件扫描和类声明的方式来设置多例
@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //@Scope("prototype")//两种方式 public class Notpad(){xxxx}
-
java配置中声明Notpad为多例
@Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //@Scope("prototype")//两种方式 public Notpad notpad(){ return new Notpad(); }
- xmlz中声明
<bean id = "iceCream" class = "com.xxx.IceCream" scope = "prototype" />
3.3、会话作用域与请求作用域
-
案例:电子商务应用中,购物车的bean,如果是单例,会导致每个用户拥有相同的购物车;如果是多例,会导致不能两个线程同时使用购物车。会话作用域最合适,因为是与给定用户关联性最大
-
方式:
- vaule=session,会告知springWeb应用中的每一个会话创建一个shoppingCart。虽然有多个shoppingCart的实例,但是对于每一个会话来说,这个bean相当于是单例的。在用户进入系统,创建了会话之后,才会创建shoppingCart的实例
- proxyMode=interface,解决了将会话或请求作用域的bean注入到单例ban中所遇到的问题。当shoppingCart注入到另外单例类的方法中,此时shoppingCart的bean并不存在。此时spring的处理方式是将shoppingCart bean的代理,注入到方法中。当调用改方法的时候,代理会对其进行懒解析并调用委托给会话作用域内真正的shoppingCart bean。interface代表代理要实现shoppingCart接口,并将调用委托给实现bean。
@Component @Scope(value = WebApplicationContext.SCOPE_SEESION, proxyMode = ScopedProxyMode.INTERFACE ) public ShoppingCat cart(){xxxx}
<bean id = "cart" class = "com.xxx.ShoppingCart" scope = "seesion" > <aop:scoped-proxy> //<aop:scoped-proxy proxy-target-class = "false"> </bean>