Back to Javatutorial

Spring探秘之Spring事件机制

docs/Spring全家桶/Spring源码分析/Spring重要机制探秘/Spring探秘之Spring事件机制.md

1.0.022.7 KB
Original Source

1. demo ׼

ʽ¼ǰ׼һ demo

  1. ׼һ¼ MyApplicationEvent
public class MyApplicationEvent extends ApplicationEvent {

    private static final long serialVersionUID = -1L;

    public MyApplicationEvent(Object source) {
        super(source);
    }
}

  1. ׼һ MyApplicationEventListener¼ MyApplicationEvent м
@Component
public class MyApplicationEventListener 
        implements ApplicationListener<MyApplicationEvent> {

    @Override
    public void onApplicationEvent(MyApplicationEvent event) {
        System.out.println(Thread.currentThread().getName() + " | " + event.getSource());
    }
}

׼һ࣬Ȳָݣ

@Configuration
@ComponentScan
public class Demo08Config {

}

@ComponentScan
public class Demo08Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context 
            = new AnnotationConfigApplicationContext(Demo08Config.class);
        // ¼
        context.publishEvent(
            new MyApplicationEvent(Thread.currentThread().getName() + " | Զ¼ ..."));
    }

}

ϴ붨һ¼ MyApplicationEventȻһ MyApplicationEventListener MyApplicationEvent ¼Ȼ main() У context.publishEvent(...) ¼

У£

main | main | Զ¼ ...

Կ¼ɹˡ

п֪¼ķ߳Ϊ main¼ļ߳Ҳ main

2. ¼

ʽǰ¼ص¼ص 4

  1. ¼
  2. ¼
  3. 㲥շ¼յ¼㲥
  4. ¼

һһ

2.1 ¼

spring ṩ¼Ϊ ApplicationEventһ̳࣬ jdk ṩ EventObject ࣬Զ¼ʱɼ̳ ApplicationEvent

public abstract class ApplicationEvent extends EventObject {

    private static final long serialVersionUID = 7099057708183571937L;

    /**  timestamp */
    private final long timestamp;

    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }

    public final long getTimestamp() {
        return this.timestamp;
    }
}

ApplicationEvent EventObjectǼ

/**
 * EventObject jdkṩλ java.util 
 */
public class EventObject implements java.io.Serializable {

    private static final long serialVersionUID = 5516075349620653480L;

    // ¼
    protected transient Object  source;

    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");

        this.source = source;
    }

    /**
     * ȡ¼
     */
    public Object getSource() {
        return source;
    }

    public String toString() {
        return getClass().getName() + "[source=" + source + "]";
    }
}

ApplicationEvent EventObject ApplicationEvent ṩԣ

  • source EventObject ԣ¼ݣ
  • timestamp: ʱ¼¼ʱ䡣

ʵϣspring ܷ ApplicationEvent ͵¼⣬Է Object ͵¼鿴ͿԷ һ㡣

2.2

spring ṩķΪ ApplicationEventPublisher£

public interface ApplicationEventPublisher {

    /**
     * ApplicationEvent͵¼
     */
    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }

    /**
     * Object͵¼
     */
    void publishEvent(Object event);

}

Ǹӿڣڶ

  • void publishEvent(ApplicationEvent event): ApplicationEvent ͵¼
  • void publishEvent(Object event): Object ͵¼

AbstractApplicationContext ApplicationEventPublisher ࣬ǿֱӵ AbstractApplicationContext#publishEvent ¼

AbstractApplicationContext ʵ֣Ǻʱپ

2.3 㲥

㲥ǽշ¼Ȼ¼㲥£

public interface ApplicationEventMulticaster {

    /**
     * Ӽ
     */
    void addApplicationListener(ApplicationListener<?> listener);

    /**
     * Ӽ beanName
     */
    void addApplicationListenerBean(String listenerBeanName);

    /**
     * Ƴ
     */
    void removeApplicationListener(ApplicationListener<?> listener);

    /**
     * Ƴ beanName
     */
    void removeApplicationListenerBean(String listenerBeanName);

    /**
     * Ƴеļ
     */
    void removeAllListeners();

    /**
     * 㲥¼
     */
    void multicastEvent(ApplicationEvent event);

    /**
     * 㲥¼
     */
    void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}

Ӵ㲥Ҫ

  1. άԶԼɾ
  2. 㲥¼

spring ĬϵĹ㲥Ϊ SimpleApplicationEventMulticasterǺٷ

2.4

¼ȻһЩ£

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * ¼
     */
    void onApplicationEvent(E event);

}

ڴ¼ʱҪʵ ApplicationListenerȻ onApplicationEvent(...) бдǵ¼߼

3. عˣ㲥ijʼ``ע

ع¼㲥ijʼ``ע̡

ڴ spring AbstractApplicationContext#refresh У¼㲥ijʼ``עֱڵ 8 10

ش£

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {

    /**
     * ʼ㲥
     * ¼㲥ʹõģʹĬϵ¼㲥
     */
    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);
        }
        else {
            // ûûù㲥ʹĬϵ¼㲥
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, 
                    this.applicationEventMulticaster);
        }
    }

    /**
     * ע
     */
    protected void registerListeners() {
        // 1\. Ƚֶӵļŵ㲥
        // AbstractApplicationContext#addApplicationListener
        for (ApplicationListener<?> listener : getApplicationListeners()) {
            getApplicationEventMulticaster().addApplicationListener(listener);
        }

        // 2\. beanFactoryлȡȡƣӵ㲥
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
        for (String listenerBeanName : listenerBeanNames) {
            getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        }

        // 3\. Ӧ¼
        Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
        //  earlyApplicationEvents Ϊ nullٷ¼
        this.earlyApplicationEvents = null;
        if (earlyEventsToProcess != null) {
            for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
                getApplicationEventMulticaster().multicastEvent(earlyEvent);
            }
        }
    }

    ...

}

㲥ijʼ߼ܼ򵥣¼㲥ʹõģʹĬϵ¼㲥Ĭϵ¼㲥 SimpleApplicationEventMulticaster

ע£

  1. Ƚֶӵļŵ㲥УǿԵ AbstractApplicationContext#addApplicationListener Ӽ
  2. beanFactory лȡȡƣӵ㲥Уע⣺ʱ beanFactory е bean ûгʼֻ beanName;
  3. ¼ͷ¼

ع̺ǴҪ㣺

  1. 㲥ʼ
  2. עᵽ㲥е

4. ¼

ǰڵ̵棬Ƕ¼Ѿ˸ŵĸҲ¼㲥ijʼע̣ Ǿʽ¼ķˡ

demo Уǵ context.publishEvent(...) ¼Ǹ

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {

    @Override
    public void publishEvent(ApplicationEvent event) {
        publishEvent(event, null);
    }

    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");
        // ¼
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
            }
        }

        // earlyApplicationEvents Ϊ nullapplicationEventӵ earlyApplicationEvents
        // ע󣬻 earlyApplicationEvents Ϊ null
        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        }
        else {
            // Ƿ¼IJ
            getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        }

        // ڸһ
        if (this.parent != null) {
            if (this.parent instanceof AbstractApplicationContext) {
                ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
            }
            else {
                this.parent.publishEvent(event);
            }
        }
    }

}

ܼ򵥣ؼΪ

// Ƿ¼IJ
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

дȻȡ¼㲥Ȼ㲥¼

ǰᵽspring ṩĬϵ¼㲥 SimpleApplicationEventMulticasterǽ SimpleApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType) ¼Ĺ㲥̣

/**
 * 㲥¼
 */
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    // 1\. ȡ TaskExecutor
    Executor executor = getTaskExecutor();
    // 2\. getApplicationListeners(...) ȡܼ¼ļ
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        // 3\. һ invokeListener(...) 
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

Ϸ 3

  1. ȡִgetTaskExecutor()
  2. ȡ¼ļgetApplicationListeners(...)
  3. üļinvokeListener(...)

ϲ¼㲥̣ú÷¡

1. ȡִgetTaskExecutor()

taskExecutor SimpleApplicationEventMulticaster һԣgetTaskExecutor() taskExecutor getter

    private Executor taskExecutor;

    public void setTaskExecutor(@Nullable Executor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    @Nullable
    protected Executor getTaskExecutor() {
        return this.taskExecutor;
    }

spring Ϊṩ͵ taskExecutor

  1. SyncTaskExecutorͬ taskExecutor execute(...) Ϊ

     @Override
     public void execute(Runnable task) {
         Assert.notNull(task, "Runnable must not be null");
         task.run();
     }
    
    

    ԿȷʵǸֱͬӵ Runnable#run ûµ߳

  2. SimpleAsyncTaskExecutortaskExecutor execute(...) Ϊ

    @Override
     public void execute(Runnable task, long startTimeout) {
         Assert.notNull(task, "Runnable must not be null");
         Runnable taskToUse = (this.taskDecorator != null 
                 ? this.taskDecorator.decorate(task) : task);
         // doExecute(...) ɻķ
         if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
             this.concurrencyThrottle.beforeAccess();
             doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
         }
         else {
             doExecute(taskToUse);
         }
     }
    
     /**
      * ɻķ
      * Ӵᴴ´ִ񣬵ûʹ̳߳
      */
     protected void doExecute(Runnable task) {
         // Կﴴ̣߳߳
         Thread thread = (this.threadFactory != null 
                 ? this.threadFactory.newThread(task) : createThread(task));
         thread.start();
     }
    
    

    Կ SimpleAsyncTaskExecutor Уᴴµִ߳

ܻܲȡִأͨԷִ֣ĻȡΪ null

ִ invokeListener(...) ʱֱӵõģ

...
        else {
            invokeListener(listener, event);
        }
...

2. ȡ¼ļgetApplicationListeners(...)

׼˵ȡܼ¼ļΪ AbstractApplicationEventMulticaster#getApplicationListeners(ApplicationEvent, ResolvableType)

/**
 * 裺
 * 1\. ӻлȡܻȡֱӷ
 * 2\. ܴӻлȡ retrieveApplicationListeners(...) ȡ
 */
protected Collection<ApplicationListener<?>> getApplicationListeners(
        ApplicationEvent event, ResolvableType eventType) {
    Object source = event.getSource();
    Class<?> sourceType = (source != null ? source.getClass() : null);
    ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
    // 1\. ӻлȡ
    ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
    if (retriever != null) {
        return retriever.getApplicationListeners();
    }
    if (this.beanClassLoader == null ||
            (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
            (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
        synchronized (this.retrievalMutex) {
            retriever = this.retrieverCache.get(cacheKey);
            if (retriever != null) {
                return retriever.getApplicationListeners();
            }
            retriever = new ListenerRetriever(true);
            // 2\. ȡǹؼ
            Collection<ApplicationListener<?>> listeners =
                    retrieveApplicationListeners(eventType, sourceType, retriever);
            this.retrieverCache.put(cacheKey, retriever);
            return listeners;
        }
    }
    else {
        return retrieveApplicationListeners(eventType, sourceType, null);
    }
}

ųؼ

  1. ӻлȡܻȡֱӷ
  2. ܴӻлȡ retrieveApplicationListeners(...) ȡ

Ǽ retrieveApplicationListeners(...)

/**
 * ȡ
 *
 */
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
        ResolvableType eventType, @Nullable Class<?> sourceType, 
        @Nullable ListenerRetriever retriever) {
    List<ApplicationListener<?>> allListeners = new ArrayList<>();
    Set<ApplicationListener<?>> listeners;
    Set<String> listenerBeans;
    synchronized (this.retrievalMutex) {
        listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
        listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
    }
    //  listeners лȡܴǰ¼ lister
    for (ApplicationListener<?> listener : listeners) {
        // жϵǰlistenerǷִ֧event
        if (supportsEvent(listener, eventType, sourceType)) {
            if (retriever != null) {
                retriever.applicationListeners.add(listener);
            }
            allListeners.add(listener);
        }
    }
    //  listenerBeans лȡܴǰ¼ lister
    if (!listenerBeans.isEmpty()) {
        ConfigurableBeanFactory beanFactory = getBeanFactory();
        for (String listenerBeanName : listenerBeans) {
            try {
                // жϵǰ listenerBeanName Ƿܼ¼
                if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
                    // лȡӦbean¼ǰʼ
                    ApplicationListener<?> listener = beanFactory.getBean(
                            listenerBeanName, ApplicationListener.class);
                    if (!allListeners.contains(listener) 
                            && supportsEvent(listener, eventType, sourceType)) {
                        if (retriever != null) {
                            // עⵥǵ
                            if (beanFactory.isSingleton(listenerBeanName)) {
                                retriever.applicationListeners.add(listener);
                            }
                            else {
                                retriever.applicationListenerBeans.add(listenerBeanName);
                            }
                        }
                        allListeners.add(listener);
                    }
                }
                else {
                    Object listener = beanFactory.getSingleton(listenerBeanName);
                    if (retriever != null) {
                        retriever.applicationListeners.remove(listener);
                    }
                    allListeners.remove(listener);
                }
            }
            catch (NoSuchBeanDefinitionException ex) {
            }
        }
    }
    // 
    AnnotationAwareOrderComparator.sort(allListeners);
    if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
        retriever.applicationListeners.clear();
        retriever.applicationListeners.addAll(allListeners);
    }
    return allListeners;
}
...

}

AbstractApplicationContext#registerListeners Уעʱ ע

  1. עֶӵļһʵ
  2. лȡ beanNameע᣻

retrieveApplicationListeners() гֵ listeners listenerBeans Ǵ͵ļġ

ϷȻе㳤߼dz

  1. listenersһ supportsEvent(listener, eventType, sourceType) жϵǰ listener ܷ¼
  2. listenerBeansһ supportsEvent(beanFactory, listenerBeanName, eventType) жϵǰ listener ܷ¼

ĴȽϸӣͲһһзˣĴ˼·

  1. Ӵ listener listenerBeanName ȡ listener Classlistener ֻ listener.getClass() ɣlistenerBeanName ͨ beanFactory.getType(listenerBeanName) ȡ

  2. ȡ listener ¼ͣһģ

    public class MyApplicationEventListener 
            implements ApplicationListener<MyApplicationEvent> {
    
        @Override
        public void onApplicationEvent(MyApplicationEvent event) {
            ...
        }
    }
    
    

    ǿȡ MyApplicationEvent

    // Щ඼jdkṩ
    ParameterizedType parameterizedType = (ParameterizedType) 
            MyApplicationEventListener.class.getGenericInterfaces()[0];
        Class<?> type = (Class)parameterizedType.getActualTypeArguments()[0];
    
    

    Ȼspring ڴⲿ߼ʱرӣϾ MyApplicationEventListener ͬʱʵ˶ӿڣ MyApplicationEventListener ĸ - - -... ӿڲ ApplicationListenerЩҪǵ

  3. ȡܼ¼ˣжϵǰжϼǷܼ¼ˣspring ƥķΪ ResolvableType#isAssignableFrom(ResolvableType)עʵ Class.isAssignableFrom ĹܣͬʱԴϵķҲʵ Class.isAssignableFrom Ĺܡ

3. üinvokeListener(...)

ڵüˣ£

/**
 * ִм
 */
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    // յõ doInvokeListener(...)
    if (errorHandler != null) {
        try {
            doInvokeListener(listener, event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
        }
    }
    else {
        doInvokeListener(listener, event);
    }
}

/**
 * ִвյõ ApplicationListener#onApplicationEvent 
 */
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        listener.onApplicationEvent(event);
    }
    catch (ClassCastException ex) {
        String msg = ex.getMessage();
        if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
            // ʡ־ӡ
        }
        else {
            throw ex;
        }
    }
}

һܼ򵥣DZһȡļһ onApplicationEvent(...)

ҪעǣǰȡִķУᵽ getTaskExecutor() ĽΪ null invokeListener(...) ֱִеģûһִ߳УҪע⡣

5. ¼Ӧ

5.1 ¼

spring Уɻᷢ ContextRefreshed ¼

Ҫ¼Ҳʮּ򵥣Ӧ Listener £

@Component
public class ContextRefreshedListener 
        implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("");
    }

}

5.2 ¼

һʼΪ¼

AnnotationConfigApplicationContext context 
        = new AnnotationConfigApplicationContext();
context.register(Demo08Config.class);
// ¼ǰ
context.publishEvent(new MyApplicationEvent(
        Thread.currentThread().getName() + " | Զ¼ ..."));
context.refresh();

к󣬻ᱨ

ӴϢ˵㲥δʼ

ô㲥ʼأǰķ֪ refresh() ̵ĵ 8 ¼ķڵ 10 һ¼ֻڵ 9 ˣ

Կ¼Ϊ onRefresh() չ׼ģ

¼ķҲͼˣ

ApplicationContext context = 
        new AnnotationConfigApplicationContext(Demo08Config.class) {
    @Override
    public void onRefresh() {
        // ﷢¼
        publishEvent(new MyApplicationEvent(
                Thread.currentThread().getName() + " | Զ¼ ..."));
    }
};

5.3 첽㲥¼

ǰԴ¼ִͬһ߳неģ demo нҲܿ

ǰѾ㲥¼ʱȻȡ executor executor ڣ executor ִУֱִУ㲥е executor Ϊ null˺׽ۣҪʵ첽ִУҪڹ㲥 executor ԡ

ǰͬҲspring ڳʼ㲥ʱжǷڹ㲥beanName Ϊ applicationEventMulticasterʹ SimpleApplicationEventMulticaster ĬϵĹ㲥

һֻҪԶ beanName Ϊ applicationEventMulticaster bean У

@Configuration
@ComponentScan
public class Demo08Config {

    /**
     * Զ㲥
     * ע⣺ƱΪ applicationEventMulticaster
     */
    @Bean
    public ApplicationEventMulticaster applicationEventMulticaster() {
        SimpleApplicationEventMulticaster applicationEventMulticaster
                = new SimpleApplicationEventMulticaster();
        // SimpleAsyncTaskExecutor springṩ첽ִ
        applicationEventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return applicationEventMulticaster;
    }
}

SimpleApplicationEventMulticaster ʹ spring ṩ첽ִSimpleAsyncTaskExecutorУ£

SimpleAsyncTaskExecutor-2 | main | Զ¼ ...

Կ̲߳߳ͬһˡ

鵽⻹꣬ǰѾ SimpleAsyncTaskExecutor ִйִ̣ʱ᲻ϴ̣߳

   protected void doExecute(Runnable task) {
        // Կﴴ̣߳߳
        Thread thread = (this.threadFactory != null 
                ? this.threadFactory.newThread(task) : createThread(task));
        thread.start();
    }

100 ¼

for(int i = 0; i < 100; i++) {
    context.publishEvent(new MyApplicationEvent(
            Thread.currentThread().getName() + " | Զ¼ ..."));
}

н

SimpleAsyncTaskExecutor-2 | main | Զ¼ ...
SimpleAsyncTaskExecutor-3 | main | Զ¼ ...
SimpleAsyncTaskExecutor-4 | main | Զ¼ ...
...
SimpleAsyncTaskExecutor-99 | main | Զ¼ ...
SimpleAsyncTaskExecutor-100 | main | Զ¼ ...
SimpleAsyncTaskExecutor-101 | main | Զ¼ ...

Կÿһ¼ᴴһִ߳У̱߳˱ԴƵشһ˷ѣ߳أ̳߳ء

ǿ SimpleApplicationEventMulticaster#setTaskExecutor IJ java.util.concurrent.Executorͱüˣֱʹ jdk ṩ̳߳أ

@Bean
public ApplicationEventMulticaster applicationEventMulticaster() {
    SimpleApplicationEventMulticaster applicationEventMulticaster
            = new SimpleApplicationEventMulticaster();
    // ʹjdkṩ̳߳
    applicationEventMulticaster.setTaskExecutor(Executors.newFixedThreadPool(4));
    return applicationEventMulticaster;
}

ʹõ jdK ṩ newFixedThreadPool 4 ̣߳У£

pool-1-thread-2 | main | Զ¼ ...
pool-1-thread-3 | main | Զ¼ ...
pool-1-thread-4 | main | Զ¼ ...
pool-1-thread-4 | main | Զ¼ ...
pool-1-thread-1 | main | Զ¼ ...
pool-1-thread-3 | main | Զ¼ ...
pool-1-thread-3 | main | Զ¼ ...
pool-1-thread-2 | main | Զ¼ ...
pool-1-thread-1 | main | Զ¼ ...
pool-1-thread-3 | main | Զ¼ ...
...

Կʼֻ 4 ߳ڹ㲥¼

Щ淶Уǽֱֹʹ jdk ṩ̵߳ijأǸᳫԶ̳߳أ㣬ڱIJ̳߳ؼģ˾ͼ򵥵ʹ jdk ṩ߳ʾ¡

5.4 Զ¼

ʵϣǰṩʾ demo Զ¼Ͳظˡ

6. ܽ

ķ spring ¼ƣ¼Ĵ¼㲥ҴԴijʼ¼ķ̡


ԭӣhttps://my.oschina.net/funcy/blog/4713339 ߸ˮƽд֮ӭָԭףҵתϵ߻Ȩҵתע