[Серия Spring] Вызов метода SpringContext.getBean() вызывает NPE?

Spring Boot задняя часть Spring

«Это 15-й день моего участия в ноябрьском испытании обновлений. Подробную информацию об этом событии см.:Вызов последнего обновления 2021 г.".

В реальной бизнес-разработке, чтобы облегчить получение объектов bean-компонентов в контейнере Spring, распространенным случаем является создание класса SpringUtil, который содержит контекст SpringContext внутри, а затем предоставляет статический способ получения объектов bean-компонентов. поза, Случайный может привести к npe

Давайте посмотрим на эту сцену сегодня

воспроизведение сцены

1. Основное инженерное сооружение

Создайте базовый проект SpringBoot, конкретный процесс здесь опущен, а ключевая информация отмечена ниже.

Этот проект используетSpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEAразвивать

Откройте веб-сервис для тестирования

<dependencies>
    <!-- 邮件发送的核心依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2. SpringUtil

Строительство фонда Spramutil Tools, контекст помогите удерживать 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) {
        return environment.getProperty(key);
    }
}

3. Пример использования

Сначала создайте простой объект bean

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

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

Затем есть еще один объект, основанный на вышеуказанном объекте, основной интерфейс, предоставляемый извне,processЕго внутренняя реализация зависит от класса перечисления, выбор политики;

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

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

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

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

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

        private String data;

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

        abstract String doProcess();

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

Сосредоточьтесь на классе перечисления в приведенной выше реализации, в классе перечисления он получен согласно SpringUtilBasicDemoобъект, а затем выполнить его частный методshow()и внутрипакетные методыtest()

Есть ли проблема с этим использованием?

4. Тестовый случай

Затем напишите простые тестовые интерфейсы

@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) {
        return basicDemo.process(data);
    }
}

Посетите следующий, чтобы увидеть, что происходит

Что? Ты имеешь в виду npe? Это ли не нормальный возврат! ! !

Далее - момент, чтобы свидетельствовать об ошибке, тот же код выше, пусть он появится NPE

5. Повторение ошибки

Затем мы добавляем грань, цель состоит в том, чтобы пропуститьSpringUtil.getBeanПолученный объект является прокси-классом

// 注意在这个方法所在类上,添加注解 @Aspect
@Around("execution(public * com.git.hui.boot.web.interceptor.server.BasicDemo.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    return joinPoint.proceed();
}

Затем повторно запрашивайте вышеупомянутый доступ

доступ к закрытым методамshow()Здесь выбрасывается исключение.Из стека сервера видно, что тип исключения NPE.Основная причина в том, чтоtestDemoнулевой

Просто при доступе к приватному методу класса агента, если есть объект bean-инъекции, я в это время получаю NULL.

Это немного волшебно, так что давайте изменим его еще раз. Внедренный объект bean-компонента не используется напрямую в приватном методе, а вместо этого вызывается общедоступный метод объекта bean-компонента. Что происходит?

Перепишите приведенный выше метод show()

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

public String show2() {
    return testDemo.showCase();
}

Протестируйте еще раз, выводите как

На самом деле нет никаких проблем! ! !

Это так удивительно, в чем причина?

  • Ключевые точки знаний: логика генерации прокси-классов Spring

6. Резюме

Кажется, я только что вошел в основную часть, и результат здесь. Это слишком много 😡, вот краткое изложение сцены, в которой возникает эта проблема. Что касается конкретных причин, они будут представлены в следующем сообщении в блоге. .

Когда мы получаем объект bean-компонента через SpringContext, не обращайтесь напрямую к его закрытым методам, что может привести к npe.

100% первая сцена

  • Этот объект bean-компонента имеет прокси-класс (если какой-либо аспект перехватывает его, например, некоторые определенные аннотации внутри класса)
  • Инъективные объекты используются в приватных методах.

Увидев вышеизложенное, возникнет вопрос, а кто будет иметь доступ к приватным методам? У меня нет дыры в голове😒, не говоря уже о том, что к приватным методам нельзя получить доступ извне.

Это включает в себя довольно распространенный сценарий.Внутренний метод класса A вызывает метод B, который вы хотите перехватить аспектом.В настоящее время мы часто делаем это

public class A {
    @Autowired
    private A a;

    public void test() {
        a.testB();
    }
    
    @Point
    public String testB() {
        return "hello";
    }
}

В приведенном выше тестовом методе вы можете получить доступ к методу testB, чтобы следовать логике аспекта.В приведенном выше классе можно напрямую использоватьa.privetMethod()сцена

Кроме того, возможен доступ к приватным методам, когда отражение выполняет некоторую логику, которая здесь не будет раскрываться;

Заинтересованные друзья могут ответить и пообщаться, и вы также можете подписаться на мой официальный аккаунт:серый блог

III. Исходный код и связанные с ним знания, которые нельзя пропустить

0. Проект

1. Публичный аккаунт WeChat: блог Yihuihui

Это не так хорошо, как письмо. Вышеупомянутое содержание чисто из семьи. Из-за ограниченных личных способностей неизбежно будут упущения и ошибки. Если вы обнаружите ошибки или у вас есть лучшие предложения, вы можете критиковать и поправьте их.

Ниже представлен серый личный блог, в котором записываются все посты в блоге по учебе и работе, приглашаю всех посетить

  • One Grey BlogПерсональный блогblog.hhui.top
  • Серый блог - специальный весенний блогspring.hhui.top