Spring series 9: annotation based spring container configuration

Write in front

In the previous articles, we said that the Spring container supports three ways to configure bean definition information. Now we will explain it in detail:

  • XML: bean definitions and dependencies are configured in XML files, which is complicated.
  • Annotation based: inject dependencies directly, configure the scanning package path in xml file, and simplify xml a lot.
  • Java based: batch scan and register bean s through configuration classes and annotations, and xml files are no longer required.

The previous cases are based on XML. This article introduces the annotation based method.

Content of this article

  1. Getting started with a simple case: using annotation based container configuration
  2. Detailed use of Autowired, in conjunction with @ Primary and @ Qulifier

Introduction to case 1

The bean definition information of the entry case is in the xml file, and annotations are used for dependency injection.

@Autowired: mark the constructor, field, setting method or configuration method as automatically assembled by Spring's dependency injection tool.

Define classes and inject dependencies through @ Autowird

public class BeanOne {
}

public class BeanTwo {
    // Annotation injection BeanOne
    @Autowired
    private BeanOne beanOne;

    @Override
    public String toString() {
        return "BeanTwo{" +
                "beanOne=" + beanOne +
                '}';
    }
}

xml file enable annotation configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        https://www.springframework.org/schema/context/spring-context.xsd
">
    <!--Annotation configuration enabled-->
    <context:annotation-config></context:annotation-config>
    
    <bean class="com.crab.spring.ioc.demo06.BeanOne" id="beanOne">
        <!--It can be injected through conventional dependency injection-->
    </bean>
    <!--Dependencies are automatically injected through annotations-->
    <bean class="com.crab.spring.ioc.demo06.BeanTwo" id="beanTwo"/>

</beans>

Get the bean s in the container for use

@org.junit.Test
public void test_annotation_config() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring2.xml");
        BeanTwo beanTwo = context.getBean(BeanTwo.class);
        System.out.println(beanTwo);
        context.close();
    }
    
// output
BeanTwo{beanOne=com.crab.spring.ioc.demo06.BeanOne@491666ad}

Introduction to case 2

In entry case 1, the traditional xml configuration dependency injection method is simplified, but the bean definition information still needs to be manually configured in xml. The method of scanning beans can be further simplified by adding a configuration section.

<context:component-scan base-package="org.example"/>

Mark classes as bean s through annotations

@Component: indicates that the annotated class is "component". When using annotation based configuration and classpath scanning, this class is regarded as a candidate for automatic detection

@Component
public class RepositoryA implements RepositoryBase {
}
@Component
public class RepositoryB  implements RepositoryBase {
}

@Component
public class ServiceA {
    @Autowired
    private RepositoryA repositoryA;

    @Autowired
    private RepositoryB repositoryB;
   	
    // Omit Getter toString()
}

xml configuration scan package path

xml configuration file, very concise

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--Scan components under the specified package bean And automatically DI-->
    <context:annotation-config/>
    <context:component-scan base-package="com.crab.spring.ioc.demo06"/>
    <!--No more complicated bean Definition of-->

</beans>

Get bean usage in container

The test procedure is similar to that in the previous article.

package com.crab.spring.ioc.demo06;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/13 15:11
 * @For me, please pay attention to the official account of crab Java notes for more technical series.
 */
public class Test {

    @org.junit.Test
    public void test() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        ServiceA serviceA = context.getBean(ServiceA.class);
        RepositoryA repositoryA = context.getBean(RepositoryA.class);
        RepositoryB repositoryB = context.getBean(RepositoryB.class);
        System.out.println(serviceA);
        System.out.println(serviceA.getRepositoryA() == repositoryA);
        System.out.println(serviceA.getRepositoryB() == repositoryB);
        context.close();
    }
}

Operation results

ServiceA{repositoryA=com.crab.spring.ioc.demo06.RepositoryA@27c86f2d, repositoryB=com.crab.spring.ioc.demo06.RepositoryB@197d671}
true
true

Conclusion: repository a, repository B and ServiceA are all scanned into the container for management, and the dependency of ServiceA has been automatically DI. Compared with the traditional XML method, this method is simple and worry-free.

Note: XML mode and annotation configuration mode can be mixed. Annotation injection is performed before XML injection. Therefore, XML configuration will override annotation injection.

@Required use

@The Required annotation is applicable to the bean property setting method. If the container does not have a corresponding bean, an exception will be thrown.

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

@The Required annotation and RequiredAnnotationBeanPostProcessor have been officially discarded since Spring Framework 5.1. A better way is to use constructor injection (or the custom implementation of initializingbean. Afterpropertieset(), or the custom @ PostConstruct method and bean property setter method).

@Autowired uses

Mark the constructor, field, Setter method or configuration method as automatically assembled by Spring's dependency injection tool, and required specifies whether it is required. This is an alternative to the JSR-330 @Inject annotation.

Tag on constructor, field, Setter method

A class that combines three annotation positions

@Component
public class Service1 {
    private RepositoryA repositoryA;
    private RepositoryB repositoryB;
    // Tag field
    @Autowired
    private RepositoryC repositoryC;

    // Tag constructor
    @Autowired
    public Service1(RepositoryA repositoryA) {
        this.repositoryA = repositoryA;
    }

    @Autowired
    public void setRepositoryB(RepositoryB repositoryB) {
        this.repositoryB = repositoryB;
    }

    @Override
    public String toString() {
        return "Service1{" +
                "repositoryA=" + repositoryA +
                ", repositoryB=" + repositoryB +
                ", repositoryC=" + repositoryC +
                '}';
    }
}

The test methods and results are as follows

    @org.junit.Test
    public void test_autowired() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        Service1 bean = context.getBean(Service1.class);
        System.out.println(bean);
        context.close();
    }

// result
Service1{repositoryA=com.crab.spring.ioc.demo06.RepositoryA@79efed2d, repositoryB=com.crab.spring.ioc.demo06.RepositoryB@2928854b, repositoryC=com.crab.spring.ioc.demo06.RepositoryC@27ae2fd0}

From the output results, the three location injection dependencies are all possible.

Starting with Spring Framework 4.3, if the target bean defines only one constructor, you no longer need to use the @ Autowired annotation on such constructors.

@Autowired injection set

Default order

All matching types in the container will be injected automatically, and the default order is the order of bean registration definition.

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/15 17:48
 * @For me, please pay attention to the official account of crab Java notes for more technical series.
 */
@Component
public class Service2 {
    @Autowired
    private List<RepositoryBase> repositoryList;
    @Autowired
    private Set<RepositoryBase> repositorySet;
    @Autowired
    private RepositoryBase[] repositoryArr;
    
    // Omit Getter and Setter
}

Tests and results

    @org.junit.Test
    public void test_collection() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        Service2 service2 = context.getBean(Service2.class);
        System.out.println(service2.getRepositoryList());
        System.out.println(service2.getRepositorySet());
        Arrays.stream(service2.getRepositoryArr()).forEach(System.out::println);
        context.close();
    }
[com.crab.spring.ioc.demo06.RepositoryA@2ddc8ecb, com.crab.spring.ioc.demo06.RepositoryB@229d10bd, com.crab.spring.ioc.demo06.RepositoryC@47542153]
[com.crab.spring.ioc.demo06.RepositoryA@2ddc8ecb, com.crab.spring.ioc.demo06.RepositoryB@229d10bd, com.crab.spring.ioc.demo06.RepositoryC@47542153]
com.crab.spring.ioc.demo06.RepositoryA@2ddc8ecb
com.crab.spring.ioc.demo06.RepositoryB@229d10bd
com.crab.spring.ioc.demo06.RepositoryC@47542153

From the result, the order is ABC.

Specify the order through @ Ordered

Add @ Ordered to repository a, repository B, and repository C. the smaller the value, the higher the priority.

@Component
@Order(0) // Specifies the order in which collections are injected
public class RepositoryA implements RepositoryBase {
}

@Component
@Order(-1)
public class RepositoryB  implements RepositoryBase {
}

@Component
@Order(-2)
public class RepositoryC implements RepositoryBase{
}

Or run the above test_collection, observe the output order.

[com.crab.spring.ioc.demo06.RepositoryC@56a6d5a6, com.crab.spring.ioc.demo06.RepositoryB@309e345f, com.crab.spring.ioc.demo06.RepositoryA@7a4ccb53]
[com.crab.spring.ioc.demo06.RepositoryA@7a4ccb53, com.crab.spring.ioc.demo06.RepositoryB@309e345f, com.crab.spring.ioc.demo06.RepositoryC@56a6d5a6]
com.crab.spring.ioc.demo06.RepositoryC@56a6d5a6
com.crab.spring.ioc.demo06.RepositoryB@309e345f
com.crab.spring.ioc.demo06.RepositoryA@7a4ccb53

From the results, the order is CBA, and the order is in line with expectations.

@Autowired injection Map

As long as the key type of Map is String, even typed Map instances can be assembled automatically.

Define a class, inject map and print

@Component
public class Service3 {
    @Autowired
    private Map<String, RepositoryBase> repositoryMap;

    public void printMap() {
        this.repositoryMap.entrySet().forEach(entry -> {
            System.out.println(entry.getKey() + "--" + entry.getKey());
        });
    }
}

Test the output

    @org.junit.Test
    public void test_map() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        System.out.println("injection map The key value pairs are as follows:");
        Service3 service3 = context.getBean(Service3.class);
        service3.printMap();
        context.close();
    }

injection map The key value pairs are as follows:
repositoryA--repositoryA
repositoryB--repositoryB
repositoryC--repositoryC

From the results, it was successfully injected into the map.

@Autowired with @ Primary

@Primary indicates that when multiple candidates are eligible to automatically assemble single valued dependencies, the bean should be given priority.

Let's take a look at a case without @ Primary.

@Component
@Order(0) // Specifies the order in which collections are injected
public class RepositoryA implements RepositoryBase {
}

@Component
@Order(-1)
public class RepositoryB  implements RepositoryBase {
}

@Component
@Order(-2)
public class RepositoryC implements RepositoryBase{
}
@Component
public class Service4 {

    @Autowired
    private RepositoryBase repositoryBase;

    @Override
    public String toString() {
        return "Service4{" +
                "repositoryBase=" + repositoryBase +
                '}';
    }
}

Since there are three repositorybases in the container, Spring cannot decide which one to choose, so it will throw unsatisfied dependencyexception as follows.

    @org.junit.Test
    public void test_require() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        Service4 service4 = context.getBean(Service4.class);
        System.out.println(service4);
        context.close();
    }
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'service4': Unsatisfied dependency expressed through field 'repositoryBase'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.crab.spring.ioc.demo06.RepositoryBase' available: expected single matching bean but found 3: repositoryA,repositoryB,repositoryC

Add repository C to @ Primary

@Component
@Order(-2)
@Primary
public class RepositoryC implements RepositoryBase{
}

Run the above test again and successfully inject repository C.

Service4{repositoryBase=com.crab.spring.ioc.demo06.RepositoryC@4df50bcc}

@Autowired with Optional

@Autowired has a required attribute, which indicates whether the dependency is optional or not. The default is false. You can use Java util. Optional packaging.

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/14 11:54
 * @For me, please pay attention to the official account of crab Java notes for more technical series.
 */
@Component
public class Service5 {
    private RepositoryA repositoryA;

    public RepositoryA getRepositoryA() {
        return repositoryA;
    }
    @Autowired
    public void setRepositoryA(Optional<RepositoryA> repositoryOptional) {
        RepositoryA repositoryA = repositoryOptional.orElseGet(() -> new RepositoryA());
        this.repositoryA = this.repositoryA;
    }
}

Starting from Spring Framework 5.0, the @ Nullable annotation can also be used to express the concept of nullability.

@Component
public class Service6 {
    private RepositoryA repositoryA;

    @Autowired
    public void setRepositoryA(@Nullable RepositoryA repositoryA) {
        this.repositoryA = this.repositoryA;
    }
}

Cooperate with @ Qualifier

As mentioned above, when there are multiple instances in a container and a Primary candidate needs to be determined, @ Primary is an effective method to use type automatic assembly, with multiple instances. When you need more control over the selection process, you can also use Spring's @ Qualifier annotation. By associating the Qualifier value with a specific parameter, the scope of type matching is reduced so that a specific bean can be selected for each parameter. Look at the case.

Quick use

Configuration of dependent classes

@Component
@Order(0)
public class RepositoryA implements RepositoryBase {
}

@Component("repositoryB") // Name specified
@Order(-1)
public class RepositoryB  implements RepositoryBase {
}

@Component
@Order(-2)
@Primary  // Mark as primary candidate
public class RepositoryC implements RepositoryBase{
}

@Qualifier specifies the name of the bean on the field and constructor

@Component
public class Service7 {
    // Specify to inject repository B
    @Autowired
    @Qualifier("repositoryB")
    private RepositoryBase repository;

    private RepositoryBase repository2;

    // Specify the injection of repositoryA in the constructor
    @Autowired
    public Service7(@Qualifier("repositoryA") RepositoryBase repository2) {
        this.repository2 = repository2;
    }
    // ellipsis
}

Test it and observe the output.

    @org.junit.Test
    public void test_qualifier() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        Service7 service7 = context.getBean(Service7.class);
        System.out.println(service7);
        context.close();
    }
Service1{repository=com.crab.spring.ioc.demo06.RepositoryB@222114ba, repository2=com.crab.spring.ioc.demo06.RepositoryA@16e7dcfd}

Conclusion: RepositoryC is marked with @ Primary annotation, and such instances will be injected under normal circumstances. The @ Qualifier specifies that RepositoryB and RepositoryA are injected, and the verification is successful.

summary

This paper introduces the annotation based Spring container configuration, simplifies the preparation of xml configuration files, and provides two quick start cases. Focus on the analysis of @ Autowired with various annotations to flexibly inject dependency coverage scenarios. The next introduction introduces more annotations and classpath scanning on the basis of.

Source address of this article: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo06

Knowledge sharing, please indicate the source of reprint. There is no order in learning, and the one who reaches is the first!

Keywords: Java

Added by cnl83 on Tue, 01 Feb 2022 12:27:26 +0200