【SpringBoot】分析 SpringBoot 中的扩展点

news/2024/11/17 4:47:42/文章来源:https://www.cnblogs.com/kukuxjx/p/18385800

1  前言

SpringBoot 它给我们留了很多的扩展点,这节我们就看看都有哪些(有的扩展点是基于 Spring 的,有的我就不具体强调到底是 SpringBoot 还是 Spring 的噢)。

另外每一种扩展点我们都从两个方面来看:

入口时机:入口就是 SpringBoot 解析或者寻找你自定义的类的时机

执行时机:就是 SpringBoot 执行的时机

2  ApplicationContextInitializer

位于spring-context包中的:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {/*** Initialize the given application context.* @param applicationContext the application to configure*/void initialize(C applicationContext);
}

2.1  入口时机

它的入口时机是在 SpringApplication 的实例化方法中:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {...// 加载所有的初始化器setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));...
}

原理:通过 SpringFactoriesLoader 加载文件 META-INF/spring.factories 中 ApplicationContextInitializer 类型

2.2  执行时机

执行时机是在 SpringApplication 的 run 方法中的准备上下文 prepareContext 里:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {...// 执行初始化
    applyInitializers(context);...
}
protected void applyInitializers(ConfigurableApplicationContext context) {// 遍历初始化器集合for (ApplicationContextInitializer initializer : getInitializers()) {// 检查是否可以被调用Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),ApplicationContextInitializer.class);Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");// 执行每个初始化器 可以对上下文进行操作
        initializer.initialize(context);}
}

当有多个的话,是放到 List 容器中的,所以先放进来的先执行。

3  ApplicationListener

位于spring-context包中的:

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {/*** Handle an application event.* @param event the event to respond to*/void onApplicationEvent(E event);
}

3.1  入口时机

入口时机我们这里看两种:

(1)定义在 META-INF/spring.factories

它的入口时机跟上边的一样的,也是在 SpringApplication 的实例化方法中:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {...// 加载所有的监听器setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));...
}

原理:通过 SpringFactoriesLoader 加载文件 META-INF/spring.factories 中 ApplicationListener 类型

(2)EventListener 注解

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EventListener {...}

可以看到这个注解是作用在方法上的,并且它的解析过程来源于,我这里就不一点点带大家看了,我画了个图:

首先是我们的注解类型的 ApplicationContext 在构造器中会注入注解相关的处理器,这里就会涉及到放置默认的 EventListenerFactory (它会给我们的方法封装一个 ApplicationListener 对象)、以及默认的 EventListenerMethodProcessor (它来解析我们的 Bean 中的 @EventListener 注解方法),两者搭配干活,最后通过 context 注入到监听器集合并且也会往广播器里放一份(广播器的初始化下边也会看一下)

3.2  执行时机

它的执行是通过 ApplicationContext 发布事件来做的,发布事件里会获取到上下文中的广播器 ApplicationEventMulticaster,然后广播器里获取监听器列表,然后顺序执行(执行的时候有个小细节是广播器里有没有配线程池,有的话就提交到线程池里异步执行)。

那我们在看执行时机之前,可能得先看下:

(1)广播器的初始化

(2)监听器的注册或者来源(上边注解形式的我们已经能看到注册的过程,我们就看一下 spring.factories 中的监听器的注册)

3.2.1  广播器的初始化

广播器的初始化位于 Spring 的刷新方法中,有一个 initApplicationEventMulticaster 即初始化广播器方法,我们看看该方法:

// public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
protected void initApplicationEventMulticaster() {// 获取 bean 工厂ConfigurableListableBeanFactory beanFactory = getBeanFactory();// 判断是否有用户自定义的广播器 applicationEventMulticasterif (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {// 有的话就用 用户自定义的广播器(比如当你需要异步执行的时候,可以自己定义一个 Bean 设置下线程池)this.applicationEventMulticaster =beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);if (logger.isTraceEnabled()) {logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");}}else {// 没有的话 就初始化一个默认的广播器 SimpleApplicationEventMulticaster 并交给 Bean 工厂管理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() + "]");}}
}

广播器的类图如下,捎带了解一下:

3.2.2  监听器的注册

上边注解形式的我们已经能看到注册的过程,我们就看一下 spring.factories 中的监听器是什么放到广播器或者 ApplicationContext 中的,这个就涉及到下边的一个扩展点,大家可以先看一下下边的扩展点 SpringApplicationRunListener,SpringBoot 在 spring.factories 放置了一个 EventPublishingRunListener,

在他的实例化方法中, 这家伙还给自己内部定义了一个广播器,并且把 spring.factories 中的 ApplicationListener 监听器都放进来:

// private final SimpleApplicationEventMulticaster initialMulticaster;
/*** @param application SpringApplication* @param args 启动参数*/
public EventPublishingRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;// 初始化一个默认的事件广播器this.initialMulticaster = new SimpleApplicationEventMulticaster();// 这里会把 SpringApplication 的监听器集合都放进广播器里for (ApplicationListener<?> listener : application.getListeners()) {this.initialMulticaster.addApplicationListener(listener);}
}

那么它是什么时候,把监听器放到 ApplicationContext 中的呢?就在它的 contextLoaded 阶段:

@Override
public void contextLoaded(ConfigurableApplicationContext context) {for (ApplicationListener<?> listener : this.application.getListeners()) {if (listener instanceof ApplicationContextAware) {((ApplicationContextAware) listener).setApplicationContext(context);}// 放到 ApplicationContext 中
        context.addApplicationListener(listener);}// 通过默认的广播器来广播事件this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

有没有有点小晕,EventPublishingRunListener 它也有广播器了,那跟 Spring 的广播器会不会多执行呢?其实不会,我们直接细看下:

看完上边两个,我们最后看下事件的发布过程,一般我们通过 ApplicationContext 进行发布,我这里直接画了个图,一起看下:

事件发布后,首先会统一把事件对象包装成 ApplicationEvent 类型,然后会判断 earlyApplicationEvents 属性是否为空,他的值的变化有两个触发点:

(1)在 Spring 刷新的时候,准备上下文的方法中,会首先对 earlyApplicationEvents 属性进行初始化,来保存一些提前发布的事件

(2)在刷新的后半段,会注册监听器,并注册到广播器里,然后把 earlyApplicationEvents 里边暂存的事件捞出来并把 earlyApplicationEvents 设置为空,然后交给广播器发布

最后到达广播器,里边有一个小细节就是线程池的设置(默认是没有的)所以默认都是同步执行的。

对于执行顺序,还是当监听器有多个的话,是放到 List 容器中的,所以先放进来的先执行。

3.3  事件 Event

那我们顺便看下 SpringBoot 中的一些常见事件:

首先是 EventPublishingRunListener 里边的 6个阶段 + 1个失败阶段涉及到的7个事件:

(1)ApplicationStartingEvent SpringBoot 开始启动阶段(run方法的最前段)

(2)ApplicationEnvironmentPreparedEvent SpringBoot 准备环境阶段

(3)ApplicationContextInitializedEvent SpringBoot 上下文初始化阶段(这个时候ApplicationContext刚实例化好,进行初始化阶段)

(4)ApplicationPreparedEvent SpringBoot 上下文就绪阶段(初始化好了)

(5)ApplicationStartedEvent SpringBoot 这时候上下文都刷新完了(就差 callRunners 没执行即 CommandLineRunner、ApplicationRunner没执行)

(6)ApplicationReadyEvent SpringBoot 服务启动完了开始跑了

(7)ApplicationFailedEvent 失败处理阶段

捎带再看一个:

WebServerInitializedEvent web容器初始化完毕事件 都是在 spring 刷新上下文的最后 finishRefresh 里发布的

它的两个子类:

ServletWebServerApplicationContext servlet容器型事件

ReactiveWebServerInitializedEvent 响应式容器事件

4  SpringApplicationRunListener

位于 SpringBoot 包里的:

public interface SpringApplicationRunListener {...6个阶段
}

4.1  入口时机

它的入口时机是在 SpringApplication 的 run 方法中的前一段:

public ConfigurableApplicationContext run(String... args) {...SpringApplicationRunListeners listeners = getRunListeners(args);// 监听器的执行 starting
    listeners.starting();...
}
private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };// 也是通过该方法 getSpringFactoriesInstances 指定 SpringApplicationRunListener 类型来加载的return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

SpringApplicationRunListeners 的内部的 listeners 就存放了所有的 SpringApplicationRunListener

原理:通过 SpringFactoriesLoader 加载文件 META-INF/spring.factories 中 SpringApplicationRunListener 类型

4.2  执行时机

执行时机的话,SpringApplicationRunListener 总共分6个阶段,外加一个失败的处理阶段(只有 starting、running 阶段不会执行失败处理,其他5个都会执行失败处理),并且失败处理不分,

比如有三个监听器 A、B、C,假如A、B执行成功,C执行失败的话,执行 failed 处理,A、B、C 三个都会执行,并不是只有 C 执行。

它的执行阶段几个时机,大家直接看图,清晰明了:

当有多个的话,是放到 List 容器中的,所以先放进来的先执行。

5  SpringBootExceptionReporter

位于 SpringBoot 包里的:

public interface SpringBootExceptionReporter {/*** Report a startup failure to the user.* @param failure the source failure* @return {@code true} if the failure was reported or {@code false} if default* reporting should occur.*/boolean reportException(Throwable failure);
}

因为它的入口和执行衔接挺快,我们就一起看:

5.1  入口以及执行时机

public ConfigurableApplicationContext run(String... args) {...Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();try {// 创建上下文context = createApplicationContext();// 入口时机:加载异常解析报告类exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);...}catch (Throwable ex) {// 执行时机1:失败处理 exceptionReporters
        handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {listeners.running(context);}catch (Throwable ex) {// 执行时机2:失败处理 exceptionReportershandleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}return context;
}

再看看失败处理:

private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {try {try {...}finally {reportFailure(exceptionReporters, exception);...}}catch (Exception ex) {logger.warn("Unable to close ApplicationContext", ex);}...
}
private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) {try {// 遍历执行 中间有一个抛错或者 reportException 返回 false 后边就不执行了for (SpringBootExceptionReporter reporter : exceptionReporters) {if (reporter.reportException(failure)) {registerLoggedException(failure);return;}}}...
}

当有多个的话,是放到 List 容器中的,所以先放进来的先执行。

6  PostProcessor

在 Spring 中,存在多种类型的 PostProcessor,它们主要用于在 Spring 容器初始化bean的过程中提供扩展点。这块可是一个大块,硬骨头,启动过程中很多都是依赖后置处理器来完成的。

6.1  BeanFactoryPostProcessor

它主要是在 BeanFactory 加载 Bean 定义之后、实例化 Bean 之前对 Bean 的定义进行自定义修改和扩展,从而影响容器中的Bean配置。我本来是想把 BeanDefinitionRegistryPostProcessor 放第一个,但是它是继承的 BeanFactoryPostProcessor,所以就拿来放第一个。

@FunctionalInterface
public interface BeanFactoryPostProcessor {void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;}

6.1.1  入口时机

入口都是通过 AbstractApplicationContext 的 addBeanFactoryPostProcessor 方法添加进来的:

@Override
public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor) {Assert.notNull(postProcessor, "BeanFactoryPostProcessor must not be null");this.beanFactoryPostProcessors.add(postProcessor);
}

比如 ConfigurationWarningsApplicationContextInitializer 它是实现了 ApplicationContextInitializer 初始化器,继而拿到上下文对象放进来的。

public class ConfigurationWarningsApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {...public void initialize(ConfigurableApplicationContext context) {context.addBeanFactoryPostProcessor(new ConfigurationWarningsApplicationContextInitializer.ConfigurationWarningsPostProcessor(this.getChecks()));}...
}

6.1.2  执行时机

执行时机是在 AbstractApplicationContext 刷新方法里调用 invokeBeanFactoryPostProcessors(beanFactory);

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());...
}
// 获取 BeanFactoryPostProcessors 其实就是直接返回 AbstractApplicationContext 中的 beanFactoryPostProcessors 集合
public List<BeanFactoryPostProcessor> getBeanFactoryPostProcessors() {return this.beanFactoryPostProcessors;
}

其中执行的这个方法里还夹杂着 BeanDefinitionRegistryPostProcessor 的入口以及执行时机,我们下边看。

6.2  BeanDefinitionRegistryPostProcessor

它主要是在容器的初始化过程中对 Bean 定义进行自定义处理,比如‌在Bean定义注册之前和之后进行操作、允许在运行时注册新的beans或修改现有的bean定义等。

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {...void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

可以看到它继承了 BeanFactoryPostProcessor。

6.2.1  入口以及执行时机

它的入口和执行是在一块的,并且跟上边的 BeanFactoryPostProcessor 执行时机都在一个里边,我们上边看到它最后是在 PostProcessorRegistrationDelegate 的 invokeBeanFactoryPostProcessors 方法中执行的,一起看一下:

final class PostProcessorRegistrationDelegate {// 执行 BeanFactoryPostProcessor 处理器public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {Set<String> processedBeans = new HashSet<>();// 看这里会涉及到 BeanDefinitionRegistryPostProcessor 的处理// 默认的 BeanFactory 是 DefaultListableBeanFactory 它是实现了 BeanDefinitionRegistry 所以基本都会走这里if (beanFactory instanceof BeanDefinitionRegistry) {BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();// 遍历每个 BeanFactoryPostProcessorfor (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {// 如果它还是 BeanDefinitionRegistryPostProcessor 的子类if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {BeanDefinitionRegistryPostProcessor registryProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor;// 则顺便执行 BeanDefinitionRegistryPostProcessor 的方法
                    registryProcessor.postProcessBeanDefinitionRegistry(registry);// 并暂时先放到集合中
                    registryProcessors.add(registryProcessor);}else {regularPostProcessors.add(postProcessor);}}List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();// 看这里就会从 Bean 工厂里获取 BeanDefinitionRegistryPostProcessor 类型的处理器String[] postProcessorNames =beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);for (String ppName : postProcessorNames) {if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {// 添加到集合中currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}}sortPostProcessors(currentRegistryProcessors, beanFactory);registryProcessors.addAll(currentRegistryProcessors);// 执行 BeanDefinitionRegistryPostProcessor 处理器
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());currentRegistryProcessors.clear();...}// 等等下边也还有很多分类执行的 就不一一看了
        ...}
}

至于人家为什么融合到一块,可能大概还是是其子类的关系吧。

6.3  BeanPostProcessor

它主要是在 Bean 初始化前后进行拦截操作,可以用来修改 Bean 的定义或实例,比如进行 AOP 代理的创建等。

public interface BeanPostProcessor {@Nullabledefault Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Nullabledefault Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}}

6.3.1  入口时机

它的入口是在 AbstractApplicationContext 刷新方法里调用 registerBeanPostProcessors(beanFactory);

protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {// 委托 PostProcessorRegistrationDelegate 注册 Bean 后置处理器PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}
public static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {...// 获取 BeanPostProcessor 实例中的所有名称String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);// 分类存放// 实现了PriorityOrdered接口List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();// 实现了MergedBeanDefinitionPostProcessor接口List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();// 实现了Ordered接口List<String> orderedPostProcessorNames = new ArrayList<>();// 没有实现排序接口List<String> nonOrderedPostProcessorNames = new ArrayList<>();// 进行分类处理for (String ppName : postProcessorNames) {if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);priorityOrderedPostProcessors.add(pp);if (pp instanceof MergedBeanDefinitionPostProcessor) {internalPostProcessors.add(pp);}}else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {orderedPostProcessorNames.add(ppName);}else {nonOrderedPostProcessorNames.add(ppName);}}// First, register the BeanPostProcessors that implement PriorityOrdered.// 排序
    sortPostProcessors(priorityOrderedPostProcessors, beanFactory);// 添加到Bean工厂
    registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);...
}
// BeanFactory 注册 BeanPostProcessor
private static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanPostProcessor> postProcessors) {if (beanFactory instanceof AbstractBeanFactory) {((AbstractBeanFactory) beanFactory).addBeanPostProcessors(postProcessors);}else {for (BeanPostProcessor postProcessor : postProcessors) {beanFactory.addBeanPostProcessor(postProcessor);}}
}
// 最后就到这里先删再加也就是后来的就排在最后 是放到 list 集合中
/** BeanPostProcessors to apply. */
// private final List<BeanPostProcessor> beanPostProcessors = new BeanPostProcessorCacheAwareList();
@Override
public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) {Assert.notNull(beanPostProcessor, "BeanPostProcessor must not be null");// Remove from old position, if anythis.beanPostProcessors.remove(beanPostProcessor);// Add to end of listthis.beanPostProcessors.add(beanPostProcessor);
}

6.3.2  执行时机

我们先看下该接口有两个方法,分别是在:

(1)Bean 初始化之前:

在 Bean 初始化之前,BeanPostProcessor 的 postProcessBeforeInitialization 方法会被调用。

这个方法允许你在 Bean 初始化之前对 Bean 实例进行修改,例如添加或修改 Bean 的属性。

(2)Bean 初始化之后:

在 Bean 初始化之后,BeanPostProcessor 的 postProcessAfterInitialization 方法会被调用。

这个方法允许你在 Bean 初始化之后对 Bean 实例进行修改,例如创建代理对象。

具体的执行流程:

(1)Bean 实例化:

当 Spring 容器创建一个新的 Bean 实例时,首先会调用构造函数或工厂方法来创建 Bean。在这个阶段,BeanPostProcessor 不会被调用。

(2)依赖注入:

接下来,Spring 容器会对新创建的 Bean 进行依赖注入。在这个阶段,BeanPostProcessor 也不会被调用。

(3)postProcessBeforeInitialization 调用:

当 Bean 的依赖注入完成后,但还没有调用 Bean 的初始化方法(如果有定义的话)之前,Spring 会调用所有已注册的 BeanPostProcessor 的 postProcessBeforeInitialization 方法。这个方法允许你修改 Bean 的实例,例如修改其属性值。

(4)Bean 初始化:

如果 Bean 定义了初始化方法(例如通过 init-method 属性或实现了 InitializingBean 接口 afterPropertiesSet方法),那么 Spring 会调用相应的初始化方法。在这个阶段,BeanPostProcessor 不会被调用。

(5)postProcessAfterInitialization 调用:

当 Bean 完成初始化后,Spring 会调用所有已注册的 BeanPostProcessor 的 postProcessAfterInitialization 方法。这个方法允许你再次修改 Bean 的实例,例如创建代理对象。

这个的执行我们得回顾下 Bean 的生命周期过程,之前我在网上看到一个比较全的图,我们看一下:

可以看到初始化前后的扩展点就是执行 BeanPostProcessor,并且有一个特殊的 InstantiationAwareBeanPostProcessor 它是在实例化的前后执行的(这个我有一篇之前是在想给每个 Feign 设置统一的 url 是用到过这个处理器来实现的,可以看看),也就是创建 Bean 对象实例的时候。

6.4  常见的 PostProcessor

6.4.1  MergedBeanDefinitionPostProcessor

首先它继承了 BeanPostProcessor 那么它可以在 Bean 初始化的前后执行扩展点,再看看它内部:

public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {// 看名称合并 BeanDefinition 可以理解为解析类的其他信息合并到 Bean 定义信息里void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);default void resetBeanDefinition(String beanName) {}
}

它有几个实现类,会涉及到下边的一些扩展点的实现,我们提前透漏一下:

CommonAnnotationBeanPostProcessor:解析 @Resource

InitDestroyAnnotationBeanPostProcessor:解析 initAnnotationType(@PostConstruct) destroyAnnotationType(@PreDestroy) 这两个属性值的指定来源于 CommonAnnotationBeanPostProcessor

CommonAnnotationBeanPostProcessor 继承了 InitDestroyAnnotationBeanPostProcessor:

public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessorimplements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {public CommonAnnotationBeanPostProcessor() {...setInitAnnotationType(PostConstruct.class);setDestroyAnnotationType(PreDestroy.class);...}
}

AutowiredAnnotationBeanPostProcessor:解析 @Autowired @Value @Lookup

JmsListenerAnnotationBeanPostProcessor:解析 @JmsListener

ScheduledAnnotationBeanPostProcessor:解析 @Scheduled

另外再提一个跟 PostProcessor 不想关的 AnnotationConfigUtils:解析 @Lazy @Primary @DependsOn @Role @Description

6.4.2  ConfigurationClassPostProcessor

这个后置处理器那是相当牛逼,你看他解析的注解 :@Configuration @Component @Import @ComponentScan @Bean 都是响当当的。

这个注解真的,咱们这里主要是看看扩展点,我以前的看原理的时候好几个都是起源于这个处理器,在这里我们就不细看了哈。

最后统一说下这些 PostProcessor 放到上下文或者Bean 工厂的时机:

在创建上下文对象的时候 AnnotationConfigApplicationContext:

public AnnotationConfigApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);
}
// 注解形式的读写器
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environmAssert.notNull(registry, "BeanDefinitionRegistry must not be null");Assert.notNull(environment, "Environment must not be null");this.registry = registry;this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);// 注册注解相关的处理器 都在这里边AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {...Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);// ConfigurationClassPostProcessorif (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));}// AutowiredAnnotationBeanPostProcessorif (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));}// CommonAnnotationBeanPostProcessorif (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));}// PersistenceAnnotationBeanPostProcessorif (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {...beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));}...return beanDefs;
}

7  Aware

Aware 接口是一系列接口的集合,它们允许 Spring 容器向 Bean 提供有关其运行环境的附加信息。这些接口通常用于让 Bean 知道 Spring 容器中的某些特定上下文信息,例如 Bean 工厂、应用上下文、资源加载器等。

public interface Aware {
}

(1)BeanFactoryAware:允许 Bean 访问 BeanFactory。

这对于需要直接访问 BeanFactory 的 Bean 特别有用,例如,当需要通过 BeanFactory 来获取其他 Bean 的引用时。

(2)ApplicationContextAware:允许 Bean 访问 ApplicationContext。

这对于需要访问整个应用上下文的 Bean 特别有用,例如,当需要获取其他 Bean 的引用或访问应用上下文中的配置信息时。

(3)EnvironmentAware:允许 Bean 访问 Environment。

这对于需要访问配置文件或系统属性的 Bean 特别有用。

(4)ResourceLoaderAware:允许 Bean 访问 ResourceLoader。

这对于需要加载资源文件的 Bean 特别有用。

(5)MessageSourceAware:允许 Bean 访问 MessageSource。

这对于需要国际化支持的 Bean 特别有用,例如,当需要从消息源中检索消息时。

(6)ApplicationEventPublisherAware:允许 Bean 访问 ApplicationEventPublisher。

这对于需要发布或订阅应用事件的 Bean 特别有用。

(7)EmbeddedValueResolverAware:允许 Bean 访问 EmbeddedValueResolver。

这对于需要解析表达式中的占位符的 Bean 特别有用。

(8)WebApplicationContextAware:允许 Bean 访问 WebApplicationContext。

这对于 Web 应用中的 Bean 特别有用,例如,当需要访问 ServletContext 或其他 Web 上下文信息时。

7.1  入口时机

入口的话,只要你的 Bean 能被 BeanFactory 管理就可以,下边的执行时机会说。

7.2  执行时机

比如上边的 8个 Aware,除了 BeanFactoryAware 其他的都是依托于 ApplicationContextAwareProcessor(实现了BeanPostProcessor)执行 Bean 初始化前扩展方法来注入的。

class ApplicationContextAwareProcessor implements BeanPostProcessor {private final ConfigurableApplicationContext applicationContext;public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {this.applicationContext = applicationContext;this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());}// Bean 初始化前扩展点方法
    @Override@Nullablepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {...invokeAwareInterfaces(bean);return bean;}private void invokeAwareInterfaces(Object bean) {if (bean instanceof EnvironmentAware) {((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());}if (bean instanceof EmbeddedValueResolverAware) {((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);}if (bean instanceof ResourceLoaderAware) {((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);}if (bean instanceof ApplicationEventPublisherAware) {((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);}if (bean instanceof MessageSourceAware) {((MessageSourceAware) bean).setMessageSource(this.applicationContext);}if (bean instanceof ApplicationStartupAware) {((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup());}if (bean instanceof ApplicationContextAware) {((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);}}
}

那我们再顺便看一下 ApplicationContextAwareProcessor 是怎么来的,它是在 AbstractApplicationContext 刷新 refresh 方法的时候,工厂创建出来后,有个 prepareBeanFactory 方法里:

@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 启动步骤标记StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// 准备刷新 Prepare this context for refreshing.
        prepareRefresh();// 通知子类刷新内部bean工厂 Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 准备工厂以便在此上下文中使用 Prepare the bean factory for use in this context.// 这个里边放入的
        prepareBeanFactory(beanFactory);...}
}
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {...// 添加Bean后置处理器  ApplicationContextAwareProcessorbeanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));...
}

8  @PostConstruct、@PreDestory

由于他俩是同一个 InitDestroyAnnotationBeanPostProcessor 处理器来完成的,就放一块来看了。

8.1  入口时机

我们上边看过 CommonAnnotationBeanPostProcessor 是继承了 InitDestroyAnnotationBeanPostProcessor,所以从 CommonAnnotationBeanPostProcessor 看起:

public CommonAnnotationBeanPostProcessor() {// 设置 InitDestroyAnnotationBeanPostProcessor 的两个属性值 告诉他解析哪两个注解setInitAnnotationType(PostConstruct.class);setDestroyAnnotationType(PreDestroy.class);ignoreResourceType("javax.xml.ws.WebServiceContext");
}
InitDestroyAnnotationBeanPostProcessor 继承了 MergedBeanDefinitionPostProcessor,那我们看下它的 postProcessMergedBeanDefinition 方法:
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {// 找 @PostConstruct @PreDestroy 的注解信息LifecycleMetadata metadata = findLifecycleMetadata(beanType);// 二次检查
    metadata.checkConfigMembers(beanDefinition);
}
private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {// 为空说明没解解析过 开始解析if (this.lifecycleMetadataCache == null) {return buildLifecycleMetadata(clazz);}// 下边是直接走缓存
    ...return metadata;
}
private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {// this.initAnnotationType @PostConstruct// this.destroyAnnotationType @PreDestory// 没有这俩注解的话 直接返回默认的空信息if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) {return this.emptyLifecycleMetadata;}// 分别存放两种类型的集合List<LifecycleElement> initMethods = new ArrayList<>();List<LifecycleElement> destroyMethods = new ArrayList<>();Class<?> targetClass = clazz;do {final List<LifecycleElement> currInitMethods = new ArrayList<>();final List<LifecycleElement> currDestroyMethods = new ArrayList<>();ReflectionUtils.doWithLocalMethods(targetClass, method -> {// 判断方法是由有 @PostConstructif (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {LifecycleElement element = new LifecycleElement(method);// 有的话就添加进currInitMethods里
                currInitMethods.add(element);if (logger.isTraceEnabled()) {logger.trace("Found init method on class [" + clazz.getName() + "]: " + method);}}// 判断方法是由有 @PreDestoryif (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {// 有的话就添加进currDestroyMethods里currDestroyMethods.add(new LifecycleElement(method));if (logger.isTraceEnabled()) {logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method);}}});initMethods.addAll(0, currInitMethods);destroyMethods.addAll(currDestroyMethods);targetClass = targetClass.getSuperclass();}while (targetClass != null && targetClass != Object.class);return (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata :new LifecycleMetadata(clazz, initMethods, destroyMethods));
}

解析完,我们最后会知道这个 Bean :

当它的类中没有 @PostConstruct 和 @PreDestory 会返回一个默认空的 emptyLifecycleMetadata

有的话得到一个 LifecycleMetadata,initMethods 属性里存放了该类所有的 @ 方法,destroyMethods 存放了该类所有的 @ 方法;

那么有了 LifecycleMetadata,还有一个二次检查的方法 checkConfigMembers,我们继续看看:

public void checkConfigMembers(RootBeanDefinition beanDefinition) {Set<LifecycleElement> checkedInitMethods = new LinkedHashSet<>(this.initMethods.size());// 遍历 @PostConstruct 方法for (LifecycleElement element : this.initMethods) {String methodIdentifier = element.getIdentifier();if (!beanDefinition.isExternallyManagedInitMethod(methodIdentifier)) {// 放进了beanDefinition里
            beanDefinition.registerExternallyManagedInitMethod(methodIdentifier);checkedInitMethods.add(element);if (logger.isTraceEnabled()) {logger.trace("Registered init method on class [" + this.targetClass.getName() + "]: " + methodIdentifier);}}}Set<LifecycleElement> checkedDestroyMethods = new LinkedHashSet<>(this.destroyMethods.size());for (LifecycleElement element : this.destroyMethods) {String methodIdentifier = element.getIdentifier();if (!beanDefinition.isExternallyManagedDestroyMethod(methodIdentifier)) {// 放进了beanDefinition里
            beanDefinition.registerExternallyManagedDestroyMethod(methodIdentifier);checkedDestroyMethods.add(element);if (logger.isTraceEnabled()) {logger.trace("Registered destroy method on class [" + this.targetClass.getName() + "]: " + methodIdentifier);}}}// 最后我们的 checkedInitMethods 和 checkedDestroyMethods 里会存放检查后的方法this.checkedInitMethods = checkedInitMethods;this.checkedDestroyMethods = checkedDestroyMethods;
}

checkedInitMethods 和 initMethods 有啥区别么?其实是为了给执行做准备,执行的话会优先取 checked里的,当 checked 是空的话,取 initMethods。

至于为什么这样可能是不是跟别的有冲突,防止多次执行吧,我猜的。

8.2  执行时机

(1)@PostConstruct 的执行

InitDestroyAnnotationBeanPostProcessor 本身是个 BeanPostProcessor,所以它是在 Bean 初始化前的前置处理 postProcessBeforeInitialization 里执行的,它又是来源于 Bean 生命周期的 doCreateBean 里的 initializeBean方法里执行的,

这个要熟悉 Bean 的生命周期过程中的一些常见方法比如 getBean->doGetBean->createBean->doCreateBean->createBeanInstance->populateBean->initializeBean,不熟悉的话可以看我前边的文章哈。

那我们看看 postProcessBeforeInitialization:

// InitDestroyAnnotationBeanPostProcessor
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 这里解析过会有缓存 直接取缓存的LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());try {// 执行
        metadata.invokeInitMethods(bean, beanName);}...return bean;
}
// LifecycleMetadata
public void invokeInitMethods(Object target, String beanName) throws Throwable {// 先取 checkedInitMethodsCollection<LifecycleElement> checkedInitMethods = this.checkedInitMethods;// checkedInitMethods 为空的话取 initMethodsCollection<LifecycleElement> initMethodsToIterate =(checkedInitMethods != null ? checkedInitMethods : this.initMethods);// 遍历执行if (!initMethodsToIterate.isEmpty()) {for (LifecycleElement element : initMethodsToIterate) {// 执行
            element.invoke(target);}}
}

假如有多个方法的话,本身是按List存储的,也没有排序这种概念,所以从上到下解析,从类的前到后依次执行。

(2)@PreDestory 的执行

它的执行是 InitDestroyAnnotationBeanPostProcessor 继承了 DestructionAwareBeanPostProcessor,也就是在销毁某个 Bean 的时候执行。

这里简单说一下销毁 Bean 都有哪些方式:

  • 容器管理的 Bean 销毁:默认销毁方法,当 Spring 容器关闭时,会自动调用所有单例(singleton)作用域的 Bean 的销毁方法(如果定义了销毁方法)。如果 Bean 实现了 DisposableBean 接口,Spring 会调用 destroy() 方法。如果 Bean 定义了 destroy-method 属性,Spring 会调用指定的方法。显式销毁:可以通过 ApplicationContext 的 close() 方法显式关闭 Spring 容器,从而触发 Bean 的销毁。
  • 编程式销毁:手动销毁:开发者可以通过调用 Bean 的销毁方法来手动销毁 Bean。例如,如果 Bean 实现了 DisposableBean 接口,可以手动调用 destroy() 方法。如果 Bean 定义了 destroy-method,可以手动调用该方法。
  • 原型作用域的 Bean 销毁:每次请求销毁:对于原型(prototype)作用域的 Bean,每次创建实例后,实例的生命周期由调用方管理。Spring 容器不会自动销毁原型作用域的 Bean。如果需要销毁,需要手动调用销毁方法。
  • 外部系统销毁:如果 Bean 的销毁是由外部框架或容器管理的,那么 Spring 容器不会调用销毁方法。这种情况下,需要将 isExternallyManagedDestroyMethod 设置为 true。

最后我们看看 postProcessBeforeDestruction:

public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());try {// 执行销毁
        metadata.invokeDestroyMethods(bean, beanName);}...
}
public void invokeDestroyMethods(Object target, String beanName) throws Throwable {// 先取 checkedDestroyMethodsCollection<LifecycleElement> checkedDestroyMethods = this.checkedDestroyMethods;Collection<LifecycleElement> destroyMethodsToUse =(checkedDestroyMethods != null ? checkedDestroyMethods : this.destroyMethods);// 遍历执行if (!destroyMethodsToUse.isEmpty()) {for (LifecycleElement element : destroyMethodsToUse) {// 执行
            element.invoke(target);}}
}

9  InitializingBean

它允许 Bean 在初始化完成后执行一些特定的操作。实现 InitializingBean 接口的目的是为了确保 Bean 在完全初始化之前执行一些必要的初始化逻辑,例如建立数据库连接、加载配置信息等。

public interface InitializingBean {void afterPropertiesSet() throws Exception;
}

9.1  入口时机

这个扩展不需要解析,只要你的 Bean 被 Bean 工厂管理就行。

9.2  执行时机

它的执行时机来源于 Bean 生命周期的 doCreateBean 里的 initializeBean方法里执行的:

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {...invokeAwareMethods(beanName, bean);/** 调用初始化方法:* 1. 若 bean 实现了 InitializingBean 接口,则调用 afterPropertiesSet 方法* 2. 若用户配置了 bean 的 init-method 属性,则调用用户在配置中指定的方法*/invokeInitMethods(beanName, wrappedBean, mbd);...
}
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)throws Throwable {// 看当前 Bean 是否实现了 InitializingBean 接口boolean isInitializingBean = (bean instanceof InitializingBean);// 是的话,并且不是交给外部管理的(没见过外部管理的,一般都是交给 Bean 工厂管理)if (isInitializingBean && (mbd == null || !mbd.hasAnyExternallyManagedInitMethod("afterPropertiesSet"))) {...// 调用afterPropertiesSet
        ((InitializingBean) bean).afterPropertiesSet();}// 如果还指定了 init-method 那么执行一下该方法if (mbd != null && bean.getClass() != NullBean.class) {String initMethodName = mbd.getInitMethodName();if (StringUtils.hasLength(initMethodName) &&!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&!mbd.hasAnyExternallyManagedInitMethod(initMethodName)) {// 调用用户自定义的方法
            invokeCustomInitMethod(beanName, bean, mbd);}}
}

10  DisposableBean

它允许 Bean 在容器销毁时执行一些特定的操作。实现 DisposableBean 接口的目的是为了确保 Bean 在销毁之前执行一些必要的清理操作,例如关闭数据库连接、释放资源等。

public interface DisposableBean {void destroy() throws Exception;        
}

10.1  入口时机

这个扩展不需要解析,只要你的 Bean 被 Bean 工厂管理就行。

10.2  执行时机

对于单例(singleton)作用域的 Bean,当 Spring 容器关闭时,会调用所有实现了 DisposableBean 接口的 Bean 的 destroy 方法。

当应用程序正常结束时,如果使用了 SpringApplication.run() 方法启动应用,Spring Boot 会在应用结束时自动调用容器的 close() 方法,从而触发 Bean 的销毁。

对于原型(prototype)作用域的 Bean,Spring 容器不会自动调用 destroy 方法,因为每个请求都会创建一个新的 Bean 实例。如果需要在原型作用域的 Bean 销毁时执行清理操作,需要手动调用 destroy 方法。

比如 SpringBoot 启动的时候会在 refreshContext 刷新上下文的时候,注册关闭函数 context.registerShutdownHook();

@Override
public void registerShutdownHook() {if (this.shutdownHook == null) {// No shutdown hook registered yet.this.shutdownHook = new Thread() {@Overridepublic void run() {synchronized (startupShutdownMonitor) {doClose();}}};Runtime.getRuntime().addShutdownHook(this.shutdownHook);}
}
protected void doClose() {// Publish shutdown event.publishEvent(new ContextClosedEvent(this));// 生命周期的关闭this.lifecycleProcessor.onClose();// 销毁 bean
    destroyBeans();...
}
protected void destroyBeans() {getBeanFactory().destroySingletons();
}
public void destroySingletons() {...String[] disposableBeanNames;synchronized (this.disposableBeans) {disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());}// 遍历执行销毁for (int i = disposableBeanNames.length - 1; i >= 0; i--) {destroySingleton(disposableBeanNames[i]);}...
}

与 destroy-method 的区别:

DisposableBean 接口是 Spring 提供的一种标准方式,而 destroy-method 是通过配置指定的销毁方法。

使用 DisposableBean 接口的好处在于它提供了一种标准的方式,而 destroy-method 则更加灵活,但可能缺乏一定的规范性。

与 InitializingBean 的关系:

DisposableBean 和 InitializingBean 分别用于销毁和初始化操作,它们可以独立使用,也可以同时在一个 Bean 上使用。

11  小结

暂时写到这里哈,想到新的扩展点的话,还有每个扩展点的一些常用操作后续再补充哈,有理解不对的地方欢迎指正哈。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/789828.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智能选择:如何筛选合适项目管理软件?

国内外主流的 10 款项目管理系统对比:PingCode、Worktile、Teambition、明道云、泛微E-cology、Asana、Trello、Monday.com、ClickUp、Wrike。在项目管理的世界里,选择合适的管理工具似乎是一个令人头疼的问题。你是否经常在众多选项中感到迷茫,不知道哪一个系统能真正提升你…

【整理】【信息收集】web渗透测试

一、WAF探测1.1 什么是WAF1.2 WAF常见的部署方式:1.3 WAF的作用1.4 如何触发waf1.5 WAF的分类1.6 如何探测WAF二、操作系统识别三、Git信息收集3.1 Git信息泄露原理3.2 github搜索技巧3.3 Git信息泄露利用方式原创 LULU 红队蓝军一、WAF探测 1.1 什么是WAF Web应用程序防火墙(…

RouteLLM:通过智能查询路由优化 AI 响应

在当今人工智能驱动的世界中,优化人工智能的使用至关重要。不同的 AI 系统 在能力和成本上各不相同,因此需要智能管理解决方案。RouteLLM 是一个创新 的框架,旨在动态地将用户查询路由到最合适的 AI 模型,确保成本效益和高质 量的响应。 什么是 RouteLLM? RouteLLM 作为 A…

vue3下拉菜单点击之后缓慢展开与缓慢关闭

利用 max-height 来实现下拉菜单的缓慢展开和关闭效果。通过设置一个固定的 max-height 值以及过渡效果,可以让菜单在展开和关闭时产生动画效果。 <template> <div class="dropdown"><div class="selected" @click="toggleDropdown&q…

我的奇妙屋

我的奇妙屋 实验一角开始发布

管理农业项目必备 10款项目管理软件评估和选择技巧

国内外主流的 10 款农业建设管理系统对比:PingCode、Worktile、建米农业工程项目管理系统、泛普软件的农业项目管理系统、开创云数字农业管理平台、Trimble Ag Software、Agworld、FarmLogs、Granular、Conservis。在管理复杂的农业建设项目时,选择合适的管理系统常常让人头疼…

第三周作业

1、在docker中分别以后台方式和交互方式启动centos,对比启动后的容器状态,实现退出容器也能保持其运行状态。 2、在docker并部署DVWA,要求:DVWA web 端口映射到8082,提供访问截图。3、Mysql练习 (1)创建一个名为"magedu_C10"的数据库; (2)在magedu_C10数据…

任正非署名文章《星光不问赶路人》:没有退路就是胜利之路

华为心声社区以总裁办电子邮件形式,发布了任正非于2020年6月19日的讲话:《星光不问赶路人》,我看了好几遍,热血沸腾。“对未来科学的探索不停步,研发不停步,继续勇往直前。不能以后生存下来了,却看不见未来了。没有明天了,这样的生存是没有意义的。战略研究院要继续扩大…

nss第四页

1、[GDOUCTF 2023]EZ WEB 首先这题查看源码可以看到有个目录访问src这代码的意思就是,如果访问/super-secret-route-nobody-will-guess 的请求方法为put,那么就输出flag 所以就直接抓包然后改请求就行了2、[GDOUCTF 2023]泄露的伪装 这题的话,需要扫描目录访问www.rar,得到…

信奥赛一本通陈老师解题 1128:图像模糊处理

​【题目描述】给定n行m列的图像各像素点的灰度值,要求用如下方法对其进行模糊化处理: 1.四周最外侧的像素点灰度值不变; 2.中间各像素点新灰度值为该像素点及其上下左右相邻四个像素点原灰度值的平均(舍入到最接近的整数)。【输入】第一行包含两个整数n和m,表示图像包含像…

作业8.26:自我介绍+软工5问

这个作业属于哪个课程 班级链接这个作业要求在哪里 作业要求链接这个作业的目标 掌握 Markdown 基本使用;初步预习教材。介绍我自己 🍁嗨,我是凌枫,运行下面的 Python 代码,您将看到我的自我介绍! class UniversityStudent:def __init__(self, major, hobbies, current_…

037.CI4框架CodeIgniter,使用Model模型绑定数据库表

01、我们创建一个数据库,如下:CREATE TABLE `user` (`id` int(20) NOT NULL AUTO_INCREMENT,`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`userpassword` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAUL…