Поговорим о механизме расширения в Spring (1)

Java задняя часть Spring контейнер

Большинство предыдущих статей из серии исходных кодов Spring представляют собой анализ базового исходного кода.Благодаря исходному коду мы можем четко понять, что такое Spring, вместо того, чтобы оставаться на поверхности. Например, когда мы хотим использовать аннотацию @Autowired, мы можем получить желаемый bean-компонент, но почему это может стоить задуматься. -- О чтении исходного кода

Чтение исходного кода Spring в сочетании с повседневным использованием может помочь нам лучше понять эту огромную техническую систему.Есть много мест в фактической разработке, которые могут извлечь уроки из некоторых ее идей, чтобы помочь нам лучше реализовать нашу собственную бизнес-логику. В этой статье точка расширения будет использоваться в качестве отправной точки для понимания того, как расширить функцию Bean в Spring в жизненном цикле Spring.

Расширение прослушивателя приложений

ApplicationListenerФактическиspringОсновные понятия в механизме уведомления о событиях; в механизме событий Java обычно есть три понятия:

  • объект события : объект события
  • источник события: источник события, где событие генерируется
  • прослушиватель событий: слушайте события и обрабатывайте их

ApplicationListenerунаследовано отjava.util.EventListener, который предусматриваетSpringРасширение механизма событий в .

ApplicationListenerОн часто используется в реальных бизнес-сценариях, например, мне обычно нравится выполнять загрузку ресурсов или инициализацию некоторых компонентов после инициализации контейнера. Контейнер здесь относится кIocконтейнер, соответствующее событиеContextRefreshedEvent.

@Component
public class StartApplicationListener implements
ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent
    contextRefreshedEvent) {
       //初始化资源文件
       //初始化组件 如:cache
    }
}

Приведенный выше код будет что-то делать после завершения обновления контейнера. Давайте посмотрим, как использовать его через пользовательские события, глядя на конкретныеdemoПеред этим давайте взглянем на некоторые проблемы.

ежедневная работа, если вы хотите использоватьSpringМеханизм распространения событий, нам нужно обратить внимание на следующие моменты:

  • Класс события, используется для описания некоторых свойств самого события, обычно наследуемыхApplicationEvent
  • Класс прослушивателя используется для прослушивания определенных событий и ответа на них. необходимо реализоватьApplicationListenerинтерфейс
  • Класс публикации событий, вам нужно опубликовать время через этот класс, чтобы слушатель мог отслеживать его, вам нужно реализоватьApplicationContextAwareинтерфейс.
  • Передайте класс события и класс слушателя вSpringконтейнер.

Тогда давайте посмотрим на это таким образомdemoконкретная реализация.

Класс события: UserRegisterEvent

UserRegisterEvent, пользователь регистрирует событие, здесь как объект события, унаследованный отApplicationEvent.

/**
 * @description: 用户注册事件
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: guolei.sgl
 * @date: 18/7/25
 */
public class UserRegisterEvent extends ApplicationEvent {

    public String name;

    public UserRegisterEvent(Object o) {
        super(o);
    }

    public UserRegisterEvent(Object o, String name) {
        super(o);
        this.name=name;
    }
}

Класс публикации событий: UserService

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

ApplicationEventPublisherAware: публиковать события, то есть сообщать всем слушателям, связанным с событием, о событии.

/**
 * @description: 用户注册服务,实现ApplicationEventPublisherAware接口
 ,表明本身具有事件发布能力
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: guolei.sgl
 * @date: 18/7/25
 */
public class UserService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    public void setApplicationEventPublisher(ApplicationEventPublisher
    applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void register(String name) {
        System.out.println("用户:" + name + " 已注册!");
        applicationEventPublisher.publishEvent(new UserRegisterEvent(name));
    }
}

здесьUserServiceфактически существует как источник событий, черезregisterРаспространение событий регистрации пользователей. Затем необходимо определить, как отслеживать это событие, а также потреблять и обрабатывать событие, здесь черезApplicationListenerЧто нужно сделать.

Класс прослушивания: BonusServerListener

Когда пользователь инициирует операцию регистрации, в службу точек отправляется сообщение для инициализации точек для пользователя.

/**
 * @description: BonusServerListener
 积分处理,当用户注册时,给当前用户增加初始化积分
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: guolei.sgl
 * @date: 18/7/25
 */
public class BonusServerListener implements
ApplicationListener<UserRegisterEvent> {
    public void onApplicationEvent(UserRegisterEvent event) {
        System.out.println("积分服务接到通知,给 " + event.getSource() +
        " 增加积分...");
    }
}

зарегистрироваться в контейнере

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
        
    <bean id="userService" class="com.glmapper.extention.UserService"/>
    <bean id="bonusServerListener"
    class="com.glmapper.extention.BonusServerListener"/>
    
</beans>

клиентский класс

/**
 * @description: 客户端类
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: guolei.sgl
 * @date: 18/7/25
 */
public class MainTest {
    public static void main(String[] args) {
        ApplicationContext context =new 
        ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService)
        context.getBean("userService");
        //注册事件触发
        userService.register("glmapper");
    }
}

В клиентском классе зарегистрируйтеnameзаglmapperпользователь, результат выполнения:

用户:glmapper 已注册!
积分服务接到通知,给 glmapper 增加积分...

Теперь рассмотрим другую задачу, добавим функцию отправки письма пользователю после регистрации пользователя. На самом деле это просто добавление класса слушателя, при условии, что слушатель прослушивает текущее событие.

/**
 * @description: 邮件服务监听器,当监听到用户的注册行为时,
    给用户发送邮件通知
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: guolei.sgl
 * @date: 18/7/25
 */
public class EmailServerListener implements
ApplicationListener<UserRegisterEvent> {
    public void onApplicationEvent(UserRegisterEvent event) {
        System.out.println("邮件服务接到通知,给 " + event.getSource() +
        " 发送邮件...");
   

Вот еслиUserRegisterEventзаменитьUserLoginEvent, то у почтового сервиса не будет никакого поведения.

Результат выполнения после добавления класса прослушивателя отправки почты:

用户:glmapper 已注册!
邮件服务接到通知,给 glmapper 发送邮件...
积分服务接到通知,给 glmapper 增加积分...

SpringМеханизм распространения событий основан на шаблоне наблюдателя (Observer), он может конвертироватьSpring Beanизменения определяются как событияApplicationEvent,пройти черезApplicationListenerмониторApplicationEventсобытие, один разSpring BeanиспользоватьApplicationContext.publishEvent( ApplicationEvent event )После публикации событияSpringКонтейнер уведомит всех зарегистрированных в контейнереApplicationListenerКласс реализации интерфейса, наконецApplicationListenerКласс реализации интерфейса определяет, следует ли обрабатывать только что выпущенныйApplicationEventсобытие.

Расширение ApplicationContextAware

ApplicationContextAwareтолько один изsetApplicationContextметод. ДостигнутоApplicationContextAwareКласс интерфейса можно найти вBeanПолучено во время загрузкиSpringконтекст приложенияApplicationContext,пройти черезApplicationContextможет получитьSpringМного информации внутри контейнера.

Обычно это требует ручного сбораBeanбудет использоваться при внедрении объектов экземпляра. Ниже приведен простойdemoвыяснить.

GlmapperApplicationContextдержатьApplicationContextобъект, реализуяApplicationContextAwareинтерфейс, чтобы датьApplicationContextделать задания.

/**
 * @description: GlmapperApplicationContext
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: guolei.sgl
 * @date: 18/7/29
 */
public class GlmapperApplicationContext implements
ApplicationContextAware {

    private  ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext
    applicationContext) throws BeansException {
            this.applicationContext=applicationContext;
    }

    public ApplicationContext getApplicationContext(){
        return applicationContext;
    }
}

нужно получить вручнуюbean:

/**
 * @description: HelloService
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: guolei.sgl
 * @date: 18/7/29
 */
public class HelloService {
    public void sayHello(){
        System.out.println("Hello Glmapper");
    }
}

Настройте в файле конфигурации:

<bean id="helloService"
class="com.glmapper.extention.applicationcontextaware.HelloService"/>

<bean id="glmapperApplicationContext"
class="com.glmapper.extention.applicationcontextaware.GlmapperApplicationContext"/>

Вызов клиентского класса:

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext context = new
        ClassPathXmlApplicationContext("beans.xml");
        
        HelloService helloService = (HelloService)
        context.getBean("helloService");
        helloService.sayHello();

        //这里通过实现ApplicationContextAware接口的类来完成bean的获取
        GlmapperApplicationContext glmapperApplicationContext =
        (GlmapperApplicationContext) context.getBean("glmapperApplicationContext");
        
        ApplicationContext applicationContext =
        glmapperApplicationContext.getApplicationContext();
        
        HelloService glmapperHelloService = (HelloService)
        applicationContext.getBean("helloService");
        
        glmapperHelloService.sayHello();
    }
}

Расширение BeanFactoryAware

мы знаемBeanFactoryэто всеIocИнтерфейс верхнего уровня контейнера, определяющий базовое поведение контейнера. выполнитьBeanFactoryAwareИнтерфейс указывает, что текущий класс специфиченBeanFactoryСпособность.

BeanFactoryAwareЕсть только один интерфейсsetBeanFactoryметод. ДостигнутоBeanFactoryAwareКласс интерфейса можно найти вBeanзагружается в процессе загрузкиBeanизBeanFactory, вы также можете получить этоBeanFactoryдругие загружены вBean.

Подумай над вопросом, почему нам нужно пройтиBeanFactoryизgetBeanполучитьBeanШерстяная ткань? Spring предоставил множество удобных методов впрыска, затем черезBeanFactoryизgetBeanполучитьBeanКаковы преимущества? Посмотрите на сцену.

теперь естьHelloService,этоHelloServiceЧтобы поздороваться, нам нужно поздороваться на разных языках, таких как китайский и английский. Общая практика такова:

public interface HelloService {
    void sayHello();
}

//英文打招呼实现
public class GlmapperHelloServiceImpl implements HelloService {
    public void sayHello() {
        System.out.println("Hello Glmapper");
    }
}

//中文打招呼实现
public class LeishuHelloServiceImpl implements HelloService {
    public void sayHello() {
        System.out.println("你好,磊叔");
    }
}

Клиентский класс должен вызываться следующим образом:

if (condition=="英文"){
    glmapperHelloService.sayHello();
}
if (condition=="中文"){
    leishuHelloService.sayHello();
}

Если однажды босс сказал, что нам нужно выйти на международный уровень, нам нужно внедрить все языки в мире, чтобы приветствовать. Вы говорите «да» или не можете это контролировать?

Итак, есть ли способ динамически решить, какую языковую реализацию вызывает мой клиентский класс, вместо того, чтобы использовать метод if-else для ее перечисления? Да, для этих сценариев, требующих динамического захвата объекта,BeanFactoryAwareЭто можно сделать хорошо. Хорошо, давайте посмотрим на преобразование кода:

вводитьBeanFactoryAware:

/**
 * @description: 实现BeanFactoryAware ,让当前bean本身具有 BeanFactory 的能力
 *
 * 实现 BeanFactoηAware 接口的 bean 可以直接访问 Spring 容器,被容器创建以后,
 * 它会拥有一个指向 Spring
 容器的引用,可以利用该bean根据传入参数动态获取被spring工厂加载的bean
 *
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: guolei.sgl
 * @date: 18/7/29
 */
public class GlmapperBeanFactory implements BeanFactoryAware {

    private BeanFactory beanFactory;

    public void setBeanFactory(BeanFactory beanFactory) throws
    BeansException {
        this.beanFactory=beanFactory;
    }
    /**
     * 提供一个execute 方法来实现不同业务实现类的调度器方案。
     * @param beanName
     */
    public void execute(String beanName){
        HelloService helloService=(HelloService)
        beanFactory.getBean(beanName);
        helloService.sayHello();
    }

}

Здесь для логического понимания добавим ещеHelloFacadeкласс, роль этого класса состоит в том, чтобы удерживатьBeanFactoryAwareобъект экземпляра, затем передатьHelloFacadeМетоды объекта экземпляра для маскировки основногоBeanFactoryAwareДетали реализации экземпляра.

public class HelloFacade {
    private GlmapperBeanFactory glmapperBeanFactory;
    //调用glmapperBeanFactory的execute方法
    public void sayHello(String beanName){
        glmapperBeanFactory.execute(beanName);
    }
    public void setGlmapperBeanFactory(GlmapperBeanFactory beanFactory){
        this.glmapperBeanFactory = beanFactory;
    }
}

клиентский класс

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext context = new
        ClassPathXmlApplicationContext("beans.xml");
        
        HelloFacade helloFacade = (HelloFacade)
        context.getBean("helloFacade");

        GlmapperBeanFactory glmapperBeanFactory = (GlmapperBeanFactory)
        context.getBean("glmapperBeanFactory");
        
        //这里其实可以不通过set方法注入到helloFacade中,
        //可以在helloFacade中通过autowired
        //注入;这里在使用main方法来执行验证,所以就手动set进入了
        helloFacade.setGlmapperBeanFactory(glmapperBeanFactory);

        //这个只需要传入不同HelloService的实现类的beanName,
        //就可以执行不同的业务逻辑
        helloFacade.sayHello("glmapperHelloService");
        helloFacade.sayHello("leishuHelloService");

    }
}

Вы можете видеть, что в классе вызывающего абонента (клиента) вам нужно только передатьbeanNameМожно переключаться между разными классами реализации, а не судить по связке if-else. Кроме того, некоторые друзья могут сказать, как программа узнает, какую из них использовать?beanNameШерстяная ткань? На самом деле, это тоже очень просто, мы можем получить этот параметр путем сплайсинга некоторыми способами, например, используяprefixуказать язык,prefix+HelloServiceопределить единственныйbeanName.

резюме

Я изначально хотел написать все точки расширения в статье, но получилось слишком длинно. Впереди еще около двух. Все в этой серииdemoдопустимыйgithubПолучите это, и вы также можете обратиться за точками расширения, которые вы можете придумать.