“This is the 15th day of my participation in the November Gwen Challenge. See details of the event: The Last Gwen Challenge 2021”.

In real business development, a common case for getting Bean objects from the Spring container is to create a SpringUtil class that holds the SpringContext context and then provides a static way to get Bean objects. An indiscretion can lead to AN NPE

Today we’ll take a look at this scenario

Scene: the repetition

1. Infrastructure construction

Set up a basic SpringBoot project, the detailed process is omitted here, and the key information is marked below

This project is developed by SpringBoot 2.2.1.RELEASE + Maven 3.5.3 + IDEA

Open a Web service for testing

<dependencies>
    <! -- Core dependencies for sending messages -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
Copy the code

2. SpringUtil

Build a basic SpringUtil utility class that holds the context with SpringContextAware

@Component
public class SpringUtil implements ApplicationContextAware.EnvironmentAware {
    private static ApplicationContext applicationContext;
    private static Environment environment;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtil.applicationContext = applicationContext;
    }

    @Override
    public void setEnvironment(Environment environment) {
        SpringUtil.environment = environment;
    }

    public static <T> T getBean(Class<T> clz) {
        return applicationContext.getBean(clz);
    }

    public static String getProperty(String key) {
        returnenvironment.getProperty(key); }}Copy the code

3. Example

Start by building a simple bean object

@Component
public class TestDemo {
    public String showCase(a) {
        return UUID.randomUUID().toString();
    }

    public String testCase(a) {
        return "test-"+ Math.random(); }}Copy the code

Then there’s another object that relies on the above object to provide the main interface to process, whose internal implementation is a policy choice based on enumerated classes;

@Component
public class BasicDemo {
    @Autowired
    private TestDemo testDemo;

    public String process(String data) {
        return Data.process(data);
    }

    private String show(a) {
        return testDemo.showCase();
    }

    String test(a) {
        return testDemo.testCase();
    }

    public enum Data {
        SHOW("show") {
            @Override
            String doProcess(a) {
                return SpringUtil.getBean(BasicDemo.class).show();
            }
        },
        CASE("test") {
            @Override
            String doProcess(a) {
                returnSpringUtil.getBean(BasicDemo.class).test(); }};private String data;

        Data(String data) {
            this.data = data;
        }

        abstract String doProcess(a);

        static String process(String data) {
            for (Data d: values()) {
                if (d.data.equalsIgnoreCase(data)) {
                    returnd.doProcess(); }}return null; }}}Copy the code

Focus on the enumeration class in the above implementation, where you get the BasicDemo object from SpringUtil and execute its private method show() and the in-package method test()

What’s wrong with this usage?

4. The test case

Let’s write a simple interface test

@Aspect
@RestController
@SpringBootApplication
public class Application implements WebMvcConfigurer {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }

    @Autowired
    private BasicDemo basicDemo;

    @GetMapping(path = "show")
    public String show(String data) {
        returnbasicDemo.process(data); }}Copy the code

What happens next

what? What happened to nPE? This is not a very normal return!!

Now it’s time to witness the bug, and the same code above, let it appear in the NPE

5. Bug repetition

Next we add a facet so that the object we get through springutil.getBean is a proxy class

// Notice that on the class of this method, add the annotation @aspect
@Around("execution(public * com.git.hui.boot.web.interceptor.server.BasicDemo.*(..) )"
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    return joinPoint.proceed();
}
Copy the code

Then request the above access again

An exception is thrown when accessing the private method show(). The stack on the server side shows that the exception type is NPE, mainly because testDemo is null

When accessing a private method of a proxy class, you will get null if there is an injected bean object inside

Instead of using the injected bean object inside a private method, what happens if you call a common method on a bean object instead

Rewrite the show() method above

private String show(a) {
    return show2();
}

public String show2(a) {
    return testDemo.showCase();
}
Copy the code

Test again, and the output is as follows

Surprisingly no problem!!

Is that amazing? What’s the reason?

  • Key point: The generation logic of Spring proxy classes

6. Summary

Seems to have just entered the main body, the results to the end here, it is too much 😡, here the first summary of the problem of the scene, as for the specific reasons to be introduced in the next piece of blog post

When we fetch a bean object through SpringContext, do not directly access its private methods, which may result in an NPE

100% mandatory scenario

  • The bean object has a proxy class (if an aspect intercepts it, such as some specific annotation inside the class)
  • An injected object is used within a private method

The question is, who is going to access private methods? My brain has no pit 😒, besides private methods in the outside also access ah

This involves A fairly common scenario, which we often do when method A inside A class calls method B that we want the section to intercept

public class A { @Autowired private A a; public void test() { a.testB(); } @Point public String testB() { return "hello"; }}Copy the code

The test method, called testB, can be accessed through the section logic. In the above class, it is possible to use a.rivetMethod () directly

In addition, it is possible to access private methods when reflection performs certain logic, so I will not expand here;

Welcome interested partners to reply and interact, you can also pay attention to my public number: A Grey blog

III. Can’t miss the source code and related knowledge points

0. Project

  • Project: github.com/liuyueyi/sp…
  • Source: github.com/liuyueyi/sp…

1. Wechat official account: Yash Blog

As far as the letter is not as good, the above content is purely one’s opinion, due to the limited personal ability, it is inevitable that there are omissions and mistakes, if you find bugs or have better suggestions, welcome criticism and correction, don’t hesitate to appreciate

Below a gray personal blog, record all the study and work of the blog, welcome everyone to go to stroll

  • A grey Blog Personal Blog blog.hhui.top
  • A Grey Blog-Spring feature Blog Spring.hhui.top