This article has participated in the weekend study plan, click the link to see details: Weekend Study Plan

Accumulate over a long period, constant dripping wears away a stone 😄

preface

I believe the @Autowired annotation has been one of the most used annotations in our actual development. Do you really know all the ways to use it? And I’m going to take you through it.

define

Let’s first look at the definition of the @Autowired annotation

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	/**
	 * Declares whether the annotated dependency is required.
	 * <p>Defaults to {@code true}.
	 */
	boolean required(a) default true;
}
Copy the code

Notice that the @target value can be used on constructors, methods, method parameters, field attributes, and annotation classes. There is only one attribute required in the annotation, which defaults to true. This attribute is used to determine whether the Autowired annotated attribute must be injected. A value of true is required and an exception will be thrown if no matching injection item is found. If false, no matching injection item is found, null is assigned to the attribute of the annotation and no exception is thrown.

use

Parameter using

For example, this is definitely the most common way we use, without any of them:

@Component
public class UserService {

    @Autowired
    OrderService orderService;

    public UserService(a){
        System.out.println("No-parameter structural injection" + orderService);
    }

    public void test(a){
        System.out.println("test="+orderService); }}@Component
public class OrderService {}@ComponentScan(basePackages = {"com.gongj.populateBean"})
public class AppApplication {}Copy the code
public static void main(String[] args) {
    AnnotationConfigApplicationContext context = 
        newAnnotationConfigApplicationContext(AppApplication.class); UserService bean = context.getBean(UserService.class); bean.test(); } result: XML no-argument construct injectionnull
test=com.gongj.populateBean.OrderService@156643d4
Copy the code

The constructor uses:

@Component
public class UserService {

    //@Autowired
    OrderService orderService;


    public UserService(a){
        System.out.println("XML no-parameter construct injection" + orderService);
    }

    @Autowired
    public UserService(OrderService orderService1){
        System.out.println("Parametric structural injection" + orderService1);
        orderService = orderService1;
    }

    public UserService(OrderService orderService1,OrderService orderService2){
        System.out.println("Two parameter construction injection" + orderService1 +"= = ="+ orderService2);
        orderService = orderService1;
    }

    public void test(a){
	System.out.println("test="+ orderService); }} the result: a reference structure into com. Gongj. PopulateBean. The OrderService @ 3 d012ddd test = com. Gongj. PopulateBean. 3 d012ddd OrderService @Copy the code

Whichever constructor you append the @Autowired annotation to, you use to instantiate the object.

For common methods:

@Autowired
public void yyy(OrderService orderService){
    System.out.println("yyy=" + orderService);
    this.orderService = orderService; } result: XML no-argument construct injectionnull
yyy=com.gongj.populateBean.OrderService@6504e3b2
test=com.gongj.populateBean.OrderService@6504e3b2
Copy the code

The method name is arbitrary.

For method parameters:

public void xxx(@Autowired OrderService orderService){
    System.out.println("xxx=" + orderService);
    this.orderService = orderService; } result: XML no-argument construct injectionnull
null
Copy the code

You can see that using the @Autowired annotation on method parameters does not work. Why is that? Well, Spring already shows that. I don’t know if you noticed, but there is a long English comment at the top of the # annotation source code, including a paragraph like this:

 * <h3>Autowired Parameters</h3>
 * <p>Although {@code @Autowired} can technically be declared on individual method
 * or constructor parameters since Spring Framework 5.0. most parts of the * framework ignore such declarations. The only part of the core Spring Framework * that actively supports autowired parameters is the JUnit Jupiter support in * the {@code spring-test} module (see the
 * <a href="https://docs.spring.io/spring/docs/current/spring-framework- reference/testing.html#testcontext-junit-jupiter-di">TestContext framework</a>
 * reference documentation for details).
Copy the code

The @autowired annotation can be declared on method parameters, but it is ignored by most of the Spring Framework 5.0 frameworks. Only the Spring-test module supports this notation. But in fact, the use of 4.3.10.release on method parameters has no effect. In 3.0.5.RELEASE, @autowired annotation on method parameters is not supported.

But you can use it in this case:

@Bean
public OrderService user(@Autowired OrderService o){
        System.out.println("user.o = "+ o);
        return newOrderService(); } result: XML no-argument construct injectionnull
user.o = com.gongj.populateBean.OrderService@1cd072a9
test=null
Copy the code

Collection, Map, array injection

@Autowired
private List<OrderService> os;

@Autowired
private Map<String,OrderService> maps;

@Autowired
OrderService[] ar;

public void otherAutowiring(a) { System.out.println(os); System.out.println(maps); System.out.println(ar); } the result:  [com.gongj.populateBean.OrderService@182decdb] {orderService=com.gongj.populateBean.OrderService@182decdb} [Lcom.gongj.populateBean.OrderService;@3796751bCopy the code

If the injected type is Map, the type of key must be String, the default value of key is beanName for each bean, and value is the specified bean type. This approach is used quite a lot, Spring combined with the policy pattern, really not cool.


Type found multiple

We all know that @AutoWired is injected by default by the injected type (byType). If there are two or more beans of the same type in the container, look up byName by property name or method parameter name. Most people will say that byType is followed by byName, but there are several layers of filtering behind byType, and if none of them are satisfied, byName is applied. Each of these approaches is described using examples.

The first step

Create an IPro interface and write a GET method.

public interface IPro {

	void get(a);

}
Copy the code

The second step

Create two subclasses, ProductService and ScienceService, and override the methods of its parent interface.

@Service
public class ProductService implements IPro{

	@Override
	public void get(a) {
		System.out.println("ProductService = "+ ProductService.class); }}@Service
public class ScienceService implements IPro{
	@Override
	public void get(a) {
		System.out.println("ScienceService = "+ ScienceService.class); }}Copy the code

The third step

Create an injection class TypeManyTest, inject IPro into the TypeManyTest class and call its GET method.

@Component
public class TypeManyTest {

    @Autowired
    IPro pro;

    public void test(a){ pro.get(); }}Copy the code

The fourth step

Modify the startup class, get the TypeManyTest bean from the container, and call its test method.

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = 
        new AnnotationConfigApplicationContext(AppApplication.class);
    TypeManyTest bean = context.getBean(TypeManyTest.class);
    bean.test();
}
Copy the code

If nothing unexpected happens, the following exceptions occur:

No qualifying bean of type 'com.gongj.populateBean.IPro' available: 
expected single matching bean but found 2: productService,scienceService
Copy the code

Find two beans according to the IPro type, and then find another bean according to the Pro as beanName, find no match, throw an exception. Change pro to productService or scienceService. ByName = byType = byName But let me look at some of the other solutions.

@Qualifier

Add the @qualifier annotation. By using the @qualifier annotation, we can specify which bean needs to be injected.

@Autowired
@Qualifier("productService")IPro pro; Start the project again and the result is ProductService =class com.gongj.populateBean.ProductService
Copy the code

You might think @qualifier is beanName. Note: the @qualifier value is not necessarily a beanName, it just compares it to a beanName that successfully matches the type. We can also use it on the ProductService class to achieve the same effect.

@Service
@Qualifier("XXXX")
public class ProductService implements IPro{

	@Override
	public void get(a) {
		System.out.println("ProductService = "+ ProductService.class); }} use:@Autowired
@Qualifier("XXXX")
IPro pro;
Copy the code

@Primary

The @primary attribute is the Primary attribute of BeanDefinition. We add annotations to the classes that need to be used by default.

@Service
@Primary
public class ScienceService implements IPro{
	@Override
	public void get(a) {
		System.out.println("ScienceService = "+ ScienceService.class); }}// The previous annotation @qualifier ("XXXX") needs to be removed or ProductService object should be used
@qualifier is resolved before @primaryUse:@AutowiredIPro pro; Result: ScienceService =class com.gongj.populateBean.ScienceService 
Copy the code

@Priority

@priority It belongs to the Javax. annotation, JSR250 specification. Priority is defined by @priority. The smaller the number, the higher the Priority.

@Service
@Qualifier("XXXX")
@Priority(3)  // Priority is 3
public class ProductService implements IPro{

	@Override
	public void get(a) {
		System.out.println("ProductService = "+ ProductService.class); }}@Service
@Priority(1)  // The priority is 1
public class ScienceService implements IPro{
	@Override
	public void get(a) {
		System.out.println("ScienceService = "+ ScienceService.class); }} use:@AutowiredIPro pro; Result: ScienceService =class com.gongj.populateBean.ScienceService
Copy the code

The source address

The method in DefaultListableBeanFactory class. Write the candidates for this Map. In the example above, the value is two because two matching beans can be found based on the IPro type first.

  • ScienceService: scienceService instance objects
  • ProductService: productService instance objects
@Nllable
protected String determineAutowireCandidate(Map
       
         candidates, DependencyDescriptor descriptor)
       ,> { Class<? > requiredType = descriptor.getDependencyType();// fetch @primary bean
        String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
        if(primaryCandidate ! =null) {
                return primaryCandidate;
        }

        // Take the highest Priority bean to define the Priority via @priority, the smaller the number, the higher the Priority
        String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
        if(priorityCandidate ! =null) {
                return priorityCandidate;
        }

        // byName
        for (Map.Entry<String, Object> entry : candidates.entrySet()) {
            String candidateName = entry.getKey();
            Object beanInstance = entry.getValue();
            if((beanInstance ! =null && 
            this.resolvableDependencies.containsValue(beanInstance)) ||
                // Based on the attribute name
                / / descriptor. GetDependencyName () : the field attribute name or method parameter names
                matchesBeanName(candidateName, descriptor.getDependencyName())) {
                
                    returncandidateName; }}return null;
}
Copy the code

The @qualifier annotations are handled in a separate place and are not covered in detail in this article.

conclusion

By default, @autowired is injected byType (byType). If there are two or more beans of the same type in the container, the following filtering conditions will occur:

  • 1. Match the name specified in the @Qualifier annotation. If the match is successful, the field is returned.

  • 2, look for the bean marked by @primary, return if any.

  • 3. Search according to the Priority specified by @priority (x). The smaller the number, the higher the Priority.

  • Finally, the bean is searched by property name or method parameter name, and null is returned if the bean with the specified name has not been found.

  • 5. Check whether @AutoWired’s required attribute is true or whether the injected type is not array, Collection, or Map.

@Autowired(required = false) IPro pro; // This method still throws an exceptionCopy the code

Post the source code

autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
        // The only required beanName is not identified
        if(isRequired(descriptor) || ! indicatesMultipleBeans(type)) {// The current dependency is required, or is not an array or Collection or Map, then an exception is thrown
            return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
        }
        else {
                return null; }}Copy the code

2. @AutoWired can be tagged on more than one constructor of the same class, but the required attribute must all be false. Other constructors are not allowed to have the @autowired annotation when one of them is required true, even if the required attribute is false.

  • If you have any questions or errors in this article, please feel free to comment. If you find this article helpful, please like it and follow it.