вопрос
описание проблемы: настроить мониторинг событий в проекте и выполнить некоторую работу по инициализации после загрузки контейнера. После запуска проекта было обнаружено, что работы по инициализации повторялись дважды. Для облегчения анализа бизнес-логика в коде убрана, а осталась только сцена.
Настроить прослушиватель
@Component
public class FreshListener implements ApplicationListener<ContextRefreshedEvent>{
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//业务代码
logger.error("将有权限人员放入缓存。。。。");
}
}
Настройте прослушиватель FreshListener для прослушивания при загрузке контейнера и добавьте список администраторов в кеш. Только чтобы обнаружить, что список загружается дважды.WHY???
Исследуйте проблему с точки зрения исходного кода
Поскольку каждый метод в исходном коде длинный, он публикуется толькофокуси ссвязанные с темойкод. Рекомендуется вместе посмотреть локальный исходный код.
Для того, чтобы прояснить этот вопрос, нам нужно иметь две точки знаний
-
механизм событий jdk
-
Механизм весеннего события
механизм событий jdk
Класс сущности пользователя
public class User {
private String username;
private String password;
private String sms;
public User(String username, String password, String sms) {
this.username = username;
this.password = password;
this.sms = sms;
}
}
пользовательский слушатель
public interface UserListener extends EventListener {
void onRegister(UserEvent event);
}
Отправить SMS прослушиватель
public class SendSmsListener implements UserListener {
@Override
public void onRegister(UserEvent event) {
if (event instanceof SendSmsEvent) {
Object source = event.getSource();
User user = (User) source;
System.out.println("send sms to " + user.getUsername());
}
}
}
Пользовательское событие
public class UserEvent extends EventObject {
public UserEvent(Object source){
super(source);
}
}
Отправить SMS событие
public class SendSmsEvent extends UserEvent {
public SendSmsEvent(Object source) {
super(source);
}
}
Класс службы, используемый для хранения прослушивателей событий, аналогичный контейнерам.
public class UserService {
private List<UserListener> listenerList = new ArrayList<>();
//当用户注册的时候,触发发送短信事件
public void register(User user){
System.out.println("name= " + user.getUsername() + " ,password= " +
user.getPassword() + " ,注册成功");
publishEvent(new SendSmsEvent(user));
}
public void publishEvent(UserEvent event){
for(UserListener listener : listenerList){
listener.onRegister(event);
}
}
public void addListeners(UserListener listener){
this.listenerList.add(listener);
}
}
тестовый класс
public class EventApp {
public static void main(String[] args) {
UserService service = new UserService();
service.addListeners(new SendSmsListener());
//添加其他监听器 ...
User user = new User("foo", "123456", "注册成功啦!!");
service.register(user);
}
}
результат операции
Запустите проект, смоделируйте регистрацию пользователя и инициируйте событие отправки SMS. Из приведенного выше простого смоделированного кода события можно вывести три существительных:Событие (Сендсмсевент),Прослушиватель (SendSmsListener),Источник событий (регистрация пользователя). Вышеописанный процесс можно описать так: регистрация пользователя ==> инициировать отправку события SMS ==> прослушиватель SMS прослушивает сообщение.
Приведенный выше код имеет два важных интерфейса:
интерфейс слушателя событий
/**
* A tagging interface that all event listener interfaces must extend.
* @since JDK1.1
*/
public interface EventListener {
}
Интерфейс — это интерфейс идентификации
интерфейс событий
/**
* <p>
* The root class from which all event state objects shall be derived.
* <p>
* All Events are constructed with a reference to the object, the "source",
* that is logically deemed to be the object upon which the Event in question
* initially occurred upon.
* @since JDK1.1
*/
public class EventObject implements java.io.Serializable {
private static final long serialVersionUID = 5516075349620653480L;
/**
* The object on which the Event initially occurred.
*/
protected transient Object source;
/**
* Constructs a prototypical Event.
* @param source The object on which the Event initially occurred.
* @exception IllegalArgumentException if source is null.
*/
public EventObject(Object source) {
if (source == null)
throw new IllegalArgumentException("null source");
this.source = source;
}
/**
* The object on which the Event initially occurred.
* @return The object on which the Event initially occurred.
*/
public Object getSource() {
return source;
}
/**
* Returns a String representation of this EventObject.
* @return A a String representation of this EventObject.
*/
public String toString() {
return getClass().getName() + "[source=" + source + "]";
}
}
В этом интерфейсе есть только исходный параметр, никакого особого значения, аналогично хранению источника данных.
Механизм весеннего события
По сравнению с демонстрацией события jdk выше, давайте проанализируем исходный код spring.
Ранее мы проанализировали, как загружаются бины в Spring, и проанализировали запись запуска проекта, не повторяя их, как известное условие.
Введите метод refresh() класса AbstractApplicationContext.
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
}
}
В этом методе есть три предложения, связанные с событиями Spring, После анализа этих трех предложений механизм событий Spring также ясен. Проанализируйте один за другим:
- initApplicationEventMulticaster(): инициализирует средство вещания событий.
- registerListeners(): регистрация прослушивателя, аналогичная приведенной выше.EventAppизservice.addListeners(new SendSmsListener()), которые будут выделены ниже.
- FinishRefresh (): Публикация событий, аналогичных приведенным выше.UserServiceсерединаpublishEvent(new SendSmsEvent(user)), остановимся на этом позже.
Введите метод registerListeners()
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
Получить реализации от всех bean-компонентов в контейнереApplicationListenerКласс интерфейса. Другими словами, если мы хотим использоватьМеханизм весеннего событияЧтобы обслуживать наш проект, слушатель, который мы пишем, должен реализовать интерфейс ApplicationListener.
Войдите в интерфейс ApplicationListener:
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
ApplicationListenerИнтерфейс наследуется отмеханизм событий jdkВ EventListener видно, чтоМеханизм весеннего событиябыть адаптированы измеханизм событий jdk. Spring добавлен в интерфейс слушателяonApplicationEvent()метод, который удобен для выполнения задач при срабатывании события, аналогично утреннемуUserListenerсерединаonRegister()метод.
назадregisterListeners()метод, после получения класса слушателя он сохраняется вТранслятор событий (приложениеEventMulticaster), что удобно для последующего использования.
Введите метод FinishRefresh().
publishEvent(new ContextRefreshedEvent(this));
Это предложение похоже на предложение в UserService.publishEvent(new SendSmsEvent(user)),а такжеContextRefreshedEventпохоже на вышеОтправить SMS событие.ContextRefreshedEventПредставленное событие — это завершение инициализации контейнера. Если инициализация контейнера завершена, то соответствующийпрослушиватель событийбудет запущен. Продолжайте продвигаться слой за слоем и приходите к:
publishEvent(Object event, ResolvableType eventType)
Следуйте, чтобы увидеть код ключа:
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
Введите многоадресное событие:
getApplicationListeners(event, type)
Это предложение означает получение слушателя в соответствии с типом события. Потому что мы можем многое настроить в проектеслушатель, у каждого слушателя будет свой соответствующийтип событияТолькособытиеслучилось,слушательбудет запущен.
Продолжайте смотреть на код в multicastEvent:
invokeListener(listener, event);
Введите команду вызова прослушивателя:
doInvokeListener(listener, event);
Введите doInvokeListener:
listener.onApplicationEvent(event);
пилаonApplicationEventВыполняется здесь, аналогично UserServicelistener.onRegister(event).
На данный момент анализ механизма события завершен.
Вернемся снова к publishEvent(Object event, ResolvableType eventType), там есть такой кусок кода:
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
Определите, есть ли у контейнера родительский контейнер, если есть входящий контейнер, снова активируйте прослушиватель событий в родительском контейнере.
Ответьте, почему прослушиватель событий выполняется дважды?
Из приведенного выше анализа исходного кода мы знаем, чтоСлушатель событий ContextRefreshedEventвrefresh()Метод, который срабатывает, точнее,refresh()в методеfinishRefresh()запущенныйСлушатель событий ContextRefreshedEvent. А мы в предыдущей статье приходим к выводу:Дочерние контейнеры могут получать родительские компоненты-контейнеры, но не наоборот.вот потому чтоВесенний контейнерИнициализировать выполнениеrefresh()метод, при срабатыванииСлушатель событий ContextRefreshedEvent,а такжеКонтейнер SpringMvcТакже выполняется во время инициализацииrefresh()метод, когда код выполняется для
publishEvent(Object event, ResolvableType eventType);
Есть кусок кода, чтобы определить, существует ли онродительский контейнер. Если он существует, он будетродительский контейнерСлушатель выполняет его один раз. Так запустите сноваСлушатель событий ContextRefreshedEvent. Так что интуитивно он инициализируется дважды.
решение:
- Строго контролируйте, выполняет ли слушатель только родительский контейнер или дочерний контейнер. Пример:
@Component
public class EvolvedFreshListener implements ApplicationListener<ContextRefreshedEvent>{
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext().getParent() == null){
logger.error("进化版====将有权限人员放入缓存。。。。");
}
}
}
- Поместите bean-компонент в подконтейнер, например, настройте его в контейнере SpringMvc, реализуйте его самостоятельно.
- Блокировка при выполнении метода слушателя, например (предоставляется партнером):
/**
* 实现此类, 可以在Spring容器完全初始化完毕时获取到Spring容器
*/
public abstract class ContextRefreshListener implements
ApplicationListener<ContextRefreshedEvent> {
private volatile boolean initialized = false;
@Override
public synchronized void onApplicationEvent(ContextRefreshedEvent event) {
if (!initialized) {
System.out.println("加锁====将有权限人员放入缓存。。。。");
initialized = true
}
}
}
адрес блога:www.liangsonghua.me
Обратите внимание на общедоступную учетную запись WeChat: отчет о консервированных яйцах Songhua Preserved Egg на доске, становитесь более захватывающим!
Введение в общедоступную учетную запись: делитесь техническими знаниями о работе на JD.com, а также технологиями JAVA и лучшими отраслевыми практиками, большинство из которых являются прагматичными, понятными и воспроизводимыми.