spring(3)高级装配
@Bean(destroyMethod="shutdown")
public DataSource dataSource() {return new EmbeddedDatabaseBuilder().addScript("classpath:schema.sql").addScript("classpath:test-data.sql").build();
}
2)使用JNDI从容器中获取DataSource
@Bean
public DataSource dataSource() {JndiObjectFactoryBean jndiObjectFactoryBean =new JndiObjectFactoryBean();jndiObjectFactoryBean.setJndiName("jdbc/myDS");jndiObjectFactoryBean.setResourceRef(true);jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);return (DataSource) jndiObjectFactoryBean.getObject();
}
3)选择不同的DataSource配置
@Bean(destroyMethod="close")
public DataSource dataSource() {BasicDataSource dataSource = new BasicDataSource();dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");dataSource.setDriverClassName("org.h2.Driver");dataSource.setUsername("sa");dataSource.setPassword("password");dataSource.setInitialSize(20);dataSource.setMaxActive(30);return dataSource;
}
Attention)我们必须要有一种方法来配置DataSource,使其在每种环境下都会选择最为合适的配置;其中一种方式是在单独的配置类(或XML 文件)中配置每个bean,然后在构建阶段(可能会使用Maven的profiles)确定要将哪一个配置编译到可部署的应用中;
problem)从开发阶段迁移到QA 阶段,重新构建可能不会出大问题,但从QA阶段迁移到 生产阶段,重新构建可能会引入bug;solution)Spring所提供 的 解决方案并不需要重新构建;
荔枝1)嵌入式数据库可能会配置为如下形式:@Configuration @Profile("dev") public class DevelopmentProfileConfig { @Bean(destroyMethod="shutdown") public DataSource dataSource() {return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).addScript("classpath:schema.sql").addScript("classpath:test-data.sql").build();} }
荔枝2)使用JNDI从容器中获取DataSource 的 Profile配置
@Configuration @Profile("prod") public class ProductionProfileConfig {@Beanpublic DataSource dataSource() {JndiObjectFactoryBean jndiObjectFactoryBean =new JndiObjectFactoryBean();jndiObjectFactoryBean.setJndiName("jdbc/myDS");jndiObjectFactoryBean.setResourceRef(true);jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);return (DataSource) jndiObjectFactoryBean.getObject();} }
荔枝3)从spring3.2开始,@Profile注解既可以在方法级别上使用,也可以在类级别上使用;(还可以和 @Bean 注解一起使用)
A1)没有定义在profile中的bean 都会被创建,无论profile 激活与否;A2)而定义在 profile中的bean,当且仅当对应的 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: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.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
</beans><beans profile="qa">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close" p:url="jdbc:h2:tcp://dbserver/~/test"p:driverClassName="org.h2.Driver" p:username="sa" p:password="password"p:initialSize="20"p:maxActive="30" />
</beans>
<beans profile="prod">
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDatabase"
resource-ref="true" proxy-interface="javax.sql.DataSource" />
</beans>
</beans>
method1)作为 DispatcherServlet 的初始化参数;method2)作为web 应用的上下文参数;method3)作为 JNDI条目;method4)作为环境变量;method5)作为JVM的 系统属性;method6)在集成测试类上, 使用 @ActiveProfiles 注解设置;(干货——注解@ActiveProfile的作用)
3.1)intro:spring提供了 @ActiveProfiles注解,来指定测试时要激活哪个profile;3.2)下面的荔枝展示了 使用 @ActiveProfiles注解 激活 dev profile;
A1)matches() 方法:会得到 ConditionContext 和 AnnotatedTypeMetadata 对象用来做出决策;A2)ConditionContext是一个接口:public interface ConditionContext { BeanDefinitionRegistry getRegistry(); ConfigurableListableBeanFactory getBeanFactory(); Environment getEnvironment(); ResourceLoader getResourceLoader();ClassLoader getClassLoader(); }
A3)通过ConditionContext,可以做到如下几点(works):
work1)借助getRegistry方法返回的BeanDefinitionRegistry检查bean的定义;work2)借助getBeanFactory方法返回的ConfigurableListableBeanFactory 检查bean是否存在,查看bean的属性work3)借助getEnvironment方法返回的Environment检查环境变量是否存在以及它的值是什么;work4)读取并检查getResourceLoader方法返回的ResourceLoader所加载的资源;work5)借助getRegistry方法返回的BeanDefinitionRegistry检查bean的定义;work6)借助getClassLoader方法返回的ClassLoader加载并检查类是否存在;
A4)AnnotatedTypeMetadata能够让我们检查带有 @Bean 注解的方法上还有什么其他的注解;public interface AnnotatedTypeMetadata { boolean isAnnotated(String annotationName); // 判断带有@Bean注解的方法是不是还有其他的注解.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); }
A1)ProfileCondition 通过 AnnotatedTypeMetadata得到了用于 @Profile 注解的所有属性;A2)根据 通过ConditionContext得到的 Environment 来检查(acceptProfiles()方法)该profile 是否处于激活状态;
1.1)problem测试用例:(error info: No qualifying bean of type [com.spring.chapter3.Disc] is defined: expected single matching bean but found 2: jayChou,leehom)
1.2)solution:spring 提供了多种可选方案来解决这样的问题。你可以将可选的bean 中的某个设为首选(primary)的bean,或者使用限定符来帮助 spring 将可选的bean的范围缩小到只有一个bean;
1.3)如果在XML 配置bean的话,设置primary属性为 true来指定;
1.1)problem:设置首选bean的 局限性: 在于@Primary 无法将可选方案的范围限定到唯一一个无歧义性的可选项中。当首选bean的数量超过一个四,我们并没有其他的方法来进一步缩小可选范围;1.2)solution:spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到一个bena 满足所规定的限制条件;(使用@Qualifier注解)
@Component
@Qualifier("cold")
public class IceCream implements Dessert { ... }
3)@Qualifier注解也可以和 @Bean注解一起使用
@Bean
@Qualifier("cold")
public Dessert iceCream() {return new IceCream();
}
@Component
@Qualifier("fashion")
public class JayChou implements Disc { ... }
@Component
@Qualifier("fashion")
public class Leehom implements Disc { ... }
4.1.1)problem:现在我们有两个带有“fashion”限定符的唱片,在自动装配Disc bean的时候,我们再次遇到了歧义性问题;4.1.2)solution:需要更多的限定符来将可选范围限定到只有一个bean;(多个 @Qualifier 注解)
@Component @Qualifier("fashion") @Qualifier("cool") public class JayChou implements Disc { ... }
@Component @Qualifier("fashion") @Qualifier("handsome") public class Leehom implements Disc { ... }
5)problem+solution:
5.1)problem:java不允许在同一个条目上重复使用出现相同类型的多个注解;因为这样的话,编译器会报错;5.2)solution:自定义限定符注解;(不能再干货——创建自定义的限定符注解)
step1)不再使用 @Qualifier("fashion") 注解,使用自定义的 @Fashion这,该注解 的定义如下:@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Fashion{ }
Attention)
A1)自定义注解本身实际上就成为了 限定符注解;A2)通过声明自定义的限定符注解,我们可以同时使用多个限定符,不会再有java 编译器的限制或错误了;
@Component
@Qualifier("fashion")
@Qualifier("cool")
public class JayChou implements Disc { ... }
// 改为
@Component
@Fashion // a qualifier annotaion.
@Cool// a qualifier annotaion.
public class JayChou implements Disc { ... }
scope1)单例(Singleton):在整个应用中,只创建bean的一个实例;(干货——默认情况下,bean以单例模式创建)scope2)原型(Prototype):每次注入或者通过spring应用上下文获取的时候,都会创建一个新的 bean 实例;scope3)会话(Session):在web 应用中,为每个会话创建一个 bean实例;scope4)请求(Request):在web 应用中,为每个请求创建一个 bean实例;
A1)default case下: 是单例作用域;A2)如果选择其他作用域,要使用 @Scope注解,它可以和 @Component 或 @Bean 联用;(干货——@Scope注解的作用)A3)不管用哪一种方式来声明原型作用域,每次注入或从 spring 应用上下文中检索该bean的时候,都会创建新的实例;
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad { ... }用XML 来配置的话,代码形式如下:
<bean id="notepad"class="com.myapp.Notepad"scope="prototype" />
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() { ... }
A1)这会创建多个 ShoppingCartbean的实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean 实际上相当于单例的;A2)@Scope注解:同时还有一个 proxyMode 属性,它被设置成了 proxyMode=ScopedProxyMode.INTERFACES;这个属性解决了将会话或请求作用域的bean 注入到单例bean中所遇到的问题;
@Component
public class StoreService {@Autowiredpublic void setShoppingCart(ShoppingCart shoppingCart) {this.shoppingCart = shoppingCart;}...
}
A1)因为StoreService 是一个单例bean,会在spring 应用上下文加载的时候创建;A2)当它创建的时候,spring会试图 将 ShoppingCart bean 注入到 setShoppingCart() 方法中;但是ShoppingCart bean 是会话作用域的,此时并不存在。直到某个client 进入系统,创建了会话后,才会出现ShoppingCart 实例;(干货——我此时才体会到了为什么需要懒加载)A3)而且,系统中有多个 ShoppingCart 实例(多个购物车):每个用户一个。我们并不想让spring注入 到某个固定的 ShoppingCart 实例到 StoreService中。我们希望的是当StoreService 处理ShoppingCart 功能时,它所使用的 ShoppingCart 实例恰好是当前会话所对应的那一个;A4)spring 并不会将实际的ShoppingCart bean 注入到 StoreService 中。spring 会注入一个到 ShoppingCart bean 的代理,如下图所示。这个代理会暴露与 ShoppingCart 相同的方法,所以 StoreService 会认为他是一个ShoppingCart。但是,当StoreService 调用 ShoppingCart 的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的 ShoppingCart bean;(干货——懒加载的调用过程,引入了代理proxy)
<bean id="cart"class="com.myapp.ShoppingCart"scope="session"><aop:scoped-proxy />
</bean>
2)为了使用 <aop:scoped-proxy /> 新元素,我们必须在 XML 配置中声明spring 的aop 命名空间
<?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/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">...
</beans>
1.1)problem:在实现的时候将值硬编码在配置类中。用XML 装配bean的话,同样值也会是硬编码的;1.2)solution:为了避免硬编码,而是想让这些值在运行时再确定。spring 提供了两种在运行时求值的方式(ways):
way1)属性占位符(Property placeholder)way2)spring 表达式语言(SpEL)
2.1)getProperty()方法有4个重载的变种形式(variant):
v1)String getProperty(String key)v2)String getProperty(String key, String defaultValue)v3)T getProperty(String key, Class<T> type)v4)T getProperty(String key, Class<T> type, T defaultValue)
2.2)稍微修改下源码,当指定属性不存在时,使用一个default value:2.3)如果我们从属性文件中得到的是一个String类型的值,那么在使用前还需要将其转换为 integer,但是使用 getProperty()方法的重载形式,就能便利地解决这个问题:(干货——getProperty()重载方法的作用)int connectionCount = env.getProperty("db.connection.count", Integer.class, 30);
method1)getRequiredProperty()方法:如果你希望这个属性必须要定义;没有定义会抛出异常;@Bean public Disc disc() {return new JayChou(env.getRequiredProperty("disc.title"),env.getRequiredProperty("disc.artist")); }
method2)containsProperty()方法:检查该属性是否存在;boolean titleExists = env.containsProperty("disc.title");
method3)getPropertyAsClass()方法:如果想要吧属性解析为类的话;Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class);
method4)String[] getActiveProfiles():返回激活的profile名称数组;method5)String[] getDefaultProfiles():返回默认的 profile 名称 的数组;method6)boolean acceptsProfiles(String... profiles):如果 environment 支持给定的 profile的话,返回ture;
4.1)intro:spring 一直支持将属性定义到 外部的属性文件中,并使用占位符将其插入到 spring bean中;4.2)在spring装配中,占位符的形式为 使用 "${...}" 包装的属性名称;<bean id="sgtPeppers"class="soundsystem.BlankDisc"c:_title="${disc.title}"c:_artist="${disc.artist}" />
4.3)如果我们依赖于组件扫描和自动装配来创建和初始化应用组建的话,就没有XML配置文件或者类了。此时,可以使用 @Value 注解,如下所示:
public JayChou(@Value("${disc.title}") String title,@Value("${disc.artist}") String artist) { this.title = title; this.artist = artist; }
4.4)为了使用占位符,必须要配置一个 PropertySourcesPlaceholderConfigurer(spring3.1推荐使用),因为它能够基于spring Environment 及其属性源来解析占位符;如下的@Bean方法配置了 PropertySourcesPlaceholderConfigurer
@Bean public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); }
4.5)如果使用了XML配置 PropertySourcesPlaceholderConfigurer,推荐使用新元素 <context:property-placeholder>:<?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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder /> </beans>
A1)解析外部属性能够将值的处理推迟到 运行时,但是它的关注点在于根据名称解析来自于 spring Environment和属性源的属性;A2)而 spring 表达式语言提供了一种更通用的方式在 运行时计算所要注入的值;
c1)使用bean的ID 来引用bean;c2)调用方法和访问对象的属性;c3)对值进行算术,关系和逻辑运算 ;c4)正则表达式匹配;c5)集合操作;
3.1)first task : 要知道将 SpEL 表达式放到 " #{...} "之中,这与属性占位符有些类似,属性占位符需要放到 " ${...} " 之中;3.2)荔枝组团来袭#{T(System).currentTimeMillis()} : 计算表达式的那一刻当前时间的毫秒数;T() 表达式会将java.lang.System 视为 java中对应的类型;因此可以调用其 static 修饰的currentTimeMillis方法; #{jaychou.artist}:得到id 为 jaychou 的bean 的artist属性; #{systemProperties['disc.title']}:引用系统属性;
4.1)通过组件扫描 创建bean的话,在注入属性和构造器参数时,我们可以使用 @Value 注解,这与之前看到的属性占位符有点类似,但现在我们要使用 SpEL表达式;看个荔枝):从系统属性中获取专辑名称和艺术家的名字;public BlankDisc(@Value("#{systemProperties['disc.title']}") String title,@Value("#{systemProperties['disc.artist']}") String artist) {this.title = title;this.artist = artist; }
4.2)在XML配置中,可以将SpEL 表达式传入 <property> or <constructor-arg> 的value属性中,或者将其作为 p-命名空间或c-命名空间条目的值;
看个荔枝)构造器参数通过SePL 表达式设置;<bean id="sgtPeppers"class="soundsystem.BlankDisc"c:_title="#{systemProperties['disc.title']}"c:_artist="#{systemProperties['disc.artist']}" />
#{3.14159} == 表示浮点值;
#{9.87E4} ==98700
#{'Hello'} ==计算String类型的字面值;
#{false} ==boolean类型的值;
#{jaychou}
#{jaychou.artist} // 对属性(artist)的引用;
#{jaychou.selectArtist()} // 调用bean上的方法;
#{jaychou.selectArtist().toUpperCase()} // 对方法的连续调用;
#{artistSelector.selectArtist()?.toUpperCase()} // 使用类型安全的运算符,当返回不为null时,才调用后面的方法;
T(java.lang.Math) // 为了在SpEL 中表达java的Math类,可以像左侧这样使用 T() 运算符;
T(java.lang.Math).PI // 将PI 值装配到bean的属性中;
T(java.lang.Math).random() // 计算得到一个0~1 间的随机数;
#{2 * T(java.lang.Math).PI * circle.radius}//计算圆周长;
#{T(java.lang.Math).PI * circle.radius ^ 2}//计算面积;
#{disc.title + ' by ' + disc.artist} // 连接字符串;
#{counter.total == 100} or #{counter.total eq 100}// 数字比较;
#{scoreboard.score > 1000 ? "Winner!" : "Loser"} //三元运算符的应用;
#{disc.title ?: 'Rattle and Hum'}// Elvis运算符,表达式判断disc.title是否为null,若是null的话,计算结果是 后面的字符串;
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'} // 判断一个字符串是否包含有效的邮件地址;
#{jaychou.songs[4].title}//计算songs集合中第5个(基于零开始)元素的title属性;(干货——SpEL 计算集合时的start index等于0);
#{jaychou.songs[T(java.lang.Math).random() * jaychou.songs.size()].title} // 随机取集合某个下标的song的title属性;
#{'This is a test'[3]} //返回第4个字母(s);
#{jaychou.songs.?[album eq '十二新作']}//SpEL 提供了查询运算符,用于对集合的过滤,得到集合的一个子集;(返回jaychou的十二新作专辑下的所有songs)
10.1)SpEL 提供了两个查询运算符: ".^[]", ".$[]";分别用于查询第一个匹配项和最后一个匹配项;#{jaychou.songs.^[album eq '十二新作']}//查找列表中第一个属于十二新作专辑的歌曲;
10.2) SpEL提供了投影运算符:(.![]),它会从集合中的每个成员中选择特定的属性放到另一个集合中;#{jaychou.songs.![title]}//将title属性投影到一个新的String类型的集合中;
10.3)投影操作可以和其它任意的 SpEL 运算符一起使用;#{jaychou.songs.?[album eq '十二新作'].![title]} // 获得十二新作专辑下的所有歌曲名称;
// 难道没有发现,上述表达式等同于 select ... where...
spring(3)高级装配相关推荐
- Spring之高级装配(二)
上一节提到Spring之装配bean(一),我们已经了解到了装配的基础知识,这部分是更为高级的bean装配技术. 高级装配内容: spring profile 有条件的bean 处理自动装配的歧义性 ...
- 《Spring实战》读书笔记-第3章 高级装配
<Spring实战>是学习Spring框架的一本非常经典的书籍,之前阅读了这本书,只是在书本上写写画画,最近整理了一下<Spring实战>的读书笔记,通过博客的方式进行记录分享 ...
- 《Spring实战》读书笔记-第3章 高级装配,zookeeper原理图
文章目录 3.1 环境与profile 3.2 条件化的bean 3.3 处理自动装配的歧义性 3.4 Bean的作用域 3.5 运行时植注入 3.6 小结 本章内容: Spring profile ...
- 《Spring实战》读书笔记-第3章 高级装配,字节跳动四面技术面
当自动装配bean时,遇到多个实现类的情况下,就出现了歧义,例如: @Autowired public void setDessert(Dessert dessert) { this.dessert ...
- 《Spring实战》读书笔记-第3章 高级装配,最新Java大厂高频面试题
<jee:jndi-lookup id="dataSource" lazy-init="true" jndi-name="jdbc/myData ...
- Spring Boot自动装配原理详解
目录 1.环境和依赖 1.1.spring boot版本 1.2.依赖管理 2.自动装配 2.1.流程概述 2.2.三大步前的准备工作 2.2.1.注解入口 2.2.2.获取所有配置类 2.3.获取过 ...
- spring Bean自动装配
spring Bean自动装配 自动装配是使用spring满足bean依赖的一种方式. spring会在应用上下文中为某个bean寻找其依赖的bean. spring自动装配需要从两个角度来实现,或者 ...
- (Spring)自动装配bean
文章目录 自动装配bean 1. 环境搭建 2. byName自动装配 3. byType自动装配 4. 使用注解自动装配 4.1 @Autowired和@Qualifier 4.2 @Resourc ...
- Spring autowire 自动装配简介
Spring autowire 自动装配简介 注意本文与一般spring 标注@Autowire 无关 如下例子定义了3个类 Dperson Dcar & Daddress 其中Dperson ...
最新文章
- 在ubuntu12.04 的QT安装
- highcharts使用教程
- 第三章:3.5 傅里叶变换
- SQL update select结合语句详解及应用
- 二、Go语言基础入门
- [转]Android中dp,px,sp概念梳理以及如何做到屏幕适配
- Ubuntu 更改ROOT密码的方法
- 机器人 工具坐标系的标定
- 在批处理文件中启动MediaPlayer播放制定文件
- Python成长之路_装饰器
- Qt之布局管理——分割窗口
- 谷歌,IE,火狐浏览器内核
- 开心电视助手v3.8最新绿色版,安卓设备远程管理工具神器
- 谈谈面向对象分析和设计
- 固体发动机内弹道matlab,固体火箭发动机内弹道性能的仿真研究
- matlab矩阵特征值分解,矩阵特征值分解与奇异值分解含义解析及应用
- linux音频设备接口,OSS--跨平台的音频接口简介
- P4147 玉蟾宫 题解
- android ndk开发
- 项目新增commitLint 和 husky 步骤
热门文章
- Codeforces Round #703 (Div. 2) B.Eastern Exhibition 中位数结论
- 牛客挑战赛47 D Lots of Edges(最短路+递归枚举子集)
- 牛客题霸 [没有重复项数字的所有排列] C++题解/答案
- [luogu-P4299] 首都(并查集 + LCT动态维护树的重心 / 维护虚儿子信息)
- [AtCoder Beginner Contest 215] A-G题解
- 周末狂欢赛1(玩游戏/Game,函数,JOIOI王国)
- 不止代码:迷宫问题(bfs)
- P1082-扩欧模板同余方程【扩欧,数论】
- 【模拟】【递归】电子表格(jzoj 2127)
- 2017-2018 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2017)