Bootstrap

Spring实战(第四版)阅读笔记

Spring

0.测试

对于组件扫描的测试

  • JAVA
package soundsystem;

import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

// 告诉JUnit使用 SpringJUnit4ClassRunner 作为测试运行器。
// 该运行器会在测试类启动时加载Spring应用上下文,从而使得我们可以使用Spring的依赖注入、配置文件等功能。
@RunWith(SpringJUnit4ClassRunner.class)
// 告诉Spring如何加载应用上下文。
@ContextConfiguration(classes = CDPlayerConfig.class)
// 在这里,它指定了一个Java配置类——CDPlayerConfig,用于初始化Spring上下文。Spring根据CDPlayerConfig类中的配置来创建和管理Bean。
public class CDPlayerTest {
    
    // 标记该变量需要自动装配
    @Autowired
    private CompactDisc cd;

    @Test
    public void cdShouldNotBeNull() {
        //断言验证是否成功注入
        assertNotNull(cd);
    }
}

1.Bean的装配

可选方法

自动化装配

  • 使用@Component来标识这个类是一个Bean
  • 可以使用@Component(“Bean’s Name”) 来显式的指定这个Bean的id,如果我们不进行显式的指定,比如这个类叫 Ice 那么它的id默认为 ice(也就是类名的第一个字母变为小写)
    • JAVA
        com.cheng.pojo
    
        // 给这个组件命名为ICEEEEEE
        @Component("ICEEEEEE")
        public class Ice{
          private Double temperature;
          public bool cold(){
              if( temperature < -10)
              return true;
              return false;
          }
        }
    
  • 使用@ComponentScan来告诉Spring应该在哪些地方进行扫描(即Bean应该在哪找) 如果不给他设置任何属性,它会默认以配置类所在的包作为基础包来扫描组件
    • JAVA
        com.cheng.config
    
        // 这个注解说明它是一个配置类
        @Configuration
        // 这个注解说明它要开启组件扫描,但是要注意,因为没给他配置任何属性,他只会在com.cheng.config包里扫描
        @ComponentScan
        public class WeatherConfig{
    
        }
    
    • XML
        <?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/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
        <!--  开启组件扫描,这里显式的指明了要使用soundsystem作为基础包扫描  -->
        <context:component-scan base-package="soundsystem"/>
        </beans>
    
  • 给@ComponentScan设置基础包
    • JAVA
        // 这样是给@ComponentScan 的value 属性指定了值
        @ComponentScan("pojo")
        // 指定基础包, 可以指定多个基础包
        @ComponentScan(basePackages="pojo")
        // @ComponentScan(basePackages={"pojo", "servlet"})
        // 上面的方法所设置的基础包都是以String类型表示的,这种方法是类型不安全的,如果重构代码,就可能会出现错误
        // 还可以指定为包中所包含的类或接口,这些类所在的包将会作为组件扫描的基础包
        @ComponentScan(basePackageClasses={Ice.class, LoginServlet.class})
    
  • 使用@Autowired注解实现自动装配(该注解不仅可以使用在类的构造器和Sette方法上,实际上,它可以用在类的任何方法上,只要这个方法需要一个Bean)
    • JAVA
        @Component
        public class CDPlayer implements MediaPlayer {
            private CompactDisc cd; 
    
            @Autowired
            public CDPlayer(CompactDisc cd) {
              this.cd = cd;
            }
    
            @Autowired
            public void setCompactDisc(CompactDisc cd) {
              this.cd = cd;
            }
        }
    
  • 标记有@Autowired的方法,Spring都会尝试满足方法参数上所声明的依赖,加入有且只有一个Bean匹配依赖需求的话,那么这个Bean将会被装配进来,但是要注意,Spring默认情况下是单例的
  • 如果没有匹配的Bean,那么在Spring的应用上下文创还能得时候,Spring将会抛出一个异常,如果我们允许不强制需要Bean来注入,可以将@Autowired的require属性设置为false

XML装配

  • 创建XML配置规范
    • 最为简单的SpringXML配置如下所示
      • XML
          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/context">
      
          <!-- 在这里编写详细配置 -->
      
          </beans>
      
    • 声明一个简单的bean
      • XML
          <bean class="soundsystem.SgtPeppers" />
      
      • 这里创建这个bean的类是通过class属性来指定的,并且要使用全限定的类名
      • 因为没有明确给定id, 所以这个bean将会根据全限定类名来进行命名,在本例中,bean的id将会是"soundsystem.SgtPeppers#0"
      • 显然,这样的名字对于我们稍后要进行的应用操作是没有什么用处的,因此我们最好给我们的bean指定一个名字
        • XML
            <bean id="compactDisc" class="soundsystem.SgtPeppers" />
        
        • 稍后将这个bean装配到别的bean之中时,将会用到这个具体的名字
        • 要注意,在基于XML的配置中,我们不再直接负责创建SgtPeppers的实例,在基于JavaConfig的配置中,我们是需要这么做的
        • 在基于XML的配置中,当Spring发现这个bean元素时,它将会调用SgtPeppers的默认构造器来创建bean
        • 并且,如果当你重命名了类,这里就会找不到对应的类,Spring将会报错
    • 借助构造器注入初始化bean
      • 使用<constructor-arg>元素
        • 将对象的引用装配到依赖于它们的其他对象之中
          • 假设我们已经声明了SgtPeppers bean,并且这个类实现了CompactDisc接口
            • 我们的CDPlayer bean需要一个CompactDisc作为参数进行装配
              • XML
                  <bean id="cdPlayer" class="soundsystem.CDPlayer">
                     <!-- 这里,通过使用ID来指明要使用的是compactDisc bean,并且ref告知Spring要将一个bean的引用传递到CDPlayer的构造器中 -->
                     <constructor-arg ref="compactDisc" />
                  </bean>
              
        • 将字面量注入到构造器中
          • 假设我们现在有一个新的实现类
            • JAVA
                public class Novel {
                   private String title;
                   private String artist;
            
                   public Novel(String title, String artist) {
                       this.title = title;
                       this.artist = artist;
                   }
                }  
            
          • 我们来对其进行构造器注入
            • XML
            <bean id="novel" class="com.cheng.pojo.Novel">
                <constructor-arg value="The Great Gatsby"/>
                <constructor-arg value="F. Scott Fitzgerald"/>
            </bean>
            
            • 注意,这里我们没有使用ref,而是使用了value属性,该属性表明给定的值要以字面量的形式注入到构造器之中
        • 将集合注入到构造器中
          • 同样,我们假设现在有了一个新的实现类
            • JAVA
                public class GroceryList {
                  String detail;
                  private List<String> items;
              
                  GroceryList(String detail, List<String> items) {
                      this.detail = detail;
                      this.items = items;
                  }
                }
            
          • 显然,在注入时,我们必须要提供一个商品列表
          • 简单的,我们可以将列表设置为null
            • XML
                <bean id="grocerylist" class="com.cheng.pojo.GroceryList">
                    <constructor-arg value="nothing important." />
                    <constructor-arg><null></constructor-arg>
                </bean>
            
          • 当然,我们也可以给一些有效的值
            • XML
                <bean id="grocerylist" class="com.cheng.pojo.GroceryList">
                    <constructor-arg value="nothing important." />
                    <constructor-arg>
                        <list>
                            <value>Apple</value>
                            <value>Banana</value>
                        </list>
                    </constructor-arg>
                </bean>
            
          • 显然,你还可以使用ref元素替代value,实现bean应用列表的装配
          • 假设有这么一个实现类
              public class Library {
                  private List<Novel> novels;
                  
                  Library(List<Novel> novels) {
                      this.novels = novels;
                  }
              }
          
          • 构造器注入
            • XML
                  <bean id="library" class="com.cheng.pojo.Library">
                      <constructor-arg>
                          <list>
                              <ref bean="novel" />
                              <ref bean="novel" />
                          </list>
                      </constructor-arg>
                  </bean>
              
      • 使用c-命名空间
        • 将对象的引用装配到依赖于它们的其他对象之中
          • 首先,要使用c-命名空间,需要在XML的顶部声明其模式,如下所示
            • XML
                <?xml version="1.0" encoding="UTF-8"?>
                <beans xmlns="http://www.springframework.org/schema/beans"
                xmlns:c="http://www.springframework.org/schema/c"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans.xsd">
                </beans>
            
          • 在c-命名空间和模式声明之后,我们就可以使用它来声明构造器参数了,如下所示
            • XML
                <bean id="cdPlayer" class="soundsystem.CDPlayer"
                      c:cd-ref="compactDisc" />
            
            • 这里解释一下这个属性名是如何组成而成的
              c-命名空间前缀     构造器参数名   注入bean引用    要注入的bean的ID
              c               :      cd     -    ref     =   "compactDisc"
            
            • 在这里,我们可以将构造器参数名改成使用参数在整个参数列表中的位置信息
              • XML
                  <!-- 这里,_0标记这个参数是构造器中的第一个参数 -->
                  <bean id="cdPlayer" class="soundsystem.CDPlayer"
                        c:_0-ref="compactDisc" />
              
        • 将字面量注入到构造器中
          • 对于上面的例子中的Novel类,使用c-命名空间进行构造器参数的注入,应该是这样的
            • XML
                <bean id="novel" 
                      class="com.cheng.pojo.Novel"
                      c:_0="The Great Gatsby"
                      c:_1="F. Scott Fitzgerald"/>
                </bean>
            
          • 可以看到,装配字面量于装配引用的区别在于属性名中去掉的-ref后缀
        • c-命名空间无法将集合装配到构造器参数中
    • 借助Setter方法实现属性注入
      • 使用<property>元素
        • 假设我们现在有一个新的实现类
          • JAVA
              public class Novel {
                  private String title;
                  private String artist;
          
                  public Novel() {
                  }
          
                  public void setTitle(String title) {
                      this.title = title;
                  }
          
                  public void setTitle(String title) {
                      this.artist = artist;
                  }
              }  
          
        • 我们来对其进行构造器注入
          • XML
              <bean id="novel" class="com.cheng.pojo.Novel">
              <property name="title" value="The Great Gatsby"/>
              <property name="artist" value="F. Scott Fitzgerald"/>
              </bean>
          
          • <property>元素为属性的Setter方法提供的功能和<constructor-arg>为构造器所提供的功能是一样的
          • name属性指定了使用哪个setter方法,在这里,title表示使用setTitle()方法
      • 使用p-命名空间
        • 首先,要使用p-命名空间,需要在XML的顶部声明其模式,如下所示
          • XML
                <?xml version="1.0" encoding="UTF-8"?>
                <beans xmlns="http://www.springframework.org/schema/beans"
                xmlns:p="http://www.springframework.org/schema/p"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans.xsd">
                </beans>
            
        • 我们可以使用p-命名空间,按照以下的方式装配title和artist属性
        • XML
            <bean id="novel"
                  class="com.cheng.pojo.Novel"
                  p:title="The Great Gatsby"
                  p:artist="F. Scott Fitzgerald" />
        
      • 由于通过Setter方法实现属性属性注入的流程和构造器方法是类似的,这里不再赘述

JAVA装配

  • 使用JavaConfig显式装配Spring
    • 创建JavaConfig类的关键在于为其添加@Configuration注解,这个注解表明这个类是一个配置类
      • 因为这节我们关注于显式配置,因此我们移除@ComponentScan注解,但要注意,自动化配置和XMl配置和Java配置是可以共存的。
        • 显式声明Bean
          • 声明简单的Bean
            • JAVA
                @Bean
                public CompactDisc sgtPeppers() {
                    return new SgtPeppers();
                }
            
            • 默认情况下Bean的id和带有@Bean注解的方法名是一样的,在本例中bean的名字将会是sgtPeppers,如果你想给他设置成一个不同的名字的话,可以通过name属性来进行指定
            • @Bean(name=“lonelyHeartsClubBand”)
            • 在这里,我们可以发挥Java提供的所有功能,只要最终生成一个CompactDisc实例即可
        • 借助JavaConfig实现注入
          • 引用创建Bean的方法
            • JAVA
                @Bean
                public CDPlayer cdPlayer() {
                   return new CDPlayer(sgtPeppers());
                }
            
            • 这个Bean和sgtPeppers稍微有些区别,在这里并没有使用默认的构造器来构建实例,而是调用了需要传入CompactDisc对象的构造器来创建CDPlayer实例
            • 看起来,CompactDisc是通过调用sgtPeppers()得到的,但情况并非完全如此,因为sgtPeppers()方法上添加了@Bean注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的Bean,而不是每次都对其进行实际的调用、
            • 而且Spring默认是单例模式,也就是每一个cdPlayer获得的CD光盘都是完全一样的。
            • 这种形式还有另一种理解起来比较简单的方式
            • JAVA
                @Bean
                // 这里实际的意思是,当你(也就是一个Bean)需要某些东西(可以是另外一个Bean)时,他会自动装配这个Bean到这个方法中,然后,方法体就可以按照合适的方式是来使用它(它需要的Bean,也就是它的依赖)
                // 而且不用明确应用CompactDisc的@Bean方法(也就是不用像上例那样)
                public CDPlayer cdPlayer(CompactDisc compactDisc) {
                   return new CDPlayer(compactDisc);
                }
            
            • 依赖于这种方式,你可以将配置分散到多个配置类,XML,以及自动扫描和装配Bean中,不管CompactDisc是采用什么方式创建出来的,只要Spring中托管的Bean可以返回一个CompactDisc,它就可以将它提供给需要它的Bean
            • 当然,你也可以使用Setter风格的DI配置
            • JAVA
                @Bean
                public CDPlayer cdPlayer(CompactDisc compactDisc) {
                   CDPlayer cdPlayer = new CDPlayer(compactDisc);
                   cdPlayer.setCompactDisc(compactDisc);
                   return cdPlayer;
                }
            
            • 最重要的是,带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例,这里所存在的可能性仅仅受到Java语言的限制

导入和混合配置

  • 1.在JavaConfig中引用XML配置
    • 假设我们现在有三个JavaConfig配置类
      • JAVA: CDConfig
          @Configuration
          public class CDConfig {
              @Bean
              public CompactDisc compactDisc() {
                  return new SgtPeppers();
              }
          }
      
      • JAVA: CDPlayerConfig
          @Configuration
          // 这里可以直接使用@Import注解导入CDConfig。但是这里我们采用一种更好的方法
          public class CDPlayerConfig {
                 @Bean
                 public CDPlayer cdPlayer(CompactDisc compactDisc) {
                     return new CDPlayer(compactDisc);
                 }
          }
      
      • JAVA: SoundSystemConfig
          @Configuration
          @Import({CDPlayerConfig.class, CDConfig.class})
          public class SoundSystemConfig {
          }
      
      • 这样,我们将所有的配置类都组合到了更高级别的SoundSystemConfig中
      • 现在,基于某种原因,我们通过XML配置了一个BlankDisc, 如下所示
        • XML: cd-config.xml
            <bean id="compactDisc" 
                  class="soundsystem.BlankDisc"
                  c:_0="Sgt. Pepper's Lonely Hearts Club Band" 
                  c:_1="The Beatles">
                  <constructor-arg>
                     <list>
                        <value>Sgt. Pepper's Lonely Hearts Club Band</value>
                        <value>With a Little Help from My Friends</value>
                        <value>Lucy in the Sky with Diamonds</value>
                        <value>Getting Better</value>
                        <value>Fixing a Hole</value>
                     </list>
                  </constructor-arg>
            </bean>
        
      • 如何让Spring同时加载它和其他基于Java的配置呢?
        • 答案是使用@ImportReource注解
        • 我们可以修改SoundSystemConfig
          • JAVA: SoundSystemConfig
              @Configuration
              // 这里没有导入CDConfig.class,防止出现冲突(即有多个可选的Bean)
              @Import({CDPlayerConfig.class})
              @ImportResource("classpath:cd-config.xml")
              public class SoundSystemConfig {
              }
          
      • 这样,BlankDisc将会装配到CDPlayer中,此时与它是通过XML配置的没有任何关系
  • 2.在XML配置中引用JavaConfig
    • 在XML中引入XML可以使用<import>元素
      • XML
          <import resource="cd-config.xml" />
      
    • 在XML中引入Java配置可以使用<bean>元素
      -XML
          <bean class="soundsystem.CDConfig" />
      

2.高级装配

Spring profile

  • 如果要在不同的环境(dev,prod,test)中配置使用不同的Bean,可以配置Spring profile
    • 声明Bean所在的环境,只需使用@Profile注解,或者通过<beans>的profile属性。
      • JAVA
          @Configuration
          public class DataSourceConfig {
              
              //这里要注意的是Spring3.1之前,只能在类级别上使用@Profile注解,但从Spring3.2开始,可以在方法级别上使用@Profile注解
              @Bean
              @Profile("dev")
              public DataSource devDataSource() {
                  return devxxx;
              }
              
              @Bean
              @Profile("prod")
              public DataSource prodDataSource() {
                  return prodxxx;
              }
          }
      
      • 尽管这两个Bean都被声明在一个profile中,但是只有规定的profile激活时,响应的bean才会被创建
      • 没有指定profile的Bean始终都会被创建,与激活哪个profile没有关系
      • XML
          <?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/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/context
          http://www.springframework.org/schema/context/spring-context.xsd"
          profile="dev">
      
          <!-- 在这里填写要使用的Bean --> 
          </beans>
      
    • 那么,如何激活某个profile呢?
      • 关键有两个属性:spring.profiles.active 和 spring.profiles.default
        • 有多种方式来设置这两个属性
          • 作为DispatcherServlet的初始化参数
          • 作为Web应用的上下文参数
            • XML
                <?xml version="1.0" encoding="UTF-8"?>
                <web-app version="2.5"
                 xmlns="http://java.sun.com/xml/ns/javaee"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
                 <context-param>
                     <param-name>contextConfigLocation</param-name>
                     <param-value>/WEB-INF/spring/root-context.xml</param-value>
                 </context-param>
             
                 <!-- 为上下文设置默认的profile -->
                 <context-param>
                     <param-name>spring.profiles.default</param-name>
                     <param-value>dev</param-value>
                 </context-param>
             
                 <listener>
                     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
                 </listener>
             
                 <servlet>
                     <servlet-name>appServlet</servlet-name>
                     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
                     <init-param>
                         <!-- 为Servlet设置默认的profile -->  
                         <param-name>spring.profiles.default</param-name>
                         <param-value>dev</param-value>
                     </init-param>
                     <load-on-startup>1</load-on-startup>
                 </servlet>
            
                 <servlet-mapping>
                     <servlet-name>appServlet</servlet-name>
                     <url-pattern>/</url-pattern>
                 </servlet-mapping>
            
                </web-app>
            
            
          • 作为环境变量
          • 作为JVM的系统属性
          • 在集成测试类上,使用@ActiveProfiles注解设置
            • JAVA
                @RunWith(SpringJUnit4ClassRunner.class)
                @ContextConfiguration(classes={PresistenceTestConfig.class})
                @ActiveProfiles("dev")
                public class PersistenceTest {
                    ...
                }
            
    • 在条件化创建Bean方面,profile机制通过基于哪个profile处于激活状态来判断,而Spring4.0提供了一种更为通用的机制来实现条件化的Bean定义
    • 这就是使用@Conditional注解定义条件化的bean

条件化的bean声明

  • 可以给一个类设置@Conditional注解,注解中只需给定一个实现类Condition接口的类,@Conditional注解会自动调取Condition接口进行条件对比
    • JAVA
        @Bean
        @Conditional(MagicExistsCondition.class)
        public MagicBean magicBean() {
            return new MagicBean();
        }
    
    • JAVA: Condition 接口
        public interface Condition {
            boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata);
        }
    
    • JAVA: MagicExistsCondition类
        public class MagicExistsCondition implements Condition {
            public boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata) {
                // 检查环境中是否定义了magic属性,如果存在,则返回true
                Environment env = context.getEnvironment();
                return env.containsProperty("magic");
            }
        }
    
    • JAVA: ConditionContext接口
        public interface ConditionContext {
            // 借助返回的BeanDefinitionRegistry检查bean定义
            BeanDefinitionRegistry getRegistry();
            // 借助返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性
            ConfigurableListableBeanFactory getBeanFactory();
            // 借助返回的Environment检查环境变量是否存在以及他的值是什么
            Environment getEnvironment();
            // 读取并探查返回的ResourceLoader所加载的资源
            ResourceLoader getResourceLoader();
            // 借助返回的ClassLoader加载并检查类是否存在
            ClassLoader getClassLoader();
        }
    
    • AnnotatedTypeMetadata接口则能够让我们检查带有@Bean注解的方法上还有什么其他的注解。

自动装配的歧义性

bean的作用域

Spring表达式语言

;