Spring Beanアセンブリーの詳細

天地

馬は3つのカテゴリに分類されます:上部中央、下部、上部。

環境に依存するBeanがアプリケーションに表示されます。データソースの使用シナリオの観点では、単純な関数を開発するときに組み込みデータベースを使用できますが、
本番環境では一般に市場シェアのあるデータベースを使用しますテスト本番環境に公開するときに
特定の環境のデータソースBean に切り替えてから、再コンパイルしてリリースしません

@Profileアノテーション

Springは当初、アセンブリ環境の特定のBeanに対処するためにプロファイルアノテーションを提供していました。

@Configuration
public class HxDataSourceConfig {
    @Bean
    @Profile("dev")
    public DataSource embeddedDataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScripts(new String[]{"hxschema.sql",
                        "hxdata.sql"})
                .build();
    }

    @Bean
    @Profile("prod")
    public DataSource mysqlDataSource() {
        DriverManagerDataSource datasource = new DriverManagerDataSource();
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        datasource.setUrl("jdbc:mysql://localhost:3306/message?useSSL=false");
        datasource.setUsername("root");
        datasource.setPassword("h123");
        return datasource;
    }
}

確認する

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = HxDataSourceConfig.class)
//激活dev配置
@ActiveProfiles("dev")
public class HxDataSourceConfigTest {

    @Autowired
    private DataSource dataSource;

    //当前激活dev配置,embeddedDataSource bean被创建
    @Test
    public void testEmbeddedDataSourceBean() {
        assert (dataSource instanceof EmbeddedDatabase);
    }
}

さらに、@ Profileにはクラスレベルで注釈を付けることもできます。

@Configuration
@Profile("dev")
public class HxDataSourceConfig {
}

ハンズオン

@ActiveProfilesアノテーションがHxDataSourceConfigTestクラスに追加されていない場合、または@ActiveProfilesがアノテーション値を指定していない場合。
どうなるの?

上記の例の2つのBeanはいずれも戻りません。プログラムはNoSuchBeanDefinitionExceptionをスローします

HxDataSourceConfigTestクラスでdevがアクティブ化されている場合、@ ActiveProfiles( "dev")。しかし、mysqlDataSource
はコメントを追加しないので、mysqlDataSource Beanが作成されますか?構成クラスを変更して、mysqlDataSource
Bean のみを定義し、@ Profileアノテーションを使用しないようにします。

プロファイルアノテーションのないBeanは、現在アクティブ化されているプロファイルの影響を受けず、引き続き作成されます。したがって、アクティブ化されたプロファイル
は、プロファイル宣言を使用して特定の環境に依存するBeanのみを制限します

@Configuration
public class HxDataSourceConfig {
    @Bean
    public DataSource mysqlDataSource() {
        DriverManagerDataSource datasource = new DriverManagerDataSource();
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        datasource.setUrl("jdbc:mysql://localhost:3306/message?useSSL=false");
        datasource.setUsername("root");
        datasource.setPassword("h123");
        return datasource;
    }
}

//test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = HxDataSourceConfig.class)
@ActiveProfiles("dev")
public class HxDataSourceConfigTest {

    @Autowired
    private DataSource dataSource;

    //测试通过
    @Test
    public void testmysqlDataSource() {
        Assert.notNull(dataSource);
    }
}

XMLアセンブリに基づくプロファイル構成

<!--方式一,基于环境创建多个定义bean的xml-->
<!--dev.xml-->
<beans profile="dev">
    <bean></bean>
</beans>

<!--prod.xml-->
<beans profile="prod">
    <bean></bean>
</beans>

<!--方式二,所有环境bean都放在一个xml,用beans嵌套-->
<beans>
    <beans profile="dev">
        <bean></bean>
    </beans>

    <beans profile="prod">
        <bean></bean>
    </beans>
</beans>

プロファイルのアクティブ化

上記の単体テストでは、テスト中にプロファイル構成済みBeanソリューションをアクティブ化することをすでに望んでいました。
クラスで@ActiveProfilesアノテーションを使用する

@ActiveProfiles("dev")
public class HxDataSourceConfigTest{
}

Springは、複数のプロファイルをアクティブ化するために、spring.profiles.activeおよびspring.profiles.defaultを提供します

spring.profiles.activeが構成されている場合は、その値を使用します。それ以外の場合
、spring.profiles.default 構成されている場合それを使用します。それ以外の場合、アクティブなプロファイルはなく、プロファイルを宣言するBeanは作成されません。

spring.profiles.activeとspring.profiles.defaultを他にどのように設定できますか?

たとえば、開発中にweb.xmlでspring.profiles.default値を設定します

<!--web.xml-->
<web-app>
    <!--other omitted for simplification-->
    <context-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </context-param>
    <servlet>
        <servlet-name>delegateServlet</servlet-name>
        <servlet-class>designpattern.delegatepattern.mock.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>
</web-app>

デプロイメント中に、環境変数、JVMシステムプロパティ、またはJNDIエントリを介してspring.profiles.active値を設定します

@Conditionalアノテーション

@Profileアノテーションは、環境条件の設定に固有です。ただし、Beanの作成では、他の依存関係を考慮する必要がある場合があります。現時点で
@ Profileアノテーションは無力であり、より一般的な条件付きアノテーションソリューションが必要です。@ Conditionalが登場します

使い方

最初に@Conditionalの注釈が付けられたjavadocを読みます。理解するためにドキュメントを読むことで、これは非常に重要です

  1. valueで指定されたコンポーネントは、valueで指定されたConditionインターフェースを実装するすべてのクラスによって設定された条件が満たされた後にのみ登録されます
  2. 条件は、プログラムで決定できる状態です。決定された結果は実際にはブール型です
  3. @Conditionalは、任意のクラスでタイプレベルのアノテーションとして直接使用することも、@ Component、@ Configurationとともに
    メタアノテーションの組み合わせのカスタムアノテーションとして間接的に使用することもできます
  4. @Conditionalアノテーションは、Beanメソッドのメソッドレベルのアノテーションとして使用できます。
  5. @Conditionalが@Configurationクラスで使用される場合、クラス内のBeanメソッド、@ ComponentScan、およびクラスに関連する
    @Importは条件に従います。
  6. @Conditionalによって注釈が付けられた条件は継承されません。スーパークラスの条件は、サブクラスによって無視されます。

第二に、私たちはこれらを検証し理解する練習をするために質問をする必要があります。

//让我们继续修改HxDataSourceConfig类如下
@Configuration
public class HxDataSourceConfig {
    @Bean
    //这边使用@Conditional注解
    @Conditional(JdbcPropertyExist.class)
    public DataSource mysqlDataSource() {
        DriverManagerDataSource datasource = new DriverManagerDataSource();
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        datasource.setUrl("jdbc:mysql://localhost:3306/message?useSSL=false");
        datasource.setUsername("root");
        datasource.setPassword("h123");
        return datasource;
    }
}

//设定条件
//application.properties文件存在hxdatasource.jdbcDriver属性则返回true
class JdbcPropertyExist implements Condition {
    //本示例定义的条件并没有依赖ConditionContext及AnnotatedTypeMetadata接口参数
    //但实际使用时,可能需要详细了解这两个接口提供的特性
    @Override
    public boolean matches(ConditionContext context,
                           AnnotatedTypeMetadata metadata) {
        InputStream inputStream = JdbcPropertyExist.class.getClassLoader()
                .getResourceAsStream("application.properties");
        Properties properties = new Properties();
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return properties.stringPropertyNames()
                .contains("hxdatasource.jdbcDriver");
    }
}

## application.properties文件
## 文件里的属性决定了mysqlDataSource是否会创建
hxdatasource.jdbcDriver

確認する

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = HxDataSourceConfig.class)
public class HxDataSourceConfigTest {
    @Autowired
    private DataSource dataSource;

    @Test
    public void testmysqlDataSource() {
        Assert.notNull(dataSource);
    }
}

自動組立のあいまいさ

「西への旅-真と偽の猿王」

観音:あなたのどちらが悟空ですか?

SpringのBeanは自動的にアセンブルされますが、条件を満たすBeanが複数ある場合、Springに問題があります。
それに材料を追加してみましょう。

@Configuration
public class HxDataSourceConfig {
    @Bean
    public EmbeddedDatabase embeddedDataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScripts(new String[]{"hxschema.sql",
                        "hxdata.sql"})
                .build();
    }

    @Bean
    public DriverManagerDataSource mysqlDataSource() {
        DriverManagerDataSource datasource = new DriverManagerDataSource();
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        datasource.setUrl("jdbc:mysql://localhost:3306/message?useSSL=false");
        datasource.setUsername("root");
        datasource.setPassword("h123");
        return datasource;
    }
}

確認する

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = HxDataSourceConfig.class)
public class HxDataSourceConfigTest {
    @Autowired
    private DataSource dataSource;

    @Test
    public void testmysqlDataSource() {
        Assert.notNull(dataSource);
    }
}

Spring开始抱恨:フィールドを自動配線できませんでした、NoUniqueBeanDefinitionException

自動的に挿入されたフィールドがDataSourceインターフェースタイプであり、構成クラスHxDataSourceConfigで
定義する2つのBean タイプがDataSourceインターフェースを実装することを宣言します。したがって、Springは手助けなしに
任意の選択を行うことはできません

とにかく、バラバラは継続します。@ Primaryを使用して、いずれかのBeanに注釈を付けることができます。
立ち上がって、春が困っているときに共有しましょうこれは、華国山の猿王を選びました。

@Bean
@Primary
public EmbeddedDatabase embeddedDataSource() {
}
<!--xml中配置primary bean-->
<bean primary="true"></bean>

その日、別の美しい猿の王もお経を強く求めていました。

@Primaryが複数ある場合、春は再び混乱します。NoUniqueBeanDefinitionException、候補の中に複数の「プライマリ」Beanが見つかりました

しかし、とにかくbalabalaは継続し、Springは修飾子ソリューション@Qualifierを提供しています。聖書に行くための
要件、「美猿王」から「石猿美王」まで、より厳しいことが理解できます内部の石猿は実際には制限です。例に戻る

//在自动注入点使用@Qualifier
@Autowired
@Qualifier("mysqlDataSource")
private DataSource dataSource;

注入ポイントの@Qualifierの値は2つのシナリオに一致できます

  1. @Qualifierを使用して、値を持つBeanを宣言します
  2. @Qualifier id値を持つBeanはありません

したがって、上記の注入ポイントは次の状況に一致する可能性があります

//场景一 bean不使用@Qualifier
//方法名和注入点@Qualifier的value相同的bean方法
//或@Component注解的类名和value相同的类(除了首字母大小写)
@Bean
public DriverManagerDataSource mysqlDataSource() {
}

//场景二:bean显式使用@Qualifier声明
@Bean
@Qualifier("mysqlDataSource")
public DriverManagerDataSource mysqlDataSource() {
}

 注入ポイントとしてデフォルトのBean IDを使用する@Qualifierの値は、単純で簡単なようです。
 ただし、この方法では、注入ポイントのコードと注入されたBeanメソッドまたはクラス名が密接に結合されます
。Bean メソッドまたはクラス名がリファクタリングされると、注入ポイントのコードを変更する必要があります。これを考慮して、Beanメソッドまたは@Componentを使用できます。クラスで
@Qulifier 使用して、明確な意味を持つ修飾子カスタマイズし、注入ポイントでこの修飾子を使用します。
 または、IDEAを使用して開発する場合、デフォルトのBean Idを使用しても問題ありません。IDEAでBeanメソッドをリファクタリングするときに、注入ポイントをインテリジェントに検知してリファクタリングできるためです。

カスタム@Qualifierアノテーション

これまでのところ、すべてがうまく機能しているので、少し問題を作りましょう。

 前作の美しい猿王の話を続けますが、今はそういう場面を想定しています。石猿は美しい猿王だけではなく、西にいるのです。
 次に、引き続き@Qualifierを使用して、一致する範囲を省略します。たとえば、Bean定義ポイントでは、注入ポイントに@Qualifier( "石傅美后王")と@Qualifier( "西西")が追加されます。

問題は、Springの@Qualifierアノテーションが同じエントリでの複数回の使用をサポートしていないことです。

解決策は、カスタム修飾子アノテーションを使用することです。メソッドは非常にシンプルです。内部アノテーションは@Qualifierアノテーションを使用できます

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Dev {
}

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Test {
}

@Configuration
public class HxDataSourceConfig {

    @Bean
    @Qualifier("mysql")
    @Dev
    public DriverManagerDataSource mysqlDataSource() {
        DriverManagerDataSource datasource = new DriverManagerDataSource();
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        datasource.setUrl("jdbc:mysql://localhost:3306/message?useSSL=false");
        datasource.setUsername("dev");
        datasource.setPassword("h123");
        return datasource;
    }

    @Bean
    @Qualifier("mysql")
    @Test
    public DriverManagerDataSource mysqlDataSource2() {
        DriverManagerDataSource datasource = new DriverManagerDataSource();
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        datasource.setUrl("jdbc:mysql://localhost:3306/message?useSSL=false");
        datasource.setUsername("test");
        datasource.setPassword("h123");
        return datasource;
    }
}

確認する

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = HxDataSourceConfig.class)
public class HxDataSourceConfigTest {
    @Autowired
    @Qualifier("mysql")
    @Dev
    private DataSource dataSource;

    @Test
    public void testmysqlDataSource() {
        //这边输出dev下的用户名,因为上面使用了@Dev
        System.out.println(((DriverManagerDataSource)dataSource).getUsername());
        Assert.notNull(dataSource);
    }
}

ここに問題があります。@ Qulifierを使用してもBeanのあいまいさの問題を解決できないが、複数のカスタム
修飾子アノテーションを使用する必要があるとは想像できません

推測できる理由の1つは、最初は適切に設計されておらず、その後のコードで修飾子の値を自由に変更することが容易ではないことです。
カスタムアノテーションとしてリファクタリングされたBeanのみを追加できます

しかし、このカスタム修飾子アノテーションの利点はまだ非常に明白です

  1. アノテーション名は説明が必要な修飾子です。上記の@Devのように、@ Qualifierのような値を指定する必要はありません。
  2. このカスタムアノテーション自体を自由に組み合わせて、複数の目標を達成できます。

豆のスコープ

スコープタイプ 説明文 使用する
シングルトン アプリケーション全体のBeanインスタンスを作成する デフォルト
プロトタイプ 注入時またはアプリケーションコンテキストから取得したときに、新しいインスタンスが作成されます。 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
会話 Webアプリケーション、各セッションのインスタンスを作成 @Scope(value = WebApplicationContext.SCOPE_SESSION、proxyMode =视状況例)
お願い Webアプリケーション、リクエストごとにインスタンスを作成 @Scope(value = WebApplicationContext.SCOPE_REQUEST、proxyMode =视状況例)
//定义bean
@Configuration
public class HxDataSourceConfig {

    //单例bean。单例是spring bean的默认作用域
    @Bean
    public DriverManagerDataSource mysqlDataSource() {
        DriverManagerDataSource datasource = new DriverManagerDataSource();
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        datasource.setUrl("jdbc:mysql://localhost:3306/message?useSSL=false");
        datasource.setUsername("dev");
        datasource.setPassword("h123");
        return datasource;
    }

    //原型bean。使用点将得到一个新的bean实例
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Fruit someKindFruit() {
        Fruit f = new Fruit();
        return f;
    }
}

//验证
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = HxDataSourceConfig.class)
public class HxDataSourceConfigTest {
    @Autowired
    private DataSource dataSource;

    @Autowired
    private DataSource dataSource2;

    //pass,因为bean mysqlDataSource是单例,dataSource和dataSource2都指向该实例
    @Test
    public void testSingletonBean() {
        Assert.isTrue(dataSource == dataSource2);
    }

    @Autowired
    private Fruit fruit1;

    @Autowired
    private Fruit fruit2;

    //pass fruit1和fruit2指向不同的实例
    @Test
    public void testPrototypeBean() {
        Assert.isTrue(fruit1 != fruit2);
    }
}

セッションリクエストスコープ

ここでは、概念の理解に焦点を当てています

 人気のある例として、私たちはすべてスーパーマーケットに行き、ショッピングカートを使いました。ショッピングカートを特定のBeanとして宣言した場合。
このBeanの場合、スーパーマーケットにショッピングカートが1つしかない場合、シングルトンと呼ばれます。
 スーパーマーケットに行くたびに、空のショッピングカートを取り出すため、これをプロトタイプと呼びます。
 今回買い物に行く限り、この会話の中での会話だと思います。
この棚はどの棚に押し使用します。
 今回のお買い物では、カテゴリごとに使い捨てのビニール袋をご要望に応じて、果物の場合は1枚
、水産物の場合は1枚入れてください

    //session作用域的bean定义
    @Bean
    @Scope(value = WebApplicationContext.SCOPE_SESSION,
            proxyMode = ScopedProxyMode.INTERFACES)
    public ShorpCart someShopCart() {
        HandPushingShopCart cart = new HandPushingShopCart();
        return cart;
    }

 ここでproxyModeが解決する問題は、ライフサイクルが短いBeanがライフサイクルが長いBeanに注入されるときの問題です。アプリケーションがセッションBean ShopCartをシングルトンBean superMarket
注入する場合、シングルトンBeanが初期化されるときに実用的な意味を持つShopCartはありません。したがって、ここでsuperMarkerに挿入されるのは
ShopCart BeanのプロキシですsuperMarketがShopCart Beanのメソッドを呼び出すと、プロキシ解決
により、実際のセッションスコープでShopCart Bean 呼び出しが委任されます。
  ここで、proxyModeによって使用されるScopedProxyMode.INTERFACES。HandPushingShopCartは
ShopCartインターフェースを実装しているため、標準のjdkインターフェースベースのプロキシを使用できます。インターフェイスを実装しない場合は、CGLIBプロキシを使用できます。proxyMode= ScopedProxyMode.TARGET_CLASSに変更するだけです。

XMLでセッションスコープを定義する方法

<bean id="someShopCart" class="com.hxapp.HandPushingShopCart"
          scope="session">
      <aop:scoped-proxy proxy-target-class="false"/>
</bean>

ランタイム値注入

外部値を注入する

一般に、柔軟性を考慮して、プログラムでハードコーディングされることはほとんどありませんが、一般に、一部の属性変数は、
システム環境変数の構成方法と同様に、属性ファイルで定義されます。

//@PropertySource一般和@Configuration一起使用
//通过使用@PropertySource和Environment,指定属性文件里的属性及值就能被填充到Environment里
@Configuration
@PropertySource(value = "application.properties")
public class SomeConfig {
    @Autowired
    Environment env;

    @Bean
    public Student anyStudent() {
        if (env.containsProperty("student.name")) {
            return new Student(env.getProperty("student.name"));
        }
        return  new Student("");
    }
}

//测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SomeConfig.class)
public class RuntimeValueInjectionTest {

    @Autowired
    Student student;

    @Test
    public void testStudentNameFromProperties() {
        Assert.assertEquals("hyman", student.getName());
    }
}

Spring 4.1では、テストで@TestPropertySourceを使用することもできます。関数は、以下に示すように@PropertySourceに似ています。

@ContextConfiguration(classes = HxDataSourceConfig.class) 
@RunWith(SpringJUnit4ClassRunner.class)
@TestPropertySource("classpath:application.properties")
public class HxDataSourceConfigTest {

    @Autowired
    Environment env;

    @Test
    public void TestHymanName2() {
        String name = env.getProperty("student.name", String.class);
        assert(name.equals("hyman"));
    }
}

属性プレースホルダーを使用して属性値を挿入する
XML構成ファイルは$ {..}の形式のプレースホルダーを使用し、宣言する必要があります<context:property-placeholder/>

<!--定义在resources目录下的some-config.xml-->
<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 https://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder/>
    <bean id="student" class="com.hxapp.Student">
        <constructor-arg name="name" value="${student.name}"/>
    </bean>
</beans>
#定义在resources目录下的application.properties文件
student.name=hyman
//测试
@RunWith(SpringJUnit4ClassRunner.class)
@TestPropertySource("classpath:application.properties")
@ContextConfiguration(locations = "classpath:some-config.xml")
public class RuntimeValueInjectionTest {
    @Autowired
    Student student;

    @Test
    public void testStudentNameFromProperties() {
        Assert.assertEquals("hyman", student.getName());
    }
}

コンポーネントのスキャンと自動配線はBean値の注入を使用します。@ Valueを使用できます

//修改配置类,添加组件扫描组件,以扫描到student bean
@Configuration
@ComponentScan(basePackages = "com.hxapp")
public class SomeConfig {
}

//修改Student类,声明为组件,构造函数参数使用@Value
@Component
public class Student {
    String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    //使用@Value
    public Student(@Value("${student.name}")String name) {
        this.name = name;
    }
}

//测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SomeConfig.class)
@TestPropertySource("classpath:application.properties")
public class RuntimeValueInjectionTest {
    @Autowired
    Student student;

    @Test
    public void testStudentNameFromProperties() {
        Assert.assertEquals("hyman", student.getName());
    }
}

SpEL(スプリング式言語)

フォーム:#{式本体}
Spring 3以降に導入
使用シナリオ:Beanアセンブリ、Spring セキュリティルール、ThymeleafテンプレートがSpELを使用してモデルデータを参照する

特徴
値に対して算術演算、関係演算、論理演算を実行する #{T(java.lang.Math).PI * R ^ 2}、#{1}、#{"123"}、#{score> 90?"良し悪し"}
参照Beanとその属性メソッド #{somebean}、#{somebean.property}、#{somebean.method()}
オブジェクトメソッドの呼び出しまたはプロパティへのアクセス #{systemProperties ['student.name']}
正規表現をサポート #{26characters.lowercaseは[az]と一致します}
操作セット #{shelf.books [0] .title}、#{shelf.books.?[title eq 'fire and ice']}

詳しくはSpring IN Action Action Editionをご覧ください

おすすめ

転載: www.cnblogs.com/hymanting/p/12726263.html