Bean management autowire, JavaConfig

2019-10-11

一 装配 wiring

1. autowired 自动装配

2. Java Configuration

3. xml Configuration

自动装配

存在一个接口 及相应的实现类

package org.tech.stu.framework.Spring.BeanManagement;

public interface CompactDisc {
    void play();
}

 

1.  创建 configuration class ,会以自身所在的包作为默认基础包

使系统启用组件自动装配,会在 package 及其子包中发现 带有 @Component 注解的类,

 

若要设置多个基础包,建议的方法是将其指定为包中所包含的类或接口。

@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})

这些类所在的包将会作为组件扫描的基础包。 考虑在包中创建一个用来进行扫描的 空标记接口( marker interface ). 通过标记接口的方式,依然

能够保持对重构友好的接口引用,但是可以避免引用任何实际的应用程序代码(稍后的重构可能会将某个类从扫描的包中删除掉)

重构会导致失败的一种方法使用 @ComponentScan value 属性 @ComponentScan(basePackages="somePkg", "anotherPkg"})

 

 

 

@Configuraiton

@ComponentScan


package org.tech.stu.framework.Spring.BeanManagement;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.tech.stu.framework.Spring.BeanManagement_demo_ComponentScan_anotherPkg.markInterface;

@Configuration
@ComponentScan(basePackageClasses={selfMarkInterface.class,markInterface.class})
public class CDPlayerConfig {

}
 

2. 接口的实现

注意 @Component 该注解

注意: 严格限定必须 自定义 有意的类命名 作为 bean  ID 在系统中使用。 避免重构类名导致装配失败。

 1 package org.tech.stu.framework.Spring.BeanManagement;
 2 
 3 import org.springframework.stereotype.Component;
 4 
 5 @Component("must_define_means_qualifier_name_with_standard")
 6 public class SgtPeppers implements CompactDisc {
 7 
 8     private String title = "Sgt. Pepper`s Lonely Hearts Club Band";
 9     private String artist = "The Beatles";
10 
11     @Override
12     public void play() {
13         System.out.println(("Playing " + title + " by " + artist));
14     }
15 }

 

==========================================================

单元测试验证 bean 装配生成

注意 Junit  必须为 4.12 版本及以上, IDEA默认为 4.11,会报错

SpringJUnit4ClassRunner 在测试的时候自动创建 Spring 的应用上下文。

@ContextConfiguration 告诉test 需要从 CDPlayerConfig 配置类中加载配置。因为该配置类

包含了 @ComponentScan, 最终的 ApplicationContext 应该包含 CompactDisc bean.

 

 1
package org.tech.stu.framework.Spring.BeanManagement;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class SgtPeppersTest {

@Autowired
@Qualifier("must_define_means_qualifier_name_with_standard")
private CompactDisc cd;

@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
}
}
 

 

@Autowired  @Qualifier(“beanID") 配合, 在字段, 构造器参数,方法,注入。

Spring 特定注解 @Autowired(required=false) 属性设置为 false时, 如果没有匹配的bean的情况, Spring会让该bean处于未装配的状态,

需要谨慎对待,在代码中没有进行null检查的话,这个处于未装配的属性可能出现 NullPointerException

====================================================================================

@Named (Java Dependency Injection)依赖注入规范 , 可设置 ID

对应 @Component 组件类的声明。

@Inject (Java Dependency Injection) 对应 Spring 的 @Autowired

注意,pom 依赖

<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>
 1 package org.tech.stu.framework.Spring.BeanManagement;
 2 
 3 import javax.inject.Inject;
 4 import javax.inject.Named;
 5 
 6 @Named("cdPlayer_required")
 7 public class CDPlayer {
 8 
 9     private CompactDisc cd;
10 
11     @Inject
12     public CDPlayer(CompactDisc compactDisc){
13         this.cd = compactDisc;
14     }
15 }

 

Note:  在 unitTest 中使用 System.out.println() 棘手, 使用 StandardOutputStreamLog, System Rules 库

 

=================================================================================

@Autowired 和 @Qualifier(value="xx") 配合 注解, 注意 @Qualifier 不能用于 构造器。 可以绕行

 

 

 

 

 

 

更好的是 使用 @Inject 加上 @Named(value="xx") 配合使用 (当某个类使用了构造器注入其他抽象类)

 构造器注入 bean

 

 

 

 

 ==================================================================================

二 Java Config 配置

自动化配置并非所有情况下可行,例如将第三方库中的组件装配到你的应用中,没有办法在其类上添加 @Component 和 @Autowired

Java Config 需要放在单独的包中。于其他的应用程序逻辑分离。

1 添加 @Configuration 注解,

如果需要引入其他的 Config class ,创建一个更高级别的 ConfigClass, 在此类中,使用@Import 将两个Config 类组合在一起。

 

 

 或者随即加上 @Import(anotherConfig.class)

2. 在JavaConfig 使用 @Bean 声明 bean,返回类型的实例的方法, 方法invoke可实现构造器注入。规范化 使用 name 属性重命名,

同样适用于方法注入(包含设置器)

带有 @Bean 方法注解的方法可以采用任何必要的 Java功能来产生bean实例。 

注意,默认的 Scope   Bean 的生成是单例的

@Bean ( name = "xxx")

推荐做法是

通过这种方式引用其他的bean 通常是最佳的选择,不会要求将 CmpactDisc 声明到同一个配置类中。甚至

没有要求 CompactDisc 必须要在 JavaConfig中声明,可通过组件扫描功能自动发现或者通过 XML 配置。

可将多个配置类, XML 文件 以及自动扫描和装备 bean之中,只要功能完成健全即可。 

@Bean( name = "defineName")

public CDPlayer cdPlayer(CompactDisc compactDiscArg){

    return new CDPlayer(compactDiscArg);

}

以下是会令人困惑的另一种写法。且不如上面的强大。

public CDPlayer cdPlayer(){

  return new CDPlayerImpl(sgtPappers());

}

=====================================================================================

运行时 值注入

动态注入值到 Spring bean 时, SpEL 是一种很便利和强大的方法。

 

bean 装配一个方面你是将对象与对象关联。

bean 装配的另一个方面指的是将一个值注入到bean的属性或者构造参数中。

示例:

@Bean

public CompactDisc sgtPeppers(){
    return new BlankDisc(" Sgt. Pepper`s lonely Club Band",  "The Beatles");

}

 

<bean id="sgt Peppers" class="soundsystem.BlankDisc" c:_title="Sgt. Pepper`s Lonely Hearts Club Band" c:_artist="The Beatles" />

----------------------------------------

避免硬编码值, 在运行时确定:

1. 属性占位符 Property placeholder

2. Spring 表达式语言  SpEL

 

处理外部值的最简单方式是声明属性源并通过 Spring 的 Environment 来检索属性。

@PropertySource 引用了类路径中一个名为 app.properties 文件,文件内为键值对形式。

会被加载到 Spring 的 Environment 中,从这里检索属性。

 

 深入学习 Spring 的 Environment

1. String getProperty(String key)

2. String getProperty(String key, String defaultValue)    指定默认值

3. T getProperty(String key, Class<T> type)

4. T getProperty(String key, Class<T> type, T defaultValue)  将值视为 T 类型

 

前两种将值视为 String,  后两种示例:

想要获取的值所代表的含义是连接池中所维持的连接数量。从属性文件中得到的是一个String类型的值,

在使用之前要将其转换为 Integer类型。

int connectionCount = env.getProperty("db.connection.count", Integer.class,30)

 

如果在使用 getProperty() 方法的时候没有指定默认值,这个属性没有定义的化,获取到的值是 null,

如果希望这个属性必须要定义,使用 getRequirementProperty(""); 此时若没有定义会抛出 IllegalStateException 异常。

想检查某个属性是否存在: containsProperty() 方法。

想将属性解析为类, 使用 getPropertyAsClass() 方法:

Class<CompactDisc>  cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class)

=====

Environment 还能解析 profile 相关属性

===========

除了从 Environment 检索属性外, Spring还提供 通过占位符装配属性的方法。

这些占位符的值会来源于一个属性源。

支持将属性定义到外部的属性文件中,并使用占位符值将其插入到 Spring bean中。

在 Spring装配中, 占位符的形式为使用 "${ ... }" 包装的属性名称。

例如

<bean id = "sgtPeppers" class="soundSystem.BlankDisc" 

            c:_title="${disc.title}"  c:_artist="${disc.artist}"     />

XML 没有任何硬编码的值,是从配置文件以外的一个源中解析到的。

========================================================

组件扫描和自动装配,是没有指定占位符的配置文件或类。 使用 @Value 注解

public BlankDisc(

    @Value("${disc.title}" String title,  @Value("${{disc.artist"})  String artist){

    this.title = title,  this.value = value

}

为了使用占位符,必须要配置一个  PropertyPlaceholderConfigurer bean  或 PropertySourcePlaceholderConfigurer

建议后者,能够给予 Spring Environment 及其属性源来解析占位符

 

 

XML 配置, Spring context 命名空间 <context:propertyplaceholder> 元素会为你生成此 bean

<context:property-placeholder/>

解析外部属性能够将值的处理推迟到运行时。

======================================================================================

Spring Expression language

 

 ===================================================================

Spring Expression Language

1. 使用bean 的 ID 来引用bean;

2. 调用方法和访问对象的属性;

3.对值进行算术、关系和逻辑运算;

4.正则表达式匹配;

5.集合操作;

DI ,Spring Security 支持使用 SpEL表达式定义安全限制规则,Spring MVC 使用 Thymeleaf 模板作为视图的时候,可以使用SpEL 表达式引用模型数据。

“#{ ... }" 

1. 数字常量,,包括整型,浮点数.  String 值和 Boolean 值, 支持科学计数法。

   #{true}  字面值 true 和 false 的计算结果就是它们对应的 Boolean类型的值。

2. 引用其他 bean 或其属性或方法(注意返回类型) : #{ sgtPeppers.artist }

  注意: 返回为 null 的问题,可以使用类型安全的运算符。

#{artistSelector.selectArtist() ?. toUpperCase()} 

?. 能够在访问它右边内容前,确保对应的元素不是 null, 如果是 null, 不会调用 toUpperCase() 方法,表达式的返回值会是null.

 

3. 通过 systemProperties 对象引用系统属性: # {systemProperties['some.attr"]}

通过组件扫描创建 bean 时, 可以使用 @Value 注解,类似属性占位符

public BlankDisc(

  @Value("#{systemProperties["some.arrti"]}" String title );

 

XML 配置中, 可将 SpEL 传入 <constructor-arg> 和 <property> 的 value 属性中,

或者将其作为 c- 或 p- 命名空间条目的值。

<bean id="sgtPeppers" class="soundSystem.BlankDisc"

    c:_title="#{systemProperties["disc.title"]}" />

 

用来操作表达式值的 SpEL 运算符

运算符类型 运算符
算术运算 +、-、*、/、%、^
比较运算符 <,>, ==, <=, >=, lt,gt,eq, le,ge
逻辑运算 and, or, not,
条件运算 ? (ternary)  三元运算,  ?: (Elvis )  当目标为null时替换
正则表达式 matches

#{scoreboard > 100 ? "Winner" : "Loser"}

#{disc.title ?: "default empty"}  检查null值, 并用一个默认值来替代null. 

==========================================================

T() 运算符的结果会是一个 Class 对象,  例如 Math

T(java.lang.Math)  返回的结果便会代表 java.lang.class.  

T() 能访问目标类型的静态方法和常量。可访问类作用域的方法和常量,

==========================================================

SpEL matches 正则表达式匹配。

#{ admin.email   matches  ' [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com' ]

matches 运算符支持表达式 左边  String 类型的文本 ( 左边 ) 应用

正则表达式 ( 右边 ).

运算结果会返回一个 Boolean 类型的值:匹配返回 true, 否则返回 false.

=====================================================

String, 数组 和 集合 运算

1. 数组  [  index ]  

#( jukebox.songs[ T( java.lang.Math ).random() * jukebox.songs.size()] . title ) 随机获取一个元素

2. 查询运算符 .?[ ]  , 对集合过滤,得到集合的一个子集。

例如: 得到 jukebox 中属性为 Aerosmith 的所有歌曲。 如下的表达式。

# ( jukebox.songs.?[artist eq  'Aerosmith'] ),  

当 SpEL 迭代歌曲列表时,会对歌曲集合的每一个条目计算该表达式,如果表达式的计算结果为true的话,

条目会放到新的集合中。

3. 查询运算符:  ” .^[ ] " 和 " .$ [ ] " 分别用来在集合中查询第一个匹配项和最后一个匹配项。

4. 投影运算符 ( .! [ ] ) , 会从集合的每个成员中选择特定的属性放到另外的集合中。

  将 jukebox 歌曲对象中的 title 属性投影到一个新的 String 类型的集合中。

   #( jukebox.songs .! [title] ) 

投影可以与其他任意的 SpEL 运算符一起使用。

#(jukebox.songs .?[artist eq 'Aerosmith'] .! [ title ] )

=================================================================

Scope 作用域

默认是 singleTon 单例

但是当类是 易变的 mutable ,会保持一些状态,重用是不安全的。不能将 class 声明为

单例的bean, 因为对象会被污染。

@Scope 可以与 @Component 或 @Bean 一起使用。

1 组件扫描发现和声明 bean

@Component

@Quailifer("defineBeanID")

@Scope(configurableBeanFactory.SCOPE_PROTOTYPE)

public class Notepad{ ... }

不要使用 @Scope("prototype" 不安全。

 

2 Java 配置中 组合使用 @Bean 和 @Scope

@Bean

@Scope(ConfigurationFactory.SCOPE_PROTOTYPE)

public Notepad notepad(){

    return new Notepad();

}

3. XML 来配置bean 

<bean id="notepad" class="com.myapp.Notepad"  scope="prototype" />

==============================================================

==============================================================

使用会话 和 请求作用域

Web应用中,能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事情。

-----------------------------------------------------------------------------------------------------------------

ShoppingCart 接口 ,是最为理想的代理模式。

@Component

@Scope(

     value = WebApplicationContext.SCOPE_SESSION,

               proxyMode=ScopedProxyMode.INTERFACES)

public  ShoppingCart cart() {  ... }

 -----------------------------------------------------------------------------------------

当 ShoppingCart 是一个类的时候, 需要使用  proxyMode = ScopedProxyMode.TARGET_CLASS ,

Spring 使用 CGLib 来生成基于类的代理。以上,表明要以生成目标类扩展的方式创建代理。

请求作用域的 bean 会面临相同的装配问题,因此,请求作用域的bean应该也以作用域代理的方式进行注入。

 

场景:

例如电子商务中,

一个bean 代表用户的购物车

-- 如果购物车是单例的话,那么将会导致所有的用户都会向一个购物车中添加商品。

-- 如果购物车是原型作用域的话,那么应用中某一个地方往购物车中添加商品,在应用的另外一个地方可能就不可用了。

原因在于这里注入的是另外一个原型作用域的购物车。

就购物车 bean 来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会话作用域,

可以使用@Scope 注解

@Component

@Scope(

    value = WebApplicationContext.SCOPE_SESSION,

    proxyMode=ScopedProxyMode.INTERFACES)

public ShoppingCart cart() { ... }

 

Spring 会为 Web 应用中的每个会话创建一个 ShoppingCart, 这会创建多个 ShoppingCart bean的实例,

但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean 实际相当于单例的。

proxyMode 属性 设置为 ScopedProxyMode.INTERFACES。 该属性解决了将会话或请求作用域的bean

注入到单例bean 中所遇到的问题。 

 

问题场景

要将 ShoppingCart bean 注入到单例 StoreService bean 的 Setter 方法中

@Component

public class StoreService {

    @Autowired

    public void setShoppingCart ( ShoppingCart shoppingCart ) {

  this.shoppingCart = shoppingCart;

  }

  ...

}

 

因为 StoreService 是一个单例的bean, 会在 Spring 应用上下文加载的时候创建。当它创建的时候,

Spring 会试图将 ShoppingCart bean 注入到 setShoppingCart() 方法中。

但是 ShoppingCart bean 是会话作用域的, 此时并不存在,直到某个用户进入系统,创建了会话之后,才会出现

ShoppingCart 实例。

 

另外,系统中将会有多个 ShoppingCart 实例: 每个用户一个。我们并不像让 Spring注入某个固定的

ShoppingCart实例到 StoreService 中。希望的是当 StoreService 处理购物车功能时,它所使用的ShoppingCart

实例恰好是当前会话所对应的那一个。

 

Spring 并不会将实际的 ShoppingCart bean 注入到 StoreService 中, Spring 会注入一个到

ShoppingCart bean 的代理, 这个代理会暴露与 ShoppingCart 相同的方法,所以 StoreService 会认为

它就是一个购物车。但是当 StoreService 调用 ShoppingCart 的方法时, 代理会对其进行懒解析并将调用委托

给会话作用域内真正的 ShoppingCart bean。

 XML 中声明作用域代理

1. 声明 Spring 的 aop 命名空间

2. <aop:scoped-proxy> 是与@Scope 注解的 proxyMode属性功能相同的

Spring XML 配置元素,它会告诉 Spring 为 bean 创建一个作用域代理。

默认情况下,会使用 CGLib创建目标类的代理。 也可以将 proxy-target-class 属性设置为

false, 进而要求它生成基于接口的代理:

<aop:scoped-proxy proxy-target-class="false" />

 

 

<bean id="cart"

      class = "com.myapp.ShoppingCart"

    scope="session">

   <aop:scoped-proxy />

</bean>

 

posted @ 2019-10-11 13:07  君子之行  阅读(137)  评论(0编辑  收藏  举报