Spring总结(3)

高级装配
1.Spring profile
  • 作用:能够在不同的环境下对应用程序执行不同的默认配置信息
  • 配置profile bean
    • 能够在指定的部署的环境中确定创建对应环境的bean
    • 将所有不同的bean定义并整理到一个或多个profile之中,并且在应用部署到每个环境的时候,激活对应的profile状态
    • Spring4可以支持@Bean与@Profile一同在Java配置中声明
  • 基于Java配置
@Configuration
public class DataSourceConfig{

    // 将Dev 和 Product的配置整合成一个Bean中进行配置

    @Bean(destroyMethod = "shutdown")
    @Profile("dev")
    public DataSource devDataSource() {
        // 开发环境
        System.out.println("创建开发环境的数据源");
        return null;
    }

    @Bean(destroyMethod = "shutdown")
    @Profile("production")
    public DataSource proDataSource() {
        // 生产环境
        System.out.println("创建生产环境的数据源");
        return null;
    }
 }
  • 基于xml配置
...
 <!--
        重复使用多元素来定义bean
    -->
    <beans profile="dev">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:sql/test.sql"></jdbc:script>
        </jdbc:embedded-database>
    </beans>

    <!--
        Qa测试环境
    -->
    <beans profile="qa">
        <bean id="datasource_qa" class="org.apache.commons.dbcp.BasicDataSource"
            destroy-method="close"
              p:url="jdbc:mysql://localhost:3306/study_mysql?useUnicode=true&amp;characterEncoding=UTF-8&amp;useServerPrepStmts=true&amp;prepStmtCacheSqlLimit=256&amp;cachePrepStmts=true&amp;prepStmtCacheSize=256&amp;rewriteBatchedStatements=true"
              p:driverClassName="com.mysql.jdbc.Driver"
              p:username="root"
              p:password="root123"
              p:initialSize="20"
              p:maxActive="2">
        </bean>
    </beans>


    <beans profile="production">
       <jee:jndi-lookup id="datasource_pro"
                        jndi-name="jdbc/mydb"
                        resource-ref="true"
                        proxy-interface="javax.sql.DataSource">
       </jee:jndi-lookup>
    </beans>
...
  • 激活profile
    • spring.profiles.active
    • spring.profiles.default
  • 设置这两个属性的方式
    • 作为DispatcherServlet的初始化参数
    • 作为Web应用的上下文参数
    • 作为JNDI条目
    • 作为环境变量
    • 作为JVM的系统属性
    • 在集成测试类上,使用@ActiveProfiles注解设置

条件化的bean声明

  • 作用:根据指定的条件化来创建bean
  • 实现:
    • 实现Spring的接口Condition
    • 在Java配置的bean中声明@Conditional
  • 代码示例:
// 配置bean
@Configuration
public class ConditionalConfig {

    @Bean
    @Conditional(StringConditional.class)
    public String getString(){
        return new String("conditional is true");
    }
}
// 声明条件
public class StringConditional implements Condition{

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return true;
    }
}
  • ConditionContext在spring中的源码
// spring4.0开始
public interface ConditionContext {
	// 返回的BeanDefinitionRegistry检查bean定义
	BeanDefinitionRegistry getRegistry();
	
	//返回的ConfigurableListableBeanFactory检查bean是 否存在,甚至探查bean的属性
	ConfigurableListableBeanFactory getBeanFactory();
	
	//返回的Environment检查环境变量是否存在以及它的值是什 么
	Environment getEnvironment();

	//读取并探查getResourceLoader()返回的ResourceLoader所加载的资源
	ResourceLoader getResourceLoader();

//借助getClassLoader()返回的ClassLoader加载并检查类是否存在
	ClassLoader getClassLoader();
}
  • AnnotatedTypeMetadata在spring的源码
// spring 4.0之后
public interface AnnotatedTypeMetadata {
	// 判断带有@Bean注解的方法是不是还有其他特定的注解
	boolean isAnnotated(String annotationName);
	
	// 判断给定类型注解的属性,即是属于元注解还是直接注解的修饰
	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);

}
自动装配的歧义性
  1. 歧义性:在spring容器中使用自动装配的方式来创建bean实例,而当有多个子类实现一个共同接口时,此时注入接口的实例对象无法知道对应于哪个子类实现
  2. 解决歧义性的方式
  • 标示首选的bean,使用@Primary,@Primary能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在Java 配置的bean声明中,但是无法将可选方案的范围限定到唯一一个无歧义性的选项 中。它只能标示一个优先的可选方案
// 摘录自《spring实战》
public interface Dessert {}

@Component
public class Cookie implements Dessert{}

@Component
@Primary	// spring会优先装配当前bean
public class Cake implements Dessert{}

// 自动装配的时候如果遇到相同的装配bean,将会以标示@Primary优先注入
@Configuration
@ComponentScan("com.dtrees.spring.core.assembly.identifier")
public class IdentifierBeanConfig {

    private Dessert dessert;

    @Autowired
    public void setDessert(Dessert dessert){
        this.dessert = dessert;
    }
}

// xml配置
<bean id="datasource_qa" class="org.apache.commons.dbcp.BasicDataSource"
            destroy-method="close"
		p:url="jdbc:mysql://localhost:3306/study_mysql?useUnicode=true&amp;characterEncoding=UTF-8&amp;useServerPrepStmts=true"
              p:driverClassName="com.mysql.jdbc.Driver"
              p:username="root"
              p:password="root123"
              p:initialSize="20"
              p:maxActive="2" 
              primary="true">
  • 限定自动装配的bean,@Qualifier注解是使用限定符的主要方式,注解所设置的参数就是想要注入的bean的ID,所引用的bean要具有String类型的“beanId”作为限定符
// 在对应的子类使用限定符指明
@Component
@Qualifier("cookie")
public class Cookie implements Dessert{}

@Component
//@Primary
@Qualifier("cake")
public class Cake implements Dessert{}

// 在配置bean的时候同样指明要注入的bean
@Configuration
@ComponentScan("com.dtrees.spring.core.assembly.identifier")
public class IdentifierBeanConfig {
    private Dessert dessert;
    @Autowired
    @Qualifier("cake")
    public void setDessert(Dessert dessert){
        this.dessert = dessert;
    }
}
  • 自定义限定符
    • 作用:解决多个bean具备相同的特性的问题,即有多个bean具有相同定义的@Qualifier,为了区分,将@Qualifier设定的修饰符拆分成为多个独立的特性,每个bean各个特性的组合(仅当一个特性除外)
// 使用自定义注解定义限定符
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {}

@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Sweet {}

....

// 通过自定义的限定符,可以进行自由组合,但是如果仅用一个来修饰bean的时候,建议不用自定义修饰符,比如这种情况便会出现错误:
@Component
@Cold
@Sweet
public class Cookie implements Dessert{} // 使用两个限定符修饰

@Component
@Sweet
public class Cake implements Dessert{}	// 使用一个限定符修饰

// test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = IdentifierBeanConfig.class)
public class DessertTest {
    @Autowired
    @Sweet   
    private Dessert dessert;
    /**
     * 这时候spring定位到有两个bean有相同的修饰符就会出错
     * NoUniqueBeanDefinitionException:
     * No qualifying bean of type 'com.dtrees.spring.core.assembly.identifier.Dessert' available: expected single matching bean but found 2: cake,cookie
     */
    @Test
    public void testDessert(){
        Assert.assertNotNull(dessert);
        System.out.println(dessert.getClass() == Cake.class);;
    }
}
bean的作用域
  1. 作用域
  • 单例(Singleton):在整个应用中,只创建bean的一个实例,spring默认作用域
  • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的 bean实例
  • 会话(Session):在Web应用中,为每个会话创建一个bean实例
  • 请求(Rquest):在Web应用中,为每个请求创建一个bean实例
  1. 实践
  • spring容器使用ConfigurableBeanFactory来声明作用域的单例和多例,避免出错
// 单例
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class SingletonBean {}

// 多例
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBean {}
  • spring web中使用WebApplicationContext声明请求和会话的作用域,对于Web应用,有5种对应的作用域
@Component
//@Scope(value = WebApplicationContext.SCOPE_GLOBAL_SESSION)
//@Scope(value = WebApplicationContext.SCOPE_APPLICATION)
//@Scope(value = WebApplicationContext.SCOPE_SESSION)
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class OrderBean {}
  • WebApplicationContext源码,定义的作用域如下
	// web的request作用域,支持标准作用域的单例和多例
	String SCOPE_REQUEST = "request";

	// web的session作用域,支持标准作用域的单例和多例
	String SCOPE_SESSION = "session";

	// web的全局session作用域,支持标准作用域的单例和多例
	String SCOPE_GLOBAL_SESSION = "globalSession";

	// web的上下文application作用域,支持标准作用域的单例和多例
	String SCOPE_APPLICATION = "application";
  • 作用域的代理模式proxyMode的使用场景
// 需求:在web应用中,需要在服务层获取当前会话或请求中一个实例bean对象,而不是每次一个会话创建一个bean,这样会产生一个会话多个bean实例,相当于session或者request中的单例

// spring做法是做一个bean的代理注入到单例bean中,并由这个代理bean委托给实际的bean去处理业务逻辑

// 基于Java的配置
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,proxyMode = ScopedProxyMode.INTERFACES)
public class OrderBean {}

// 基于xml的配置
 <bean id="oderBean" class="com.dtrees.springmvc.bean.OrderBean" scope="session">
        <aop:scoped-proxy proxy-target-class="true"/>
    </bean>
  • ScopedProxyMode源码
public enum ScopedProxyMode {
	// 默认是NO的值
	DEFAULT,
	// 不创建代理对象
	NO,
	// 基于JDK的动态代理模式
	INTERFACES,

	// 基于CGLIB的动态代理模式
	TARGET_CLASS;
}
运行时注入bean
属性占位符(Property placeholder):
* 通过Spring的Environment来检索属性
* 解析属性占位符,即${ ... }包装的属性名称
// 使用spring的Environment来检索属性
@Configuration
@ComponentScan("com.dtrees.spring.core.assembly.placeholder")
@PropertySource("classpath:spring/app.properties")
public class EnvironmentConfig {
    @Autowired
    private Environment environment;    //app.properties 的数据注入到spring的Environment中

    /**
     * 希望这个属性必须要定义,使用getRequiredProperty
     * @return
     */
    @Bean
    public PropertiesBean propertiesBean(){
        return new PropertiesBean(environment.getProperty("title"),
                                  environment.getProperty("age",Integer.class));
    }
}
// 上述注释掉propertiesBean也可以正常注入,对于$表达式而言是直接从环境变量environment中取值
// 使用占位符注入
// 2. 自动扫描组件并注入组件属性信息
@Component
public class PropertiesBean implements Serializable{
	public PropertiesBean(@Value("${title}") String title, @Value("${age}") int age) {
        this.title = title;
        this.age = age;
    }

//其他的属性
private List<Songs> songs;

...
}
Spring表达式语言(SpEL)
  • SpEL特征
    • 使用bean的ID来引用bean;
    • 调用方法和访问对象的属性
    • 对值进行算术、关系和逻辑运算;
    • 正则表达式匹配;
    • 集合操作
  • SpEL表达式:#{ … }
  • 样例
// 使用SpEL表达式注入属性和构造器参数
// 1. 定义配置文件
   @Configuration
@PropertySource("classpath:spring/app.properties")
@ComponentScan("com.dtrees.spring.core.assembly.placeholder")
public class SpELConfig {
    /**
     *  注意这里使用SpEl表达式的时候需要在配置文件配置PropertySourcesPlaceholderConfigurer,如果没有配置将会报错
     * @return
     */
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
        return new PropertySourcesPlaceholderConfigurer();
    }
}
   
   // 2.这时候才能在bean中使用SpEl表达式注入
   @Component
public class SpELBean implements Serializable{

    private String title;

    private String age;

    public SpELBean(@Value("#{systemProperties['title']}") String title,
                    @Value("#{systemProperties['age']}") String age) {
        this.title = title;
        this.age = age;
    }
}

// 其他SpEl表达式的样例,在Java的Config文件下
@Value("#{3.1444}") //表示字面值
    private float v1;

    @Value("#{3.1444E}") //使用科学记数法
    private float v2;

    @Value("#{'使用字符串'}")    //字符串
    private String v3;

    @Value("#{false}")  // 使用boolean
    private boolean isBean;

    // 引用bean、属性和方法
    @Value("#{propertiesBean}")     // 注入bean
    private PropertiesBean propertiesBean;

    @Value("#{propertiesBean.title}")
    private String title;

    @Value("#{propertiesBean.getTitle()}")      // 使用方法
    private String title2;

    @Value("#{propertiesBean.getTitle().toUpperCase()}")      // 使用方法
    private String title3;

    // 安全类型检查,防止出现空指针
    @Value("#{propertiesBean?.getTitle()?.toUpperCase()}")      // 使用方法
    private String title4;

    // 类作用域的方法和常量,要用T()这个关键的运算符进行转换
    @Value("#{T(java.lang.Math).PI}")       //
    private double pi;

    // 执行运算符
    @Value("#{T(java.lang.Math).PI * 2.3}")     // 基本运算符
    private double result;

    @Value("#{T(java.lang.Math).PI > 5}")       // 比较运算符
    private boolean gt;

    @Value("#{propertiesBean.age == 20}")       // 比较运算符
    private boolean gt2;

    @Value("#{propertiesBean.age eq 20}")       // 比较运算符
    private boolean gt3;

    @Value("#{propertiesBean.title + ' to ' + propertiesBean.age}")     // 字符串连接
    private String str;

    // 三元运算符
    @Value("#{propertiesBean.age > 20?'old':'young'}")     // 字符串连接
    private String ageState;

    @Value("#{propertiesBean?.email?.matches(''[\\s._%+-]+@[\\s.-]+\\.com)}")   // 使用正则表达式
    private boolean emailChecked;

    // 从集合或者数组获取元素
    @Value("#{'string'[2]}")    // 下标基于0开始
    private String subString;
  • 集合查询运算和投影运算
// 1.定义存放的对象
public class Songs  implements Serializable{

    private String name;

    private String artist;
	 
	 // get and set 方法...
	
 }
// 2.在Java的Config文件下
    // 查询运算,查找所有的歌中的名称包含有keithl的歌曲列表,
    @Value("#{propertiesBean.songs.?[name eq 'keithl']}")
    private List<Songs> songsList;

    // 查找集合中的第一个匹配的歌曲包含keithl的
    @Value("#{propertiesBean.songs.^[name eq 'keithl']}")
    private List<Songs> firstSong;

    // 查找集合中的第一个匹配的歌曲包含keithl的
    @Value("#{propertiesBean.songs.$[name eq 'keithl']}")
    private List<Songs> lastSong;

    // 投影运算,从集合的每个成员中选择特定的属性放到另外一个集合,下面将集合中song对象的name取出来并存放到新的集合中
    @Value("#{propertiesBean.songs.![name]}")
    private List<String> titleList;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

疾风先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值