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.mzph.cn/bicheng/49373.shtml

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

相关文章

新版本异次元荔枝V4自动发卡系统源码

新版本异次元荔枝V4自动发卡系统源码&#xff0c;增加主站货源系统&#xff0c;支持分站自定义支付接口&#xff0c;目前插件大部分免费&#xff0c;UI页面全面更新&#xff0c;分站可支持对接其他分站产品&#xff0c;分站客服可自定义&#xff0c;支持限定优惠。 源码下载&a…

HTML(六)——HTML表单和框架

HTML 表单 HTML 表单用于收集用户的输入信息&#xff0c;是一个包含表单元素的区域 HTML 表单表示文档中的一个区域&#xff0c;此区域包含交互控件&#xff0c;将用户收集到的信息发送到 Web 服务器。 HTML 表单通常包含各种输入字段、复选框、单选按钮、下拉列表等元素。 …

SpringBoot整合Spring Boot Admin实现监控

目录 基本操作流程&#xff1a; 服务端 server 0.创建一个springboot项目 1.导入依赖 2.添加配置信息 3.在启动类添加注解 4.运行 客户端client 1.添加依赖 2.添加配置信息 3.运行 基本操作流程&#xff1a; 服务端 server 0.创建一个springboot项目 1.导入依赖 …

杰发科技Bootloader(1)—— Keil配置地址

IAP方式 BootLoader方式 UDSBoot方式 AC7801的地址分配 用户空间的的地址从8000000开始分配&#xff0c;大小是64页&#xff0c;即128K。 RAM地址从20000000开始 基于UDSboot调试-Boot 烧录Boot之后&#xff0c;ATClinkTool无法连接 用keil查看内存&#xff0c;地址到8005388…

前端在浏览器总报错,且获取请求头中token的值为null

前端请求总是失败说受跨域请求影响&#xff0c;但前后端配置已经没有问题了&#xff0c;如下&#xff1a; package com.example.shop_manage_sys.config;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Conf…

【数据结构】详解二叉树及其操作

无论你觉得自己多么的了不起&#xff0c;也永远有人比你更强。&#x1f493;&#x1f493;&#x1f493; 目录 ✨说在前面 &#x1f34b;知识点一&#xff1a;二叉树的遍历 • &#x1f330;1.创建一棵二叉树 • &#x1f330;2.二叉树的遍历 •&#x1f525;前序遍历 •&a…

提升自身的国际影响力-香港服务器托管的优势

随着全球化的不断深入&#xff0c;中国企业正以前所未有的速度走向世界舞台&#xff0c;不仅在全球市场上展现其竞争力&#xff0c;更在寻求通过技术创新和服务优化来提升自身的国际影响力。在这一过程中&#xff0c;服务器的选择与托管成为了一个至关重要的环节。特别是在香港…

基于SpringBoot实现验证码功能

目录 一 实现思路 二 代码实现 三 代码汇总 现在的登录都需要输入验证码用来检测是否是真人登录&#xff0c;所以验证码功能在现在是非常普遍的&#xff0c;那么接下来我们就基于springboot来实现验证码功能。 一 实现思路 今天我们介绍的是两种主流的验证码&#xff0c;一…

使用php adodb5连接人大金仓数据库

打开php中的pgsql扩展 extensionpgsql使用adodb5连接kingbase数据库 <?php include("adodb5/adodb.inc.php"); $fox_dbtype pgsql; $fox_host 192.168.1.66; $fox_user system; $fox_pwd 123456; $fox_dbname testkingbase; $fox_port 54321;$dbNewADOCo…

大数据技术--实验01-Hadoop的安装与使用【实测可行】

使用下面表中的软件版本进行配置&#xff1a; 准备好后&#xff0c;按照下面的步骤进行配置。 配置VMware网络 在VMWare主界面&#xff0c;点击“编辑”>“虚拟网络编辑”菜单进入虚拟网卡参数设置界面。选择VMnet8条目&#xff0c;点击“NAT设置”按钮后可以看到我们的VM…

使用UDP套接字编程详解【C语言】

UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是一种面向无连接的传输层协议&#xff0c;用于在计算机网络上发送数据。它与 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;相比具有轻量、高效的特点&…

英语(二)-我的学习方式

章节章节汇总我的学习方式历年真题作文&范文 目录 1、背单词 2、学语法 3、做真题 4、胶囊助学计划 写在最前&#xff1a;我是零基础&#xff0c;初二就听天书的那种。 本专栏持续更新学习资料 1、背单词 单词是基础&#xff0c;一定要背单词&#xff01;考纲要求要…

云动态摘要 2024-07-23

给您带来云厂商的最新动态,最新产品资讯和最新优惠更新。 最新优惠与活动 数据库上云优选 阿里云 2024-07-04 RDS、PolarDB、Redis、MongoDB 全系产品新用户低至首年6折起! [免费体验]智能助手ChatBI上线 腾讯云 2024-07-02 基于混元大模型打造,可通过对话方式生成可视化…

中文分词库 jieba 详细使用方法与案例演示

1 前言 jieba 是一个非常流行的中文分词库&#xff0c;具有高效、准确分词的效果。 它支持3种分词模式&#xff1a; 精确模式全模式搜索引擎模式 jieba0.42.1测试环境&#xff1a;python3.10.9 2 三种模式 2.1 精确模式 适应场景&#xff1a;文本分析。 功能&#xff1…

【Zotero插件】Zotero Tag为文献设置阅读状态 win11下相关设置

【Zotero插件设置】Zotero Tag为文献设置阅读状态 win11下相关设置 1.安装Zotero Tag1.1安装1.2配置1.3 win11的相关设置1.3.1 字体安装 参考教程 2.支持排序的标注参考教程 1.安装Zotero Tag 1.1安装 Zotero Tag插件下载链接安装方法&#xff1a;Zotero–》工具–》附加组件…

googleTest 源码主线框架性分析——TDD 01

TDD&#xff0c;测试驱动开发&#xff0c;英文全称Test-Driven Development&#xff0c;简称TDD&#xff0c;是一种不同于传统软件开发流程的新型的开发方法。它要求在编写某个功能的代码之前先编写测试代码&#xff0c;然后只编写使测试通过的功能代码&#xff0c;通过测试来推…

苹果和乔布斯的传奇故事,从车库创业到万亿市值巨头

苹果公司的品牌故事&#xff0c;就像一部充满创新、挑战与辉煌的科幻大片&#xff0c;让人目不暇接。 故事始于1976年&#xff0c;那时&#xff0c;年轻的史蒂夫乔布斯与斯蒂夫沃兹尼亚克在加州的一个简陋车库里&#xff0c;用他们的热情和智慧&#xff0c;捣鼓出了世界上第一…

python学习之闭包与装饰器

一、闭包 闭包允许一个函数访问并操作函数外部的变量&#xff08;即父级作用域中的变量&#xff09;&#xff0c;即使在该函数外部执行。 特性&#xff1a; (1)外部函数嵌套内部函数。 (2)外部函数可以返回内部函数。 (3)内部函数可以访问外部函数的局部变量。 def out()…

linux中使用docker安装mongodb

随着容器的普及&#xff0c;越来越多服务都喜欢跑在容器中&#xff0c;并且安装也很方便快捷&#xff0c;接下来一起看下linux中使用docker来安装mongodb吧&#xff01; 1.首先安装docker&#xff1b; 使用Yum 进行安装&#xff0c;我安装docker比较喜欢参考阿里云中的安装步骤…

通过泛型+函数式编程封装成通用解决方案|缓存穿透、缓存击穿,缓存雪崩

缓存更新方法封装 用到了泛型、函数式编程。 使用函数式编程是因为我们这个是一个通用的工具&#xff0c;使用泛型&#xff08;泛型&#xff08;Generics&#xff09; 允许我们定义类、接口和方法&#xff0c;可以使用不同类型的参数进行操作&#xff09;可以实现数据类型的通…