Want to inject static members with @ Autowired? The government doesn’t recommend you, but it still wants to do so

Time:2021-1-16

Life is too short to do something that no one wants. This article has been publishedhttps://www.yourbatman.cnIncluded, including spring technology stack, mybatis, JVM, middleware and other small and beautifulspecial columnFor free learning. Pay attention to the official account.Bat’s Utopia】Break them one by one, master them in depth, and refuse to taste them.

Want to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do so

preface

Hello, everyone. I’m brother a. Through the study of the first two articles in this column, I believe you have a new understanding of the application of static keyword in spring / spring boot, and can explain most of the problems / questions you encounter in your work. This article continues to talk about a more common case of static keyword: using@AutowiredDependency injection static members (properties).

In Java, for static members, we have some basic common sense: static variables (members), which areBelong to classThe same static method belongs to the class, and the common method (instance method) belongs to the object. The spring container managesInstance objectIncluding its@AutowiredDependency injection is the object instance in the container, so static members cannot be used directly@AutowiredInjected.

This is easy to understand: class members are initialized earlier, and they do not need to rely on the creation of instances, so the spring container may not be “born” at this time. What about dependency injection?

This example, you may have known before:

@Component
public class SonHolder {

    @Autowired
    private static Son son;

    public static Son getSon() {
        return son;
    }
}

Then “normal use” the component:

@Autowired
private SonHolder sonHolder;

@Transaction
public void method1(){
    ...
    sonHolder.getSon().toString();
}

Running the program, the result is wrong

Exception in thread "main" java.lang.NullPointerException
    ...

Obviously,getSon()I got a null, so I threw you an NPE.
Want to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do so


Version Convention

Unless otherwise specified, the contents of this article are based on the following versions:

  • JDK:1.8
  • Spring Framework:5.2.2.RELEASE

text

Speaking of@AutowiredNo one is unfamiliar with the function of annotation,automatic assemblyWell. According to the definition of this annotation, it seems that it can be used in many places:

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

In this paper, we focus on the case it uses on the field member attribute, and the focus of this paper is to label it on the static attribute.

Note: Although spring official does not recommend field / attribute injection now, its convenience is still irreplaceable, so it is in the process of implementationBusiness developmentIt’s still the mainstream way to use it


Scene description

If there is such a scenario requirement: to create a room, a group of students and a teacher need to be introduced. At this time, I need to understand theseuserAccording to the rules (such as the test account with the word “test” in the name), check and filter the validity of the data, and then the creation logic can run normally. This case also has the following features:

  • User name / details need to be obtained from UC center by remote call (such as feignclient mode)

    • Therefore, it is necessary to bridge and provide anticorrosive coating
  • This filtering rule is very functional and can be used in many places in the project

    • It’s a bit of a tool. It’s wooden

After reading the “topic”, I feel it’s quite simple. It’s a very normal business requirement case. Let me simulate its implementation.

Get user data from UC user center (use local data to simulate remote access)

/**
 *Simulate to the remote user center, and obtain user data in batches according to IDS
 *
 * @author yourbatman
 * @date 2020/6/5 7:16
 */
@Component
public class UCClient {

    /**
     *Simulate the result return of remote call (there are normal and test data)
     */
    public List<User> getByIds(List<Long> userIds) {
        return userIds.stream().map(uId -> {
            User user = new User();
            user.setId(uId);
            user.setName("YourBatman");
            if (uId % 2 == 0) {
                user.setName(user.getName() + "_test");
            }
            return user;
        }).collect(Collectors.toList());
    }

}

Explanation: the actual situation here may be just a case@FeignClientInterface. This example uses it to mock

Because the function of filtering test user is too highcurrencyAnd the rules also need to be closed and encapsulated, so we have our rulesinsideHelp classUserHelper

/**
 *Tool method: according to user IDs, filter out test users according to certain rules and return results
 *
 * @author yourbatman
 * @date 2020/6/5 7:43
 */
@Component
public class UserHelper {

    @Autowired
    UCClient ucClient;

    public List<User> getAndFilterTest(List<Long> userIds) {
        List<User> users = ucClient.getByIds(userIds);
        return users.stream().filter(u -> {
            Long id = u.getId();
            String name = u.getName();
            if (name.contains("test")) {
                System.out.printf ("id = s, name = s is the test user, filtered, ID, name");
                return false;
            }
            return true;
        }).collect(Collectors.toList());
    }

}

Obviously, it depends internally onUCClientThe result of this remote call. After encapsulation, any component of our business service layer can enjoy the tool as much as possible

/**
 *Business service: classroom service
 *
 * @author yourbatman
 * @date 2020/6/5 7:29
 */
@Service
public class RoomService {

    @Autowired
    UserHelper userHelper;

    public void create(List<Long> studentIds, Long teacherId) {
        //Because students and teachers are collectively referred to as users, they can be put together for verification
        List<Long> userIds = new ArrayList<>(studentIds);
        userIds.add(teacherId);
        List<User> users = userHelper.getAndFilterTest(userIds);

        //... after excluding the test data, execute the creation logic
        System.out.println ("classroom created successfully");
    }

}

Write a test program to simulate service call

@ComponentScan
public class DemoTest {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(DemoTest.class);

        //Analog interface call / unit test
        RoomService roomService = context.getBean(RoomService.class);
        roomService.create(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L), 101L);
    }
}

Run the program and output the result

id=2 name=YourBatman_ Test is the test user, filtered
id=4 name=YourBatman_ Test is the test user, filtered
id=6 name=YourBatman_ Test is the test user, filtered
Classroom created successfully

Everything is so beautiful and peaceful, then why are there the problems pointed out in this paper? As the saying goes, “if you don’t die, you won’t die.” there are always some “extreme” players who like to play with flowers. Let me guess why you want to rely on injecting static member attributes?
Want to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do so


Can you guess why you have such a need?

From the naming of the sample class above, I can probably guess what you mean.UserHelperIt is named as a tool class

  1. Methods are static tool methods
  2. The more convenient to use, the better

    1. Obviously, the static method is the most convenient

The status quo is: UsingUserHelperYou have to deal with user information first@AutowiredIt is inconvenient to inject its instance. So you try to putgetAndFilterTest()This method becomes a static method, so that it can be called directly through the class name instead of relying on the injection of the userhelper instance. So you take it for granted to “optimize”:

@Component
public class UserHelper {

    @Autowired
    static UCClient ucClient;
    
    public static List<User> getAndFilterTest(List<Long> userIds) {
        ... // the processing logic is the same as above
    }
}

Properties and methods are modified with static, so that users can access them directly through the class name (without injection)

@Service
public class RoomService {

    public void create(List<Long> studentIds, Long teacherId) {
        ...
        //Call the static method directly through the class name
        List<User> users = UserHelper.getAndFilterTest(userIds);
        ...
    }
}

Run the program and output the result

07:22:49.359 [main] INFO org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - Autowired annotation is not supported on static fields: static cn.yourbatman.temp.component.UCClient cn.yourbatman.temp.component.UserHelper.ucClient
07:22:49.359 [main] INFO org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - Autowired annotation is not supported on static fields: static cn.yourbatman.temp.component.UCClient cn.yourbatman.temp.component.UserHelper.ucClient
...
Exception in thread "main" java.lang.NullPointerException
    at cn.yourbatman.temp.component.UserHelper.getAndFilterTest(UserHelper.java:23)
    at cn.yourbatman.temp.component.RoomService.create(RoomService.java:26)
    at cn.yourbatman.temp.DemoTest.main(DemoTest.java:19)

I thought it was perfect, but the result was not perfect. I specially pasted two more info logs, which tell you why NPE exceptions are thrown@Autowired does not support tagging on static fields / properties
Want to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do so


Why can’t @ Autowired inject static member properties

Static variables belong toClass itselfWhen the class loader loads static variables, the context of springNot yetIs loaded, so it is impossible to bind values to static variables (this is just the most superficial reason, not accurate). At the same time, spring doesn’t encourage injecting values into static variables (implication: not impossible), because it thinks it will increase the coupling and is not test friendly.

These are appearances, so how does spring actually “operate”? We follow theAutowiredAnnotationBeanPostProcessorThe output of this info log is here:

AutowiredAnnotationBeanPostProcessor:

//Building @ Autowired injection metadata method
//In short, it is to find out which dependency injection is required under the class
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    ...
    //Loop recursion, because the parent class should also be managed
    do {
        //Traverse all fields (including static fields)
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            if (Modifier.isStatic(field.getModifiers())) {
                logger.info("Autowired annotation is not supported on static fields: " + field);
            }
            return;
            ...
        });
        //Traverse all methods (including static methods)
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            if (Modifier.isStatic(method.getModifiers())) {
                logger.info("Autowired annotation is not supported on static methods: " + method);
            }
            return;
            ...
        });
        ...
        targetClass = targetClass.getSuperclass();
    } while (targetClass != null && targetClass != Object.class);
    ...
}

These lines of code explain why spring doesn’t give static fields / static methods execution@AutowiredInjectedThe real reason: when scanning the metadata to be injected into the class, the static members (including properties and methods) are directly selected and ignored.

So where is the entrance to the process? Is spring really unable to assign values to static members at this stage and chooses to ignore it? Let’s continue to call this method. The only use of this method isfindAutowiringMetadata()Method, and it is called in three places:

Call place 1: the execution time is earlier, in theMergedBeanDefinitionPostProcessorDuring BD merging, the metadata to be injected will be parsed and checked. It will act on each BD, so the first sentence of the two info logs in the above example is output from this

AutowiredAnnotationBeanPostProcessor:

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    metadata.checkConfigMembers(beanDefinition);
}

Call place 2: inInstantiationAwareBeanPostProcessorthat isAfter the instance is createdTo assign a value to a property (i.epopulateBean()Implementation. So it will also act on each BD. in the above example, the second sentence of the two info logs is output from this

AutowiredAnnotationBeanPostProcessor:

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        metadata.inject(bean, beanName, pvs);
    }
    ...
    return pvs;
}

Call 3: this method is special, it means that for theTarget instance(not just the class, but the instance itself) directly calls the “local” processing method to implement injection. This is a public method provided by spring for “external” use / injection. For example, it is more practical to inject attributes into instances outside the container. This article will introduce how to use this method

Note: spring will not call this method itself, so it will not automatically output logs (this is why there are three calls, but only two logs)

AutowiredAnnotationBeanPostProcessor:

public void processInjection(Object bean) throws BeanCreationException {
    Class<?> clazz = bean.getClass();
    InjectionMetadata metadata = findAutowiringMetadata(clazz.getName(), clazz, null);
    try {
        metadata.inject(bean, null, null);
    }
    ...
}

This part of the source code explains why spring doesn’t let you@AutowiredThe reason for injecting static members. In this case, is there no way to meet my “demands”? The answer is yes, and then look down.


N ways of indirectly implementing static member injection

Although spring will ignore you and use it directly@Autowired + static memberInjection, but there are many ways to do itbypassThese restrictions enable the injection of values into static variables. The following two methods are introduced for reference:

Method 1: use the set method as a springboard to assign values to static members

@Component
public class UserHelper {

    static UCClient ucClient;

    @Autowired
    public void setUcClient(UCClient ucClient) {
        UserHelper.ucClient = ucClient;
    }
}

Method 2: use@PostConstructAnnotation, in which static members are assigned values

@Component
public class UserHelper {

    static UCClient ucClient;

    @Autowired
    ApplicationContext applicationContext;
    @PostConstruct
    public void init() {
        UserHelper.ucClient = applicationContext.getBean(UCClient.class);
    }
}

Although it is called two ways, in fact, I think thinking is just oneDelay assigning values to static member properties. Therefore, based on this ideaExactlyThere will be n implementation schemes (just make sure you assign values to them before using them). You can think for yourself. Brother a doesn’t need to give examples one by one.


Advanced implementation

AswelfareBrother a here provides a higher level (BI) implementation mode for your learning and reference

@Component
public class AutowireStaticSmartInitializingSingleton implements SmartInitializingSingleton {

    @Autowired
    private AutowireCapableBeanFactory beanFactory;

    /**
     *When all the singleton Bena are initialized, the static members are assigned values
     */
    @Override
    public void afterSingletonsInstantiated() {
        //Because it is to assign values to static attributes, it is feasible to inject a new instance here
        beanFactory.autowireBean(new UserHelper());
    }
}

Userhelper classNo longer neededtagging@ComponentAnnotation, that is to say, it no longer needs to be managed by the Spirng container (the static tool class really does not need to be managed by the container, after all, we do not need to use its instance), which is also a manifestation of cost saving to some extent.

public class UserHelper {

    @Autowired
    static UCClient ucClient;
    ...
}

Run the program and output the result

08:50:15.765 [main] INFO org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - Autowired annotation is not supported on static fields: static cn.yourbatman.temp.component.UCClient cn.yourbatman.temp.component.UserHelper.ucClient
Exception in thread "main" java.lang.NullPointerException
    at cn.yourbatman.temp.component.UserHelper.getAndFilterTest(UserHelper.java:26)
    at cn.yourbatman.temp.component.RoomService.create(RoomService.java:26)
    at cn.yourbatman.temp.DemoTest.main(DemoTest.java:19)

report errors. Of course, I did it on purpose. Although it was abnormal, did you see our progressThe info log only prints one sentence(think for yourself why.). I don’t want to make a fuss. The correct posture should be as follows:

public class UserHelper {

    static UCClient ucClient;
    @Autowired
    public void setUcClient(UCClient ucClient) {
        UserHelper.ucClient = ucClient;
    }
}

Run the program again,business as usual(info log will not output either.). I think there are three advantages in this way:

  1. It is more controllable to manage the dependency injection of this case manually. Instead of leaving it to the spring container for automatic processing
  2. ToolsitselfIt doesn’t need to be added to the spring container, which can save the cost if there are a large number of such cases
  3. It’s a bit advanced, and it’s an artifact of X (don’t underestimate x, it’s a favorite word, your salary increase often comes from the success of x)

Of course, you can do the same:

@Component
public class AutowireStaticSmartInitializingSingleton implements SmartInitializingSingleton {

    @Autowired
    private AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor;
    @Override
    public void afterSingletonsInstantiated() {
        autowiredAnnotationBeanPostProcessor.processInjection(new UserHelper());
    }
}

It can still work normally. Isn’t that exactly what we’ve been talking aboutCall office 3Well, I’ll apply what I’ve learned right away. Have a good time.
Want to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do so


Suggestions for use

If you need to use this kind of tool, you need to know what a real util tool class is? If your tool class has external dependencies, it depends on theexample, then it can’t be called a tool class. Please don’t use it as static. It’s easy to play bad. You can use it nowIt happens to beAll instances managed by spring are managed by defaultSingle caseSo you can assign the value once. What if one day it becomes more than one instance (even if the probability is very small)?

There is a hidden danger in forcing people to do so. At the same time, it also breaks the relationship of priority and life cycle, which makes “beginners” feel confused. Of course, if you insist on using it in this way, then please do a good job in relevant specifications / reduction. For example, using the Bi usage method recommended by me above is a better choice. At this timeManual managementIt is often safer than automatic, reducing the possible maintenance cost in the later stage.


Thinking questions

  1. In the@AutowiredWhen metadata is injected, the Spring Factory / container is ready, theoreticallyAbsolutelyThe ability to help you complete the injection / assignment. In this case, why does spring “refuse” to do so? Can you inject static members directly?
  2. since@AutowiredYou can’t inject static attributes. What about static methods? @What about the value annotation?

summary

This paper introduces the relationship between spring dependency injection and static, and explains the use background and cause analysis. Brother a thinks it’s delicious, and it should be of great help to you.

Finally, I would like to say to my friends: dependence injectionMain purpose, is to let the container generate an instance of an object, then manage its lifecycle, andIn the life cycleUsing them makes unit testing easier (what? If you don’t write unit testing, you should pay attention to me. The next column will focus on unit testing. And if you use static variables / class variables, it’s easyExpandedThe scope of use makes it uncontrollable. This kind of static field isimplicit sharing Spring doesn’t recommend you to do this, so be careful when using it~


Follow brother a

Author Your Batman
Personal site www.yourbatman.cn
E-mail [email protected]
WeChat fsx641385712
Active platform Want to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do so Want to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do soWant to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do soWant to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do soWant to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do soWant to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do soWant to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do soWant to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do so
official account Utopia of bat (ID: bat Utopia)
Knowledge planet Bat’s Utopia
Daily article recommendation Daily article recommendation

Want to inject static members with @ Autowired? The government doesn't recommend you, but it still wants to do so