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都是单例,实际上,也是因为它都是以形参接收参数吧。且类的属性都是单例的。
                给@[email protected]("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。  

<!-- 未完待续,回家吃饭了 -->

时间: 2024-08-01 14:31:45

Spring in Action 4th 学习笔记的相关文章

Spring in Action 4th 学习笔记 之 AOP

先说说为什么需要AOP 最简单的一个例子就是日志记录,如果想记录一些方法的执行情况,最笨的办法就是修改每一个需要记录的方法.但这,真的很笨... 好的方法,应该是通过反射获取方法,然后去匹配,如果需要记录日志,那就调用日志方法即可. 这就是AOP 的Weaving,俗称编织.织入,就是将需要添加的功能编织到现有功能中,而不需要修改现有代码. 另一个例子,不那么大众的需求:我想给一个对象添加方法,怎么实现? 如果有学过js.Python等动态语言,你肯定知道它们支持给对象添加方法,直接添加即可.

Spring In Action 4 学习笔记(一)Spring概览

Spring的核心概念就是DI和AOP,是Spring实现所有复杂华丽框架的基石. 相对于EJB等重型框架,Spring更加轻量化,可以强化普通的POJO对象. 1.简化JAVA开发 为了尽可能简化Java的开发,Spring遵循如下4个策略: Lightweight and minimally invasive development with POJOs 使用POJO类进行轻量化低侵入式的开发 Loose coupling through DI and interface orientati

Spring实战-Spring in Action, 4th Edition-2015年第4版本

In Action系列中最畅销的Spring图书,近十万读者学习Spring的共同选择!In Action系列中最畅销的Spring图书,有近10万读者选择本书来学习Spring! Spring框架已经成为Java开发人员的必备知识,而且Spring 3引入了强大的新特性,例如SpEL.Spring表达式语言.IoC容器的新注解以及用户急需的对REST的支持.无论你是刚刚接触Spring还是被Spring 3.0的新特性所吸引,本书都是掌握Spring的最佳选择. 下载地址: Spring in

[Spring Data Repositories]学习笔记--定义自己的repository

有时,我们会需要用到自己定义的一些查询方法,可以按照下面几步进行. 1. 定义一个包含该方法的接口 Interface UserRepositoryCustom { public void someCustomMethod(User user); } 2. 定义实现 class UserRepositoryImpl implements UserRepositoryCustom { public void someCustomMethod(User user){ //Your custom im

[Spring Data Repositories]学习笔记--为repository添加通用的方法

如果想把一个方法加到所有的repository中,用前一篇提到的方法就不合适了. 英文原版,请看 http://docs.spring.io/spring-data/data-mongo/docs/1.5.2.RELEASE/reference/html/repositories.html#repositories.custom-behaviour-for-all-repositories 1. 定义自己的repository,要从基础的repository进行继承. public interf

[Spring Data Repositories]学习笔记--使用现有的repository

以下内容是在学习Spring-Data-mongoDB中的Spring Data Repositories时做的一些笔记.备忘! 感觉学习还是看官方的资料比较透彻一些. Spring Data Repositories目的:减少重复的持久化代码. 常用的几个repository interface, Repository <-- CurdRepository <-- PagingAndSortingRepository 最后一个主要是用来做分页和排序用的. Repository使用步骤 1.

spring in action 4th --- quick start

读spring in action. 不是特别聪明的人,只好多练习,多实践.读书和思考很容易变得混乱,因为没有实践.因此,虽然花了很多时间,还是要动手记录下来. 环境搭建 quick-start 1.环境搭建 jdk1.8 gradle 2.12 Intelij idea 2016.2.1 1.1创建一个gradle项目 在idea中,new -> project -> gradle 创建一个空项目.创建成功后修改build.gradle : group 'com.test' version

spring in action 3学习(一)装配bean

从事java开发工作已有1年多,期间学了很多,各项技术学的很快,但,忘得更快.如今工作.生活各项都相对稳定,想好好巩固下基础,在此申请了博客园,打算留下学习的足迹.留背将来复习或回味. (一)声明一个简单bean public class Juggler implements Performer{ private int beanBags = 3; public Juggler() { } public Juggler(int beanBags) { this.beanBags = beanBa

Spring+SpringMVC+MyBatis集成学习笔记【一】

一,首先要清楚,SpringMVC其实就是Spring的一个组件       例如我们知道Spring中有类似于,AOP TX等等类似的组件,所以SpringMVC其实就是Spring的一个组件,是Spring框架的一部分,千万不要把SpringMVC当成是另一种框架!       所以在配置上,还是按照配置Spring的套路来,该配置监听,配置监听,该配置配置文件,配置配置文件,一切照旧 二,Spring在Web.xml中的配置       在Web.xml中配置Spring大致可以分为两点