Spring系列-04-事件机制,监听器,模块/条件装配

事件机制&监听器

SpringFramework中设计的观察者模式-掌握

SpringFramework 中, 体现观察者模式的特性就是事件驱动监听器监听器充当订阅者, 监听特定的事件;事件源充当被观察的主题, 用来发布事件;IOC 容器本身也是事件广播器, 可以理解成观察者

  • 事件源:发布事件的对象
  • 事件:事件源发布的信息 / 作出的动作
  • 广播器:事件真正广播给监听器的对象【即ApplicationContext
    • ApplicationContext 接口有实现 ApplicationEventPublisher 接口, 具备事件广播器的发布事件的能力
    • ApplicationEventMulticaster 组合了所有的监听器, 具备事件广播器的广播事件的能力
  • 监听器:监听事件的对象
    在这里插入图片描述

事件与监听器使用

两种方式, 一种是实现``ApplicationListener接口, 一种是注解@EventListener`

ApplicationListener接口

SpringFramework 中内置的监听器接口是 ApplicationListener , 它还带了一个泛型, 代表要监听的具体事件:

@FunctionalInterface // 函数式接口
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E event);
}

事件ContextRefreshedEventContextClosedEvent , 它们分别代表容器刷新完毕即将关闭, 这里以监听ContextRefreshedEvent事件为例子

@Component // 监听器注册到IOC容器当中
public class ContextRefreshedApplicationListener implements ApplicationListener<ContextRefreshedEvent> { // 监听 ContextRefreshedEvent 事件@Override public void onApplicationEvent(ContextRefreshedEvent event) {System.out.println("ContextRefreshedApplicationListener监听到ContextRefreshedEvent事件!");}
}

@EventListener注解式监听器

使用注解式监听器, 组件不再需要实现任何接口, 而是直接在需要作出事件反应的方法上标注 @EventListener 注解即可

@Componentpublic class ContextClosedApplicationListener {@EventListenerpublic void onContextClosedEvent(ContextClosedEvent event) {System.out.println("ContextClosedApplicationListener监听到ContextClosedEvent事件!");}
}
public class QuickstartListenerApplication {    public static void main(String[] args) throws Exception {System.out.println("准备初始化IOC容器。。。");AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.linkedbear.spring.event.a_quickstart");System.out.println("IOC容器初始化完成。。。");ctx.close();System.out.println("IOC容器关闭。。。");/*控制台输出结果如下: ContextRefreshedApplicationListener监听到ContextRefreshedEvent事件!IOC容器初始化完成。。。ContextClosedApplicationListener监听到ContextClosedEvent事件!IOC容器关闭。。。*/}
}
  • ApplicationListener 会在容器初始化阶段就准备好, 在容器销毁时一起销毁
  • ApplicationListener 也是 IOC 容器中的普通 Bean

SpringFramework中的内置事件-熟悉

在 SpringFramework 中, 已经有事件的默认抽象, 以及4个默认的内置事件

ApplicationEvent

ApplicationEvent是事件模型的抽象, 它是一个抽象类, 里面也没有定义什么东西, 只有事件发生时的时间戳。

public abstract class ApplicationEvent extends EventObject // 继承自 jdk 原生的观察者模式的事件模型, 并且把它声明为抽象类

Class to be extended by all application events. Abstract as it doesn’t make sense for generic events to be published directly.

翻译:

由所有应用程序事件扩展的类。它被设计为抽象的, 因为直接发布一般事件没有意义

ApplicationContextEvent

public abstract class ApplicationContextEvent extends ApplicationEvent { // 继承ApplicationEventpublic ApplicationContextEvent(ApplicationContext source) {super(source);}public final ApplicationContext getApplicationContext() {return (ApplicationContext) getSource();}
}

构造方法将IOC 容器一起传进去, 这意味着事件发生时, 可以通过监听器直接取到 ApplicationContext 而不需要做额外的操作, 这才是 SpringFramework 中事件模型扩展最值得的地方。下面列举的几个内置的事件, 都是基于这个 ApplicationContextEvent 扩展的

ContextRefreshedEvent&ContextClosedEvent

这两个是一对, 分别对应着 IOC 容器刷新完毕但尚未启动, 以及 IOC 容器已经关闭但尚未销毁所有 Bean 。这个时机可能记起来有点小困难, 小伙伴们可以不用记很多, 只通过字面意思能知道就 OK , 至于这些事件触发的真正时机, 在我的 SpringBoot 源码小册第 16 章中有提到, 感兴趣的小伙伴可以去看一看。在后面的 IOC 原理篇中, 这部分也会略有涉及。

ContextStartedEvent&ContextStoppedEvent

这一对跟上面的时机不太一样了。ContextRefreshedEvent 事件的触发是所有单实例 Bean 刚创建完成后, 就发布的事件, 此时那些实现了 Lifecycle 接口的 Bean 还没有被回调 start 方法。当这些 start 方法被调用后, ContextStartedEvent 才会被触发。同样的, ContextStoppedEvent 事件也是在 ContextClosedEvent 触发之后才会触发, 此时单实例 Bean 还没有被销毁, 要先把它们都停掉才可以释放资源, 销毁 Bean 。

自定义事件开发

什么时候需要自定义?

想自己在合适的时机发布一些事件, 让指定的监听器来以此作出反应, 执行特定的逻辑

自定义事件到底有什么刚需吗?讲道理, 真的非常少。很多场景下, 使用自定义事件可以处理的逻辑, 完全可以通过一些其它的方案来替代, 这样真的会显得自定义事件很鸡肋

运行示例

论坛应用, 当新用户注册成功后, 会同时发送短信、邮件、站内信, 通知用户注册成功, 并且发放积分。

在这个场景中, 用户注册成功后, 广播一个“用户注册成功”的事件, 将用户信息带入事件广播出去, 发送短信、邮件、站内信的监听器监听到注册成功的事件后, 会分别执行不同形式的通知动作。

自定义事件

仿Spring内置的事件, 继承ApplicationEvent

/*** 注册成功的事件*/
public class RegisterSuccessEvent extends ApplicationEvent {public RegisterSuccessEvent(Object source) {super(source);}
}

监听器

使用实现ApplicationListener接口, 添加@EventListener注解两种

实现ApplicationListener接口
@Component
public class SmsSenderListener implements ApplicationListener<RegisterSuccessEvent> {@Overridepublic void onApplicationEvent(RegisterSuccessEvent event) {System.out.println("监听到用户注册成功, 发送短信。。。");}
}
添加EmailSenderListener注解
@Component
public class EmailSenderListener {@EventListenerpublic void onRegisterSuccess(RegisterSuccessEvent event) {System.out.println("监听到用户注册成功!发送邮件中。。。");}
}
@Component
public class MessageSenderListener {@EventListenerpublic void onRegisterSuccess(RegisterSuccessEvent event) {System.out.println("监听到用户注册成功, 发送站内信。。。");}
}

注册逻辑业务层(事件发布器)

只有事件和监听器还不够, 还需要有一个事件源来持有事件发布器, 在应用上下文中发布事件

Service 层中, 需要注入 ApplicationEventPublisher 来发布事件, 此处选择使用回调注入的方式

@Service
public class RegisterService implements ApplicationEventPublisherAware {ApplicationEventPublisher publisher;/*** 用户注册: 注册后会发布事件, 也就是说它是事件源*/public void register(String username) {// 用户注册的动作。。。System.out.println(username + "注册成功。。。");// 发布事件, 将我们自定义的事件进行发布publisher.publishEvent(new RegisterSuccessEvent(username));}@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher publisher) {this.publisher = publisher;}
}

测试启动类

public class RegisterEventApplication {public static void main(String[] args) throws Exception {AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.linkedbear.spring.event.b_registerevent");RegisterService registerService = ctx.getBean(RegisterService.class);// 调用方法, 进行发布registerService.register("张大三");/*控制台打印结果如下: 张大三注册成功。。。监听到用户注册成功, 发送邮件中。。。监听到用户注册成功, 发送站内信。。。监听到用户注册成功, 发送短信。。。*/}
}

注解式监听器的触发时机比接口式监听器早

调整监听器的触发顺序

监听器上标**@Order可以调整触发顺序, 默认的排序值为 Integer.MAX_VALUE , 代表最靠后**

使用如下

@Order(0)
@Component
public class MessageSenderListener {@EventListenerpublic void onRegisterSuccess(RegisterSuccessEvent event) {System.out.println("监听到用户注册成功, 发送站内信。。。");}
}

模块装配&条件装配(理解)

SpringBoot 的自动装配, 基础就是模块装配 + 条件装配

模块装配

原生手动装配

最原始的 Spring不支持注解驱动开发, 后续逐渐引入注解驱动开发, 现在常用@Configuration + @Bean 注解组合, 或者 @Component + @ComponentScan 注解组合, 可以实现编程式/声明式的手动装配

存在的问题: 如果要注册的 Bean 很多, 要么一个一个的 @Bean 编程式写, 要么就得选好包进行组件扫描, 而且这种情况还得每个类都标注好 @Component 或者它的衍生注解才行。面对数量很多的Bean , 这种装配方式很明显会比较麻烦

模块概念和模块装配

模块可以理解成一个一个的可以分解、组合、更换的独立的单元, 模块与模块之间可能存在一定的依赖, 模块的内部通常是高内聚的, 一个模块通常都是解决一个独立的问题

模块特征

  • 独立的
  • 功能高内聚
  • 可相互依赖
  • 目标明确

模块构成

在这里插入图片描述

模块装配可以理解为把一个模块需要的核心功能组件都装配好(注意, 这里强调了核心功能)

Spring的模块装配

SpringFramework中的模块装配, 是在3.1之后引入大量**@EnableXXX注解, 来快速整合激活**相对应的模块(这里用的是词语是激活, 也就是一个开关的意思, 不需要我们再手动将这些模块所需的Bean挨个注册了)

@EnableXXX注解的使用例子

  • EnableTransactionManagement :开启注解事务驱动
  • EnableWebMvc :激活 SpringWebMvc
  • EnableAspectJAutoProxy :开启注解 AOP 编程
  • EnableScheduling :开启调度功能(定时任务)
@Import注解解析

模块装配的核心原则:自定义注解 + @Import 导入组件

@Import注解解析

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {/*** {@link Configuration @Configuration}, {@link ImportSelector},* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.*/Class<?>[] value();
}

文档中写道"允许导入 @ Configuration类、ImportSelector和ImportBeanDefinitionRegistrar实现以及常规组件类", 这里的组件类就被Spring管理的普通类

@Import使用案例

模仿Spring的@EnableXxx注解, 实现一个自己定义的注解

定义老板和调酒师模型

老板

@Data
public class Boss {}

调酒师

@Data
public class Bartender {   private String name;
}
注册调酒师对象(配置类统一管理)
@Configuration
public class BartenderConfiguration {@Beanpublic Bartender zhangxiaosan() {return new Bartender("张小三");}@Beanpublic Bartender zhangdasan() {return new Bartender("张大三");}}
定义注解
@Import({Boss.class, BartenderConfiguration.class}) // 说明要导入
public @interface EnableTavern {}

启动类里或者配置类上用了包扫描, 恰好把这个类扫描到了, 导致即使没有 @Import 这个 BartenderConfiguration , Bartender 调酒师也被注册进 IOC 容器了

酒馆配置类
@EnableTavern // Tavern配置类中添加上这个注解, 表明使用这个配置时将其相关bean激活, 并自动注册到Spring中 
@Configuration
public class TavernConfiguration {}
测试
public class TavernApplication {public static void main(String[] args) throws Exception {AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);System.out.println("--------------------------");// 一次性取出IOC容器指定类型的所有BeanMap<String, Bartender> bartenders = ctx.getBeansOfType(Bartender.class);// 会打印出两个调酒师, 分别为zhangxiaosan和zhangdasan的内存地址bartenders.forEach((name, bartender) -> System.out.println(bartender));}
}
ImportSelector接口解析
public interface ImportSelector {/*** 根据导入@Configuration类的AnnotationMetadata选择并返回应导入的类的名称。即是全限定类名* 返回值: 类名, 如果没有, 则为空数组*/String[] selectImports(AnnotationMetadata importingClassMetadata);/*** 返回一个谓词, 用于从导入候选项中排除类, 以传递方式应用于通过此选择器的导入找到的所有类。
如果此谓词对于给定的完全限定类名称返回true , 则该类将不会被视为导入的配置类, 从而绕过类文件加载和元数据内省* 返回值: 可传递导入的配置类的完全限定候选类名称的筛选谓词, 如果没有, 则为null*/@Nullabledefault Predicate<String> getExclusionFilter() {return null;}}

Interface to be implemented by types that determine which @Configuration class(es) should be imported based on a given selection criteria, usually one or more annotation attributes.

它是一个接口, 它的实现类可以根据指定的筛选标准(通常是一个或者多个注解)来决定导入哪些配置类

ImportSelector使用案例

注意了, 这是在@Import的案例上继续进行的

定义吧台类
@Data
public class Bar {}
吧台配置类
@Configuration
public class BarConfiguration {   @Beanpublic Bar myBar() {return new Bar();}
}
实现ImportSelector接口
public class BarImportSelector implements ImportSelector {   @Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[] {Bar.class.getName(), BarConfiguration.class.getName()};}
}
@EnableTavern的@Import添加BarImportSelector全类名
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class})
public @interface EnableTavern {
}
重新运行TavernApplication

最终结果会打印出bar, 也就说明ImportSelector是可以导入普通和配置类的

ImportBeanDefinitionRegistrar接口

如果说 ImportSelector 更像声明式导入的话, 那 ImportBeanDefinitionRegistrar 就可以解释为编程式向 IOC 容器中导入 Bean 。不过由于它导入的实际是 BeanDefinition ( Bean 的定义信息)

关于BeanDefinition的, 后续细说, 目前暂时不对ImportBeanDefinitionRegistrar进行过多性的解析, 相关用到的方法会简单说明

ImportBeanDefinitionRegistrar使用案例

注意了, 这是在ImportSelector的案例上继续进行的

定义服务员模型
@Data
public class Waiter {}
实现ImportBeanDefinitionRegistrar接口
public class WaiterRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {/*registerBeanDefinition传入两个参数第一个参数: Bean的id第二个参数: RootBeanDefinition要指定Bean字节码对象(即是class对象)*/registry.registerBeanDefinition("waiter", new RootBeanDefinition(Waiter.class));}
}
把WaiterRegistrar标注在@EnableTavern的 @Import
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class, WaiterRegistrar.class})
public @interface EnableTavern {}
运行TavernApplication

运行结果会发现waiter出现在打印结果当中

模块装配总结

什么是模块装配?

  • 将每个独立的模块所需要的核心功能组件进行装配

模块装配的核心是什么?

  • 通过@EnableXxx注解快速激活相应的模块

模块装配方式有几种

四种, 分别如下

  1. @Import + @Bean
  2. @Import + @Configuration
  3. @Import + ImportSelector实现类
  4. @Import + ImportBeanDefinitionRegistrar实现类

条件装配

有了模块装配为什么还需要有条件装配?

模块装配的主要目的是将应用程序划分为一系列的功能模块, 每个模块内部管理自己的 Bean 定义

上述的模块装配颗粒度太粗了, 没法做更加细致的控制, 比如什么情况要使用哪个配置, 而条件装配则是根据条件装配是指根据特定的条件来决定一个配置是否生效。例如, 可以根据系统属性、Bean 的存在与否、类路径上的特定资源等条件来决定某个配置是否应该被加载

Profile

Spring3.1引入进来profile, profile翻译过来就有配置文件的意思, 作用就是基于项目运行环境动态注册所需要的组件, 通常用于区分测试, 预发布, 生产环境的配置

Javadoc解释如下

Indicates that a component is eligible for registration when one or more specified profiles are active. A profile is a named logical grouping that may be activated programmatically via ConfigurableEnvironment.setActiveProfiles or declaratively by setting the spring.profiles.active property as a JVM system property, as an environment variable, or as a Servlet context parameter in web.xml for web applications. Profiles may also be activated declaratively in integration tests via the @ActiveProfiles annotation.

@Profile 注解可以标注一些组件, 当应用上下文的一个或多个指定配置文件处于活动状态时, 这些组件允许被注册。

配置文件是一个命名的逻辑组, 可以通过 ConfigurableEnvironment.setActiveProfiles 以编程方式激活, 也可以通过将 spring.profiles.active 属性设置为 JVM 系统属性, 环境变量或 web.xml 中用于 Web 应用的 ServletContext 参数来声明性地激活, 还可以通过 @ActiveProfiles 注解在集成测试中声明性地激活配置文件。

profile仍然存在颗粒度粗的问题, 因为profile控制的是整个项目的运行环境, 无法根据单个bean是否装配, 这里得依靠@Conditional实现更加细的粒度

@Profile使用

下述代码是基于模块装配代码上继续操作

调酒师工作的环境应该是啤酒店而不是其它随意环境, 例如菜市场

伪代码如下

if(工作环境 == 啤酒店) {调酒师工作
} else {调酒师不来=}

现在通过@Profile进行环境选择

@Profile("beer-shop") // 这里是@Porfile指定环境, 表明当运行环境为beer-shop, 那么就会将以下bean注册到Spring中
@Configuration
public class BartenderConfiguration {@Beanpublic Bartender zhangxiaosan() {return new Bartender("张小三");}@Beanpublic Bartender zhangdasan() {return new Bartender("张大三");}
}

之前Javadoc中不是指出了, 可以通过以下方式指定运行环境吗?

  • 编程式
  • Spring的active属性(最常用)
  • @ActiveProfiles
编程式指定

需要说明的是, ApplicationContext默认profile是"default", 也就说我们的@Profile(“beer-shop”)是不匹配的, 那么该配置下的两个bean是不会注册到IOC容器中的(自己可以运行)

可以通过以下方式指定运行环境

public class TavernApplication {public static void main(String[] args) throws Exception {// 注意了! 这里没有在构造方法指定配置文件, 由于AnnotationConfigApplicationContext传入配置类, 内部会进行自动初始化, 到时候打印不出bean, 所以这里选择手动将配置类注册到ctx中AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();// 给ApplicationContext的环境设置正在激活的profile// PS: environment是一个Spring中环境对象(后续会说)ctx.getEnvironment().setActiveProfiles("beer-shop");// 注册配置, 注意了这个register动作必须在setActiveProfiles之后ctx.register(TavernConfiguration.class);// 改变了环境(即是配置发生了变化, 得我们通知Spring), 那么这里需要调用refresh进行刷新ctx.refresh();// 此时运行控制台就会出现zhangxiaosan和zhangdasanStream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);}
}
声明式指定

在IDEA的启动配置的VM Option中指定下述参数

-Dspring.profiles.active=beer-shop

指定完后如下
在这里插入图片描述

当然也可以通过Spring的配置文件声明

spring:prifiles:active: beer-shop

Conditional

condition翻译过来就是条件, @Conditional 是在 SpringFramework 4.0 版本正式推出, 目的就是将满足@Conditiaonal上指定所有条件的bean进行装配(看到没有, 这里就实现了更加细粒度的装配)

Javadoc

Indicates that a component is only eligible for registration when all specified conditions match.

A condition is any state that can be determined programmatically before the bean definition is due to be registered (see Condition for details).

The @Conditional annotation may be used in any of the following ways:

as a type-level annotation on any class directly or indirectly annotated with @Component, including @Configuration classes
as a meta-annotation, for the purpose of composing custom stereotype annotations
as a method-level annotation on any @Bean method
If a @Configuration class is marked with @Conditional, all of the @Bean methods, @Import annotations, and @ComponentScan annotations associated with that class will be subject to the conditions.

被 @Conditional 注解标注的组件, 只有所有指定条件都匹配时, 才有资格注册。条件是可以在要注册 BeanDefinition 之前以编程式确定的任何状态。

@Conditional 注解可以通过以下任何一种方式使用:

  • 作为任何直接或间接用 @Component 注解的类的类型级别注解, 包括@Configuration 类
  • 作为元注解, 目的是组成自定义注解
  • 作为任何 @Bean 方法上的方法级注解

如果@Configuration配置类被@Conditional 标记, 则与该类关联的所有@Bean 的工厂方法, @Import 注解和 @ComponentScan 注解也将受条件限制。

唯一需要解释就是最后一句话, 它想表达的是@Conditional 注解标注的 组件类 / 配置类 / 组件工厂方法 必须满足 @Conditional 中指定的所有条件, 才会被创建 / 解析

@Conditional使用
@Conditional普通使用
@Conditianal注解解析以及Condition实现类
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {/*** 要注册组件必须匹配的所有条件*/Class<? extends Condition>[] value();
}

查看@Conditional 注解源码, 返现中需要传入一个 Condition 接口的实现类数组, 说明咱还需要编写条件匹配类做匹配依据。那咱就先写一个匹配条件

public class ExistBossCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// 这里用的是BeanDefinition做判断而不是Bean, 考虑的是当条件匹配时, 可能Boss还没被创建, 导致条件匹配出现偏差return context.getBeanFactory().containsBeanDefinition(Boss.class.getName());}
}
@Conditional添加规则类

在BarConfiguration中指定bar创建需要有Boss存在

@Configuration
public class BarConfiguration { @Bean@Conditional(ExistBossCondition.class)public Bar bbbar() {return new Bar();}
}
注释@Import, 减少干扰项

为了不干扰结果, 现在将EnableTavern上的@Import注释掉

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
// @Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class, WaiterRegistrar.class})
public @interface EnableTavern {}
运行main

运行下面的main方法, 那么就会发现确实 Bossbbbar 都没了

public static void main(String[] args) throws Exception {AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}
@Conditional派生派生

Javadoc中说了, @Conditional可以派生, 那么派生一个新注解@ConditionalOnBean, 即是当指定Bean存在的时候为之匹配

定义条件匹配规则类

首先明确需要一个条件匹配规则类, 虽然是派生的, 但是@Cdonditianal最终还是需要传入这个规则类的

public class OnBeanCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// 从ConditionalOnBean注解中获取到需要匹配的Bean类型和Bean名Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnBean.class.getName());// 遍历需要匹配的Bean类型, 检查Bean工厂中是否包含对应的Bean定义, 如果不包含则返回falseClass<?>[] classes = (Class<?>[]) attributes.get("value");for (Class<?> clazz : classes) {if (!context.getBeanFactory().containsBeanDefinition(clazz.getName())) {return false;}}// 遍历需要匹配的Bean名称, 检查Bean工厂中是否包含对应的Bean定义, 如果不包含则返回falseString[] beanNames = (String[]) attributes.get("beanNames");for (String beanName : beanNames) {if (!context.getBeanFactory().containsBeanDefinition(beanName)) {return false;}}// 执行到这里说明所有的Bean类型和Bean名称都匹配, 则返回truereturn true;}
}
定义注解
@Documented
@Conditional(OnBeanCondition.class) // 这里指定需要条件匹配规则类
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD}) // 这里指明了, 字段可用, 方法可用
public @interface ConditionalOnBean {/*** class对象*/        Class<?>[] value() default {};/*** beanNames指定bean*/String[] beanNames() default {};
}
替换原生的@Condional
@Bean
// @ConditionalOnBean(beanNames = "xxx.xxxx.xxx.Boss") // 全类名可以
@ConditionalOnBean(Boss.class) // class对象也行
public Bar bbbar() {return new Bar();
}

此时重新运行, 发现也确实是一样的效果

参考资料

从 0 开始深入学习 Spring

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

create-vue源码学习之 gradient-string 渐变色打印

效果 在使用 create-vue 脚手架时&#xff0c;想实现如下的打印效果。 探究过程 翻到源码里看到这一行 没错&#xff0c;绿色部分就是告诉我们如何生成的。可以看到引入了 gradient-string 包 于是乎&#xff0c;我来试试 pnpm i gradient-string pnpm i --save-dev …

1.4、存储系统

目录 存储器的层次结构外存&#xff08;辅存&#xff09;内存CPU的寄存器Cache总结举例局部性原理 练习题 高速缓存Cache总结举例总结 练习题 Cache的地址映像方法直接相联映像全相联映像组相联映像练习题 Cache替换算法Cache页面淘汰算法Cache的读写过程练习题 磁盘总结固态硬…

人工智能(AI)在办公场所的广泛应用

人工智能&#xff08;AI&#xff09;在办公场所的广泛应用正逐步改变着我们的工作方式和效率。随着技术的进步&#xff0c;越来越多的公司和组织开始采用各种AI技术来优化工作流程、提升生产力&#xff0c;并提供更好的用户体验。以下是人工智能在办公方面的一些主要作用和影响…

Ecovadis评估的流程是什么

Ecovadis评估流程是一个全面、系统且注重细节的过程&#xff0c;旨在为企业提供关于其可持续性表现的深入洞察。这一评估不仅覆盖了企业在环境、社会和治理方面的多个方面&#xff0c;还强调了持续改进的重要性&#xff0c;确保企业能够不断提升其CSR&#xff08;企业社会责任&…

社交圈子聊天交友系统搭建社交app开发:陌生交友发布动态圈子单聊打招呼群聊app介绍

系统概述 社交圈子部天交友系统是一个集成即时通讯、社区互动、用户管理等功能的在线社交平台。它支持用户创建个人资料&#xff0c;加入兴趣围子&#xff0c;通过文字、图片、语音、视频等多种方式进行交流&#xff0c;满足用户在不同场景下的社交需求 核心功能 -&#xff0c;…

Window系统下MySQL安装教程

1、MySQL各版本介绍 MySQL Community Edition MySQL Community Edition 是MySQL官方发布的免费版本&#xff0c;适用于个人用户和小型团队使用。它包含了基本的数据库功能&#xff0c;如创建表、插入数据、查询数据等。 MySQL Enterprise Edition MySQL Enterprise Edition 是…

【数据结构】AVL树(图文解析 + 代码实现)

目录 1、AVL树的概念 2、AVL树结点的定义 3、AVL树的插入 4、AVL树的旋转 4.1 左单旋 4.2 右单旋 4.3 右左双旋 4.4 左右双旋 5、AVL树的验证 6、AVL树的性能 前面对map/multimap/set/multiset进行了简单的介绍&#xff0c;会大仙&#xff0c;这几个容器有个共同点是…

【AI大模型】程序员AI的未来——Copilot还是Claude3.5 Sonnet?

近期&#xff0c;Anthropic发布了Claude 3.5 的“大杯”模型 —— Claude 3.5 Sonnet&#xff01; 这次发布的 Sonnet 代表意大利的“十四行诗”&#xff0c;结构复杂&#xff0c;在智能水平、功能多样性和处理能力上都有所提升&#xff0c;能够应对更复杂的认知任务&#xff…

解决VS2019+Qt联合开发双击Resource Files弹不出资源编辑器问题

目录 一、右键Resource.qrc文件 二、选择打开方式 三、鼠标选择Qt Resource Editor&#xff0c;并设置为默认值 四、最后点击确定&#xff0c;再次双击qrc文件&#xff0c;成功打开 最近在开发中&#xff0c;遇见一个问题&#xff0c;在VS联合Qt开发时&#xff0c;需要添加…

前后端分离项目部署,vue--nagix发布部署,.net--API发布部署。

目录 Nginx免安装部署文件包准备一、vue前端部署1、修改http.js2、npm run build 编译项目3、解压Nginx免安装,修改nginx.conf二、.net后端发布部署1、编辑appsetting.json,配置跨域请求2、配置WebApi,点击发布3、配置文件发布到那个文件夹4、配置发布相关选项5、点击保存,…

推荐一款基于 SpringBoot2 的后台管理系统脚手架,非常轻量简单(附源码)

前言 在现代软件开发中&#xff0c;后台管理系统是企业数字化转型的关键组成部分。然而&#xff0c;现有软件常常存在一些痛点&#xff0c;如复杂的权限管理、缺乏灵活的工作流配置、监控和日志功能不完善等。此外&#xff0c;许多系统study 成本高&#xff0c;依赖关系复杂&a…

VS2015加断点(红色),修改过后,断点变为白色不能命中

实际这个问题是因为&#xff1a;源文件和原始版本不同。解决方法有二&#xff1a; 一&#xff0c;在断点上右键&#xff0c;选择“位置”》勾选”允许源代码与原始版本不同&#xff1b; 二&#xff0c;点击菜单栏“调试”》“选项和设置”》“常规”》去掉“要求源文件与原始…

MINE:Mutual Information Neural Estimation

Mutual Information Neural Estimation 摘要 文章认为高维连续随机变量之间的互信息可以通过在神经网络上梯度下降优化得到。 文中提出了互信息估计器(Mutual Information Neural Estimator),它在维度和 样本大小上都是可伸缩的&#xff0c;可以通过反向传播训练的&#xff0…

OCC 布尔运算

目录 一、裁剪原理 二、使用详解 1. 差集 (Cut) 2. 联合 (Fuse/Union) 3. 交集 (Common/Intersection) 三、例子 1、两个盒子裁剪 2、任意面裁剪 四、总结 一、裁剪原理 OpenCASCADE (OCC) 中的裁剪(Boolean Cut)原理主要基于布尔运算。布尔运算是计算机图形学中的…

力扣第二十四题——两两交换链表中的节点

内容介绍 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4] 输出&#xff…

Python-numpy基础--------2

1.full()创建函数 目录 1.full()创建函数 2.创建单位矩阵 3.linspace创建 4.logspace 创建 5.二维数组的索引和切片&#xff1a; 1.索引直接获取 在NumPy中&#xff0c;full() 函数用于创建一个给定形状、类型的新数组&#xff0c;并用指定的值填充这个数组。这个函数非…

数模·插值和拟合算法

插值 将离散的点连成曲线或者线段的一种方法 题目中有"任意时刻任意的量"时使用插值&#xff0c;因为插值一定经过样本点 插值函数的概念 插值函数与样本离散的点一一重合 插值函数往往有多个区间&#xff0c;多个区间插值函数样态不完全一样&#xff0c;简单来说就…

AWS监控工具,监控性能指标

执行AWS监视是为了跟踪在AWS环境中主动运行的应用程序工作负载和资源&#xff0c;AWS监视器跟踪各种AWS云指标&#xff0c;以帮助提高在其上运行的应用程序的整体性能。 借助阈值突破警报系统&#xff0c;AWS应用程序监控在识别性能瓶颈来源方面起着至关重要的作用&#xff0c…

linux版mysql8配置表名不区分大小写

mysql8的安装步骤可参考&#xff1a; mysql8的安装步骤 如果在安装mysql8&#xff0c;初始化之前&#xff0c;没有在my.cnf配置忽略大小写配置&#xff1a; [mysqld] lower_case_table_names1我们就需要重新初始化mysql 1 备份数据库文件 2 停止mysql服务 systemctl stop …

HTML+CSS+JS扫雷(可自定义雷数,大小,可插旗)

源代码在最后面 点赞❤️ 关注⭐️谢谢&#x1f61c; 实现功能 随机扫雷自定义地雷数、游戏棋盘大小插旗 效果图&#xff08;部分图片&#xff09; 源代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><m…