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的配置,可以XMLJavaConfig注解

一般推荐使用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。  
原文地址:https://www.cnblogs.com/larryzeal/p/5418338.html