I recently decided to compile some examples of design patterns used in my work and applied them. Each article will be developed in an “example, a model, a bit of principle” manner. Scenarios that use the pattern, an introduction to the pattern itself, and the source code behind the pattern will be presented.
1: An example
A business scenario is when a user signs up for an activity. After signing up for the event, do 3 things.
- User and activity relationship is entered into the database
- If the user is invited to register, he/she needs to inform the inviter that the invitation is successful and the invitation relationship is added to the database.
- The user receives the notification of successful registration
Considering that the business can be divided into three relatively independent parts, which is convenient for future expansion. The event publishing mechanism of Spring is used to decouple the business.
2: Process execution
Let’s see how the process works.
- Introduce an event publisher
@Slf4j
@Service
@AllArgsConstructor
public class ActivityService extends BaseService {
private final ApplicationEventPublisher publisher; }
Copy the code
- Release the events corresponding to the three services
// Invite event log.info("Invitation event, event id: {}, inviter user id: {}, accept a user id: {}, has invited toll: {}, also need to invite number: {}", activity.getId(), inviteUserId, userId, invitedCount, inviteToCount); publisher.publishEvent(ActivityWrapper.ActivityInviteEventForSend()); / / message event publisher. PublishEvent (ActivityWrapper ActivityNoticeEventForSend (); / / registration event publisher. PublishEvent (ActivityWrapper ActivityEnrollEventForSend ());Copy the code
- Event object encapsulation takes message events as an example
/** * package enrollment event ** @param activityenrollment dto dto * @param userId userId * @return event
*/
public static ActivityEnrollEvent ActivityEnrollEventForSend(ActivityEnrollDTO activityEnrollDTO) {
ActivityEnrollEvent activityEnrollEvent = CloneUtils.clone(activityEnrollDTO, ActivityEnrollEvent.class);
return activityEnrollEvent
.setUserId(userId)
.setPicbookCollectionId(picbookCollectionId)
.setPid(pid)
.setAppId(appId)
.setInviteUserId(inviteUserId);
}
Copy the code
- Construct the message listener corresponding to the event and process the business logic. Take message events as an example. After receiving the event, the message listener encapsulates the event and sends it to MQ.
@Slf4j
@Service
@AllArgsConstructor
public class ActivityNoticeListener {
private final IRocketMqService rocketMqService;
@Async
@EventListener
public void activityNoticeHandler(ActivityNoticeEvent activityNoticeEvent) {
List<ActivityNoticeEvent.ActivityNotice> activityNoticeList = activityNoticeEvent.getActivityNoticeList();
if(CollectionUtil isNotEmpty (activityNoticeList)) {/ / 1 message sent to MQfor (ActivityNoticeEvent.ActivityNotice activityNotice : activityNoticeList) {
log.info("Message event, activity ID :{}, target user :{}, message type :{},", activityNotice.getId(), activityNotice.getOpenId() , activityNotice.getMsgType()); / / 2.1 ActivityMsgTemplateTypeEnum get messages typestypeEnum = ActivityMsgTemplateTypeEnum.get(activityNotice.getMsgType()); String messageRequest = json.tojsonString (activityNotice); sendRocketMqMsg(messageRequest,typeEnum); }}}Copy the code
3: effect review
The business model was completed around April and went through many iterations. The obvious benefits are:
-
The message business code was tweaked several times during the iteration. This includes what message to send, what parameters to carry, and when to send it. In these iterations, you only need to modify the event objects encapsulated in the Wrapper to control the message objects at the upper level. Even after a major overhaul, the trigger logic of the message was fixed, and the process was clear, it was much easier to change.
-
Locating bugs is very convenient.
4: Implement a simple event mechanism
Spring’s event mechanism is simple to implement. It’s just 3 steps: Get an event publisher (spring comes with it), define an event, and register a listener (spring4.2 only requires annotations). The way it works is that the event publisher publishes the event, the listener receives the event, and the end.
- To define an event, you need to inherit ApplicationEvent and pass in the event source
@Data
@Accessors(chain = true)
public class ActivityEnrollEvent extends AbstractEvent {
public ActivityEnrollEvent(Object source) {
super(source); this.timestamp = System.currentTimeMillis(); }}Copy the code
- Define a listener and add an annotation @EventListener
@Component
public class DemoListener {
@EventListener
public void doSomething(Event event){
}
}
Copy the code
- Publish event
private final ApplicationEventPublisher publisher;
publisher.publishEvent(Event event)
Copy the code
The Demo link zhuanlan.zhihu.com/p/85067174 【 handling If there is any infringement immediately delete 】
5 source
Spring events are a very important mechanism and play an important role in the spring Application lifecycle. Here is the source code
- The publishing initialization spring events is ApplicationEventMulticaster issued by event publishing. The event publisher is initialized when the Spring Application Context is started. Is very familiar with the refresh initApplicationEventMulticaster can be found in the () ().
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();
}
Copy the code
This method is easy to click on. If the custom applicationEventMulticaster with custom, if there is no custom with spring the default. The container checks for injection of a custom event publisher by checking the core container beanFactory.
/**
* Initialize the ApplicationEventMulticaster.
* Uses SimpleApplicationEventMulticaster if none defined in the context.
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
*/
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); }}else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]"); }}}Copy the code
- RegisterListeners () can be found in the Refresh method of the previous step.
// Check for listener beans and register them.
registerListeners();
Copy the code
Click on the source code here is very interesting. The first is to inject static listeners. When we register our custom listener that implements the ApplicationListener interface, we pass only the beanName to the event listener multiCaster, not the instance of the bean itself. Do not initialize FactoryBeans here: We need to leave all regular beans uninitialized to let post-processors apply to them! . This means waiting for the bean post-processors to initialize the listener bean. Bean post-Processors are typically bean lifecycle enhancements at the container level. Here, browsing through some web material, I think the purpose is to identify the listener bean that implements the applicationEventLister interface and inject it into the container.
protected void registerListeners() {
// Register statically specified listeners first.
for(ApplicationListener<? > listener : getApplicationListeners()) { getApplicationEventMulticaster().addApplicationListener(listener); } // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized tolet post-processors apply to them!
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true.false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
// Publish early application events now that we finally have a multicaster...
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if(earlyEventsToProcess ! = null) {for(ApplicationEvent earlyEvent : earlyEventsToProcess) { getApplicationEventMulticaster().multicastEvent(earlyEvent); }}}Copy the code
The actual Listener instance loading is actually implemented by a class that implements the BeanPostProcessor interface. This class name is ApplicationListenerDetector. This class annotation should confirm the previous guess. See comments. detect beans which implement the ApplicationListener.
{@code BeanPostProcessor} that detects beans which implement the {@code ApplicationListener}
* interface. This catches beans that can't reliably be detected by {@code getBeanNamesForType} * and related operations which only work against top-level beans. class ApplicationListenerDetector implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor { }Copy the code
The code is also clear. if(instanceof ApplicationListener) –> context.addApplicationListener
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof ApplicationListener) {
// potentially not detected as a listener by getBeanNamesForType retrieval
Boolean flag = this.singletonNames.get(beanName);
if(Boolean.TRUE.equals(flag)) { // singleton bean (top-level or inner): register on the fly this.applicationContext.addApplicationListener((ApplicationListener<? >) bean); }else if (Boolean.FALSE.equals(flag)) {
if(logger.isWarnEnabled() && ! this.applicationContext.containsBean(beanName)) { // inner bean with other scope - can't reliably process events logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " + "but is not reachable for event multicasting by its containing ApplicationContext " + "because it does not have singleton scope. Only top-level listener beans are allowed " + "to be of non-singleton scope."); } this.singletonNames.remove(beanName); } } return bean; }Copy the code
- Release the events of release process is completed in ApplicationEventMultiCaster. First, a cache map.key is defined by the event type and sourceType sourceType. The listenerRetriever maintains a list of listeners.
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64); //key public ListenerCacheKey(ResolvableType eventType, @Nullable Class<? >sourceType) {
Assert.notNull(eventType, "Event type must not be null");
this.eventType = eventType;
this.sourceType = sourceType; } //value private class ListenerRetriever { public final Set<ApplicationListener<? >> applicationListeners = new LinkedHashSet<>(); }Copy the code
Then when the published event type and event source type match the key in the Map, Return the list of listeners in value as the match result. This usually happens when the event is not first published, avoiding going through all listeners and filtering. If the event is first published, all event listeners are iterated and matched according to the event type and event source type.
/**
* Actually retrieve the application listeners for the given event and source type.
* @param eventType the event type
* @param sourceType the event source type
* @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes)
* @return the pre-filtered list of application listeners for the given event and source type*/ private Collection<ApplicationListener<? >> retrieveApplicationListeners( ResolvableType eventType, Class<? >sourceType, ListenerRetriever retriever) {// This is a list of matching listeners. >> allListeners = new LinkedList<ApplicationListener<? > > (); Set<ApplicationListener<? >> listeners; Set<String> listenerBeans; synchronized (this.retrievalMutex) { listeners = new LinkedHashSet<ApplicationListener<? >>(this.defaultRetriever.applicationListeners); listenerBeans = new LinkedHashSet<String>(this.defaultRetriever.applicationListenerBeans); } // Walk through all listenersfor(ApplicationListener<? > Listeners: listeners) {// Determine whether the event listeners are a matchif (supportsEvent(listener, eventType, sourceType)) {
if(retriever ! = null) { retriever.applicationListeners.add(listener); } // Add matching listeners to the list allListeners. Add (listener); }} / / prioritize AnnotationAwareOrderComparator matching list of listeners. The sort (allListeners);return allListeners;
}
Copy the code
Finally, the matching listener is called to execute the logic
@override public void multicastEvent(Final ApplicationEvent event, ResolvableType eventType) {// Obtain the event type ResolvableTypetype= (eventType ! = null ? eventType : resolveDefaultEventType(event)); // Traverses all event listeners that match the eventfor(final ApplicationListener<? > listener : getApplicationListeners(event,type)) {// Get the task Executor in the event publisher. By default this method returns null Executor Executor = getTaskExecutor();if(executor ! = null) {// Async callback listening method executor.execute(newRunnable() {
@Override
public void run() { invokeListener(listener, event); }}); }else{// Synchronize callback listener method invokeListener(listener, event); }}}Copy the code
6 the end
Bring in some personal feelings. Go from zero to one in a row in recent months and go out on a project on your own. I deeply feel that to do a good job in business, technology is indispensable, but it also needs a lot of soft capabilities. It’s really hard. Efforts may not succeed, do not work hard must fail. Tread on thin ice.