Spring Bean assembly in-depth

Tian Ji

Horses are divided into upper, middle and lower thirds. The other side comes out, avoids the next, the other side comes out, avoids the upper part, the other side comes out, avoids the middle, and wins.

Environment-dependent beans will appear in applications. In terms of data source usage scenarios, embedded databases may be used when developing simple functions,
while production environments generally use databases that have a market share. We will not switch to
the data source bean of a specific environment when publishing to the test production environment , and then recompile and release.

@Profile annotation

Spring initially provided profile annotations to address specific beans in the assembly environment.

@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;
    }
}

verification

@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);
    }
}

In addition, @Profile can also be annotated at the class level

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

Hands

If the @ActiveProfiles annotation is not added to the HxDataSourceConfigTest class or @ActiveProfiles does not specify an annotation value.
What will happen?

None of the two beans in the above example will return. The program throws NoSuchBeanDefinitionException

If dev is activated on the HxDataSourceConfigTest class, @ActiveProfiles ("dev"). But mysqlDataSource
does not add any comments, so will the mysqlDataSource bean be created? Modify the configuration class to only define a mysqlDataSource
bean, and do not use @Profile annotation.

Beans without a profile annotation are not subject to the currently activated profile and will still be created. Therefore, the activated profile
will only restrict those beans that use the profile declaration to depend on a specific environment

@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);
    }
}

Profile configuration based on XML assembly

<!--方式一,基于环境创建多个定义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>

profile activation

In the above unit test, we have already wanted to activate the profile configured bean solution during the test.
Use @ActiveProfiles annotation on the class

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

Spring provides spring.profiles.active and spring.profiles.default to activate more than one profile

If spring.profiles.active is configured, take its value, otherwise if spring.profiles.default
is configured, take it, otherwise no profile is active and any beans that declare profiles will not be created.

What other ways can spring.profiles.active and spring.profiles.default be set?

For example, set the spring.profiles.default value in web.xml during development

<!--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>

Set the spring.profiles.active value through environment variables, JVM system properties, or JNDI entries during deployment

@Conditional annotation

The @Profile annotation is specific to setting environmental conditions. But the creation of the bean may have to consider other dependencies. At this time,
@Profile annotation is powerless and needs a more general conditional annotation solution: @Conditional is coming

Instructions

First read the javadoc annotated by @Conditional, this is very important, through reading the document to understand

  1. The component specified by value will only be registered after the conditions set by all the classes that implement the Condition interface specified by value are satisfied
  2. condition is a state that can be determined programmatically. The determined result is actually a boolean type
  3. @Conditional can be used as a type-level annotation directly on any class, or indirectly, together with @Component, @Configuration as
    a meta-annotation combination custom annotation.
  4. The @Conditional annotation can be used as a method-level annotation on bean methods.
  5. When @Conditional is used on the @Configuration class, the bean methods in the class, @ComponentScan and
    @Import related to the class are subject to conditions.
  6. The condition annotated by @Conditional is non-inherited. The superclass condition will be ignored by the subclass.

Secondly, we should take questions to practice verify and understand these.

//让我们继续修改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

verification

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

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

Ambiguity of automatic assembly

"Journey to the West-The True and False Monkey King"

Guanyin: Which of you is Goku?

Beans in Spring are automatically assembled, but if there are multiple beans that meet the conditions, Spring will be in trouble.
Let us add some material to it.

@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;
    }
}

verification

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

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

Spring开始抱怨:Could not autowire field,NoUniqueBeanDefinitionException

We declare that the automatically injected field is the DataSource interface type, and
the two bean types that we define in the configuration class HxDataSourceConfig implement the DataSource interface. So Spring cannot make
arbitrary choices without help .

But balabala will continue anyway, we can use @Primary to annotate one of the beans. Let it
stand up and share when Spring is in trouble . This selected the Monkey King for Huaguo Mountain.

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

That day, another beautiful monkey king was also clamoring for a sutra.

When there are multiple @Primary, spring is again confused. NoUniqueBeanDefinitionException, more than one 'primary' bean found among candidates more than one 'primary' bean found among candidates.
Obviously @Primary focuses on a qualitative perspective and does not restrict how the client uses (such as unique use, etc.)

But balabala continues anyway, spring provides a qualifier solution: @Qualifier. It can be understood that the
requirements for going to the Bible are stricter, from 'beauty monkey king' to 'stone monkey beauties king'. The stone monkey inside is actually a limitation. Back to the example

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

The value in @Qualifier at the injection point can match 2 scenarios

  1. Use @Qualifier to declare a bean with value
  2. No bean with @Qualifier id value

Therefore, the above injection point can match the following situation

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

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

 The value of @Qualifier using the default bean Id as the injection point seems to be simple and straightforward.
 But in this way, the code of the injection point and the injected bean method or class name are tightly coupled. When the bean method or
class name is reconstructed, the code of the injection point needs to be modified. Considering this, you can use
Use @Qulifier on the class to customize a qualifier with a clear meaning, and then use this qualifier at the injection point.
 Or if you use IDEA to develop, it is no problem to use the default bean Id, because when refactoring bean methods in IDEA, the injection point can be intelligently sensed and refactored.

Custom @Qualifier annotation

Up to now, everything is working well, let us create a little trouble.

 Continuing the previous story of the beautiful monkey king, we now assume such a scene, the stone monkey beautiful monkey king is not the only one, and there is one in the west.
 Then we continue to use @Qualifier to abbreviate the matching range. For example, at the bean definition point, the injection point is added with @Qualifier ("石猴 美 猴王") and @Qualifier ("西西")

The problem is that Spring's @Qualifier annotation does not support multiple uses on the same entry

The solution is to use a custom qualifier annotation, the method is very simple, the internal annotation can use @Qualifier annotation

@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;
    }
}

verification

@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);
    }
}

There is a problem left here. It is difficult to imagine that using @Qulifier cannot solve the problem of bean ambiguity, but needs to use multiple custom
qualifier annotations.

One reason that can be guessed is that it was not well designed at the beginning, and the subsequent code is not easy to change the qualifier value at will. You
can only add a bean that is refactored as a custom annotation

But the advantages of this custom qualifier annotation are still very obvious

  1. Annotation names are self-explanatory qualifiers, you don't need to give it a value like @Qualifier, such as our @Dev above
  2. This custom annotation itself can be freely combined to achieve multiple goals.

bean scope

Scope type description use
Singleton Create a bean instance for the entire application default
prototype New instances are created when injecting or when obtained through the application context @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Conversation Web application, create an instance for each session @Scope(value = WebApplicationContext.SCOPE_SESSION,proxyMode =视情况)
request Web application, create an instance for each request @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);
    }
}

Session request scope

Here focuses on understanding the concept

 As a popular example, we have all been to supermarkets and used shopping carts. If we declare the shopping cart as a specific bean.
For this bean, if there is only one shopping cart in the supermarket, it is called a singleton.
 Every time we go to the supermarket, we will take an empty shopping cart, so this is called a prototype.
 As far as we go shopping this time, we think it is a conversation, in this conversation. We
use this shopping cart no matter which shelf we push the shopping cart to.
 We regard the disposable plastic bags used for each purchase of different categories in this shopping as a request. Put one plastic bag when buying fruits and
another plastic bag when buying aquatic products.

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

 The problem that proxyMode solves here is: the problem when beans with a shorter life cycle are injected into beans with a longer life cycle. If the application injects session bean ShopCart into the singleton bean superMarket
, there is no ShopCart of any practical significance when the singleton bean is initialized. So what is injected into superMarker here is
the proxy of ShopCart bean. When superMarket calls the method of ShopCart bean, the proxy resolution will delegate the call to
the ShopCart bean in the actual session scope.
  ScopedProxyMode.INTERFACES used by proxyMode here, because HandPushingShopCart implements
ShopCart interface, you can use standard jdk interface-based proxy. If you do not implement the interface, you can use CGLIB proxy, just change to proxyMode = ScopedProxyMode.TARGET_CLASS

How to define session scope in xml

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

Runtime value injection

Inject external values

Generally, it is rarely hard-coded in the program, considering the flexibility. Generally, some attribute variables are defined in the attribute file, which is similar to the way we
configure system environment variables.

//@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 also provides the use of @TestPropertySource in testing, the function is similar to @PropertySource, as shown below

@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"));
    }
}

Use attribute placeholders to inject attribute values
XML configuration files use placeholders in the form of $ {..}, and need to be declared<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());
    }
}

Component scanning and autowiring use bean value injection, you can use @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 Expression language)

Form: # {Expression Body}
Introduced since Spring 3
Use scenarios: bean assembly, spring security rules, Thymeleaf templates use SpEL to reference model data

characteristic Examples
Perform arithmetic, relational, and logical operations on values #{T(java.lang.Math).PI * R ^ 2}、#{1}、#{"123"}、 #{score > 90 ? "good" :"bad"}
Reference bean and its attribute method #{somebean}、 #{somebean.property} 、#{somebean.method()}
Call object methods or access properties #{systemProperties['student.name']}
Support regular expression #{26characters.lowercase matches [a-z]}
Operation set #{shelf.books[0].title}、#{shelf.books.?[title eq 'fire and ice']}

For more details, please refer to Spring IN Action Action Edition

Guess you like

Origin www.cnblogs.com/hymanting/p/12726263.html