Spring in Action 4th 学习笔记
约定:
一、@Xxx Class 表示被@Xxx注解的类。同理还有@Xxx注解的字段或方法。
例如:@Bean Method。
二、@Component Class 同时代指 @Controller、@Service、@Repository。
All beans in a Spring application context are given an ID. -- 如果不指明,也会给定一个默认的ID:类名,首字母小写。
【】上面这句,不适合XML方式,因为XML方式的默认ID是全路径再加上#{n} ,例如 a.b.c.D#2 。
Spring的配置,可以XML、JavaConfig和注解。
一般推荐使用JavaConfig和注解,因为JavaConfig可以保证类型一致,重构重命名都不会影响。
@RunWith(SpringJUnit4ClassRunner.class) //测试开始时,会自动创建Spring的ApplicationContext。 @ContextConfiguration(classes=CDPlayerConfig.class) // 从什么地方加载JavaConfig。默认导包。
【】Bean在第三方库中时,无法使用注解扫描导入~
这时,可以使用JavaConfig或者XML配置。但是,建议使用JavaConfig配置,因为更强大,且类型有保证。
JavaConfig is configuration code。就是说,它不应该包含任何业务代码,也不应该被任何业务代码包含。
【】@Autowired 可以用在任何方法上,本质都是注入形参。
【】@Autowired(required=false) 这样注入失败不会异常! 但是需要判断null异常。
【】问题:如果有多个bean,那怎么选择?同时满足也会异常,需要窄化范围,见后面。
@Component 基本等同于 @Named (JSR330)
@Autowired 基本等同于 @Inject (JSR330)
纯JavaConfig:
JavaConfig 就是@Configuration Class。
如果JavaConfig需要自动扫描并创建Bean,那就再加上@ComponentScan注解。
注意,这里的扫描对象是@Component Class 。
而且,自动生成的Bean,其ID就是类名,首字母小写。(XML则是全路径再加上#{n} ,例如 a.b.c.D#2 。)
示例如下:
@Configuration // 声明这是JavaConfig,Spring会自动加载 @ComponentScan() // 自动扫描,需要指定路径,稍后详解 public Class MyConfig{ }
如果JavaConfig不需要自动扫描,那就需要在JavaConfig类中设置@Bean Method。示例如下:
@Configuration // 声明这是JavaConfig,Spring会自动加载 public Class MyConfig{ @Bean(name="youSpecifiedID") public MyType myType(){ return new MyType(); // 看这里,创建对象!!! } }
继续,这种情况下(不自动扫描),怎么解决依赖注入的问题?最简单的办法如下:
@Configuration public Class MyConfig{ @Bean(name="youSpecifiedID") // 默认ID是类型首字母小写 public TypeA typeA(){ return new TypeA(); } @Bean() public TypeB typeB(){ return new TypeB( typeA() ); // 最简单直接的办法,就是调用@Bean Method } }
注意,上面这样直接调用@Bean Mehtod的方式,不会每次都调用@Bean Method!Spring会拦截该调用,而直接注入Bean(这里是typeA)。
JavaConfig还可以导入其他的JavaConfig或XML,如下:
@Configuration @import( Config1.class, Config2.class ) // 导入任意多个其他的JavaConfig @importResource( "classpath:xxx.xml" ) // 导入XML public Class ConfigAll{ /* your Code here */ }
XML导入XML,使用<import resource="another.xml" />。
XML导入JavaConfig,使用<bean class="xx.xx.JavaConfig" />。这样Spring创建该bean的时候会自动扫描并导入相关数据。
【】【】XML配置,可以使用spring tool suite (https://spring.io/tools/sts)
该工具还可以检查大多数XML的配置。
【】【】XML的依赖注入。
【】【】构造方法:<bean ...><constructor-arg ref="refID" /></bean> 注意引用是ref,如果是字面值,使用value!!!如 value="Hello"。
传入null:<constructor-arg><null/></constructor-arg>
【】【】c名称空间:<bean ... c:形参名-ref="refID" /> 注意,如果是字面值,使用【c:形参名】即可。形参位置也一样。
注意,需要导入名称空间。
形参名不方便,如果压缩文件,会破坏形参名。
可以使用形参位置【_n】。如果只有一个形参,n可以省略。
【】集合List和Set
<list>
<value></value>
<value></value>
</list>
<list>
<ref bean="refID"/>
<ref bean="refID"/>
</list>
【】property
【】p名称空间。
【】util名称空间。
<util:constant> References a public static field on a type
<util:list> java.util.List
<util:map> java.util.Map
<util:properties> java.util.Properties
<util:property-path> References a bean property (or nested property)
<util:set> java.util.Set
高级注入
不同的环境下可能需要注入不同的Bean,例如DataSource,有dev、qa和prod环境,该怎么解决?
笨办法:配置不同的XML,在编译时选择使用哪个。(例如,可以通过Maven Profiles选择。)
缺点:不同的环境需要不同的编译!!
Spring的解决方案:在运行时决定选择使用哪个。
找出需要选择的Bean,放到一个或多个Profile中,然后Spring根据不同的Profile自动选择创建的Bean。
JavaConfig中:
① 在不自动扫描的JavaConfig中,可以使用@Profile注解到@Bean Method上。
② 在自动扫描的JavaConfig中,需要将@Profile注解到@Component Class上。
注意:没有被@Profile的@Bean Method或@Component Class,各种环境均被创建!!!
在XML中:
① <beans ... profile="dev">
注意,是beans,根节点。这样当前XML中的所有Bean都在dev Profile下创建。
② <beans profile="dev"><bean/>...<bean/></beans>
<beans profile="prod"><bean/>...<bean/></beans>
注意,这里不是根节点。
Spring怎么判断Profile?
两个地方:spring.profiles.default, spring.profiles.active
Spring会先检查spring.profiles.active的值,有则用;没有再去检查spring.profiles.default的值,有则用,无则没有Profile。
Spring怎么去设置Profile?
There are several ways to set these properties: As initialization parameters on DispatcherServlet As context parameters of a web application As JNDI entries As environment variables As JVM system properties Using the @ActiveProfiles annotation on an integration test class
例如,在web.xml中 <!-- 【】设置context的默认profile --> <context-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </context-param> <!-- 【】设置servlet的默认Profile --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class> org.springframework.web.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> 上面,设置了default Profile,给dev用。其他情况下,可以通过设置active来覆盖default。
测试时,可以通过@ActiveProfiles("dev")注解到测试类上,设置并激活spring.profiles.active。
【】上面是Spring3的方式,Spring4提供了更强大的方式。
@Conditional 注解到@Bean Method上面。
@Conditional(ConditionImpl.class) 【】这里的ConditionImpl就是Condition接口的实现类,只有一个方法,就是match,用于判断是否符合条件。
public interface Condition { boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata); }
可以通过ConditionContext实参获取Environment对象,从而判断是否包含指定的Property。
public class MagicExistsCondition implements Condition { public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment env = context.getEnvironment(); return env.containsProperty("magic"); //【】判断环境中是否包含magic Property。 } }
只有符合的情况下,被@Conditional注解的@Bean method才会创建Bean。
【】注意,还可以通过实参(ConditionContext和AnnotatedTypeMetadata),获取其他对象,从而用于判断。
【】Spring4,@Profile已经被重构,新版基于@Conditional实现。
【】【】解决NoUniqueBeanDefinitionException问题。
【】【】办法一:指定primary bean。
@Primary 注解到@Component Class或者 @Bean Method上。
<bean ... primary="true"/>
【】注意,一种类型只能指定一个。
【】缺点:不能自由选择!
【】【】办法二:@Qualifier 注解到@Autowired或者@Inject。
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
【】说明,@Qualifier可以注解到@Component上,意思是将Bean的qualifier设为注解的值。如无该注解,默认类名首字母小写。
【】说明,@Qualifier也可以注解到@Bean Method上。相当于注解到@Component Class上。
因为@ComponentScan时使用@Component,没有@ComponentScan时则使用@Bean。
【】【】默认,spring中所有bean都被创建为singleton。(单例)。
spring scope:singleton、prototype、session(web)、request(web)。
@Scope("prototype") 注解到@Component上,或<bean scope="prototype">。
@Scope("prototype") 注解到@Bean上。
【】SSH,应对app请求,无状态,可以考虑request scope。
【】购物车,可以使用session scope。
【】【】想知道的是,application context中,如何保证 bean id 不冲突??
@Service都是单例,实际上,也是因为它都是以形参接收参数吧。且类的属性都是单例的。
给@Service@Scope("singleton")类中的方法@Autowired注入@Scope("session")的bean时:
① @Service一开始就被spring创建,而@Scope("session")则等到有请求时才会被创建,之前怎么办?
② @Scope("session")的bean,应该是每个session一个,而@Service只有一个,应该怎么选择?
【】这时,可以使用代理,让spring注入代理,然后根据实际情况选择调用的bean。这就是@Scope的属性proxyMode。
当proxyMode=ScopedProxyMode.INTERFACES时,只能代理接口的实现。
当proxyMode=ScopedProxyMode.TARGET_CLASS时,使用CGLIB,可以代理任何类。
注解时:@Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
XML时: <bean ... scope="session"></bean>
注意,XML时,默认使用CGLIB代理,如果想使用JDK代理, <aop:scoped-proxy proxy-target-class="false" />
注意,XML使用proxyMode,需要导入aop空间。
【】【】【】一些值,不想硬编码,而是在运行时载入。
Spring有两种办法:property placeholders、Spring Expression Language (SpEL)。二者类似,但用途和表现不同。
【】【】理解什么是placeholder propery:${ xxx } 这里${} 就是placeholder,里面的xxx就是property。
property placeholders: 该方法是从一个source加载property到Environment对象中,然后就可以使用Environment对象调用加载的property。 【】@PropertySource("classpath:/com/soundsystem/app.properties") 可以注解到JavaConfig中。 然后通过env.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) // 【】注意,可以设置默认值 另外,如果要求必须提供某个property,那可以使用env.getRequiredProperty(key),这样,如果不提供,就会异常。 另外,如果想检查某个property是否存在,可以使用env.containsProperty(key)。 另外,如果想将某个property转成类对象,可以使用env.getPropertyAsClass(key,xx.class) 除了property,Environment同样提供了获取Profile的方法。 String[] getActiveProfiles() String[] getDefaultProfiles() boolean acceptsProfiles(String... profiles) //如果Environment支持给定的Profiles,就true。
除了Environment,还可以使用${key}的方式注入。
【】【】【】使用@Value("${key}")注解。【注意与使用@Value("#{}")的区别】
public BlankDisc(@Value("${disc.title}") String title, @Value("${disc.artist}") String artist) { this.title = title; this.artist = artist; }
使用这种方式(@Value("${key}"))时,需要配置PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。
从3.1开始,推荐使用PropertySourcesPlaceholderConfigurer bean。
在JavaConfig中配置:
@Bean public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); }
在XML中配置:
<context:property-placeholder /> 注意,直接返回PropertySourcesPlaceholderConfigurer bean。
【】【】Spring Expression Language (SpEL)
The first thing to know is that SpEL expressions are framed with #{ ... }, much as property placeholders are framed with ${ ... }.
SpEL使用#{ ... }。
#{ 12 } 数字 #{ 9.8E4 } 98000 #{ 'abc你好' } String #{ true } #{ T(System).currentTimeMillis() } 【】T(System) 意思是导入java.lang.System。 #{ beanID.property } 【】根据beanID调用某个bean的属性 #{ beanID.method() } 【】根据beanID调用某个bean的method() #{ beanID.getName().toUpperCase() } 【】这里假定beanID对应的bean有getName()方法,且返回String,那可以继续调用toUpperCase()方法。 但是,如果返回null,那就异常了!怎么解决? 解决办法:type-safe operator 【?.】 #{ beanID.getName()?.toUpperCase() } 这样,如果getName()返回null,整体就返回null。 #{jukebox.songs[4].title}
【】调用集合或数组
【】SpEL针对操作集合或数组,提供了一个选择操作符。【.?[]】
用法:#{jukebox.songs.?[artist eq 'Aerosmith']} 集合或数组中的元素的artist属性值为"Aerosmith"的,返回列表。
另外,针对操作集合或数字,还提供了【.^[]】【.?[]】 。前者是选择第一个匹配的元素,后者是选择最后一个匹配的元素。
用法:#{jukebox.songs.^[artist eq 'Aerosmith']} #{jukebox.songs.$[artist eq 'Aerosmith']}
另外,还提供了一个projection operator 【.![]】,该操作符用于投影符合的元素属性到一个新的集合。
用法:#{jukebox.songs.![title]}
【小结】 SpEL针对集合或数组的操作: .?[] 选择所有匹配的元素(集合); .^[] 选择第一个匹配的元素; .$[] 选择最后一个匹配的元素。 .![] 选择属性,投影到新集合。 【】这些操作可以组合!!!就是链式编程而已。
【】【】由于都是String,所以,尽量少用 简用 SpEL!
【】【】【】使用@Value("#{}")注入【注意与使用@Value("${key}")的区别】
public BlankDisc( @Value("#{systemProperties['disc.title']}") String title, @Value("#{systemProperties['disc.artist']}") String artist) { this.title = title; this.artist = artist; }
【】【】【】XML中使用SpEL。 ① <property ... value="#{}"> ② <constructor-arg value="#{}"> ③ p-namespace ④ c-namespace entry
【】如果想调用Class,需要使用T() operator。T()操作符主要用于调用静态方法和常量!!! 【】【】SpEL操作符的分类: 数学计算【+, -, *, /, %, ^】 比较【<, lt, >, gt, ==, eq, <=, le, >=, ge】 逻辑【and, or, not】 条件【?: (ternary 三元运算符), ?: (Elvis)】 正则【matches】 #{scoreboard.score > 1000 ? "Winner!" : "Loser"} #{disc.title ?: 'Rattle and Hum'} 【】变种用法,如果null,则返回'Rattle and Hum'。Elvis operator。
posted on 2016-04-22 12:46 LarryZeal 阅读(1256) 评论(0) 编辑 收藏 举报