范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

阿里大佬教你写Spring组件

  一、宗旨
  在如日中天的 Spring 架构体系下,不管是什么样的组件,不管它采用的接入方式如何眼花缭乱,它们永远只有一个目的:  接入Spring容器二、Spring 容器
  Spring 容器内可以认为只有一种东西,那就是   bean   ,但是围绕  bean   的生命周期,Spring 添加了许多东西 2.1bean的生命周期2.1.1 实例化 bean 实例
  实例化 bean 实例是 spring 针对 bean 作的拓展最多的周期
  它包括:  bean 的扫描  bean 的解析  bean 实例化
  常见扫描相关内容:
  @Component   、  @Service   、  @Controller   、  @Configuration   、  applicationContext.xml
  spring/springboot 在启动的时候,会扫描到这些注解或配置文件修饰的类信息
  根据拿到的类信息,经过第二步解析后,转换成   BeanDefintion   存入到 spring 容器当中,  BeanDefintion   描述 bean 的 class、scop、beanName 等信息
  在 bean 的解析过程中,我们常用到的 Properties 读取 、   @Configuration   配置类的处理 会在这一步完成
  bean 的实例化实际有自动完成和调用   getBean()   时候完成,还有容器初始化完毕之后实例化 bean ,他们都是根据 bean 的定义  BeanDefintion   来反射目标 bean 类,并放到 bean 容器当中
  这就是大名鼎鼎的 bean 容器,就是一个 Map  private final Map singletonObjects = new ConcurrentHashMap<>(256);2.1.2 设置实例属性
  这一阶段是   @Value   、  @Autowired   、  @Resource   注解起作用的阶段 2.1.3 bean 前置处理
  BeanPostProcessor   前置处理方法 2.1.4 bean init 处理
  @PostConstruct   注解起作用的阶段 2.1.5 bean 后置处理
  BeanPostProcessor   后置处理方法 2.1.6 正常使用2.1.7 bean 销毁
  @PreDestroy   注解起作用的阶段
  bean   的销毁过程中,主要的作用就是释放一些需要手动释放的资源和一些收尾工作,如文件归并、连接池释放等
  在了解了 Spring bean 的生命周期后,我们接下来介绍自建 Spring 组件的接入方式  三、使用简单配置类接入方式
  使用配置类接入 Spring ,一般需要搭配   PostConstruct   来使用,并且要确保 Spring 能扫描到配置
  如,在组件   quartz-configable   1.0 版本当中,就是使用的这种方式
  quartz-configable   需要扫描用户自定义的  job   来注册到  quartz-configable   自动创建的调度器  Scheduler   当中,并启动调度器  Scheduler
  在注册   Job   的过程当中,又添加了自定义的  TriggerListener   监听器,来监听配置的变动,以动态调整  Job   执行时机 @Configuration public class QuartzInitConfig {      @Autowired     private Scheduler scheduler;      @Autowired     private CustomTriggerListener customTriggerListener;      @PostConstruct     public void init() {         //先把所有jobDetail放到map里         initJobMap();          //添加自定义Trigger监听器,进行任务开关的监听和故障定位的配置         addTriggerListener(scheduler, customTriggerListener);          //添加任务到任务调度器中         addJobToScheduler(scheduler);          //启动任务调度器         try {             scheduler.start();         } catch (SchedulerException e) {             log.error("任务调度器启动失败", e);             throw new RuntimeException("任务调度器启动失败");         }         log.info("任务调度器已启动");     }      private void initJobMap() {         //省略部分代码     }      private void addJobToScheduler(Scheduler scheduler) {         //省略部分代码     }      private void addTriggerListener(Scheduler scheduler, CustomTriggerListener customTriggerListener) {         //省略部分代码     } }
  QuartzInitConfig   类的作用是把扫描到的任务类放入调度器当中,并添加自定义监听(用于动态修改 cron 表达式)
  此类加载有两个过程:  注入组件初始化需要的资源  根据注入的资源初始化组件
  步骤   1   所需要的功能与 Spring 的注入功能完美契合,而恰好  @Configuration   修饰的类也被当作了一个  Spring bean   ,所以才能顺利注入组件需要的资源
  步骤   2   的初始化任务,极为契合  Spring bean   创建完毕后的初始化动作  @PostConstruct   当中,它同样是资源注入完毕后的初始化动作。 四、带有条件的简单配置类
  有时候,我们希望通过开关或者特定的配置来启用应用内具备的功能,这时候,我们可以使用   @ConditionalOnProperty   来解决问题
  risk   组件扫描出符合规则的切点,在切点执行之前,去执行发送风控数据到风控平台的动作 @Configuration @ConditionalOnProperty({"risk.expression", "risk.appid", "risk.appsecret", "risk.url"}) public class RiskAspectConfig {      //项目内配置     @Value("${risk.expression}")     private String riskExpression;      @Bean     public DefaultPointcutAdvisor defaultPointcutAdvisor() {         SpringBeans springBeans = springBeans();         RiskSenderDelegate riskSenderDelegate = new RiskSenderDelegate(springBeans);         GrjrMethodInterceptor grjrMethodInterceptor = new GrjrMethodInterceptor(riskSenderDelegate);         JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut();         jdkRegexpMethodPointcut.setPattern(riskExpression);         log.info("切面准备完毕,切点为{}", riskExpression);         return new DefaultPointcutAdvisor(jdkRegexpMethodPointcut, grjrMethodInterceptor);     }      //省略部分代码  }
  虽然类   RiskAspectConfig   是一个 Spring 配置类,方法  defaultPointcutAdvisor()   创建了一个切点顾问,用来在切点方法处实现风控的功能,但是,并不是应用启动之后,切点就会生效,这是因为有  @ConditionalOnProperty   的存在
  @ConditionalOnProperty   的作用:
  根据提供的条件判断对应的属性是否存在,存在,则加载此配置类,不存在,则忽略。
  当应用中存在如下配置时:  grjr:   risk:    expression: xxxx    appid: xxx    appsecret: xxx    url: xxx
  RiskAspectConfig   配置类才会被加载,才会生成切点顾问  DefaultPointcutAdvisor   ,因此切点就会生效
  当需要的配置逐渐增多的时候,一条条添加进   @ConditionalOnProperty   显得比较冗长复杂,这时候该如何处理呢? 五、使用对应的 Properties 配置类来封装配置
  在项目   fastdfs-spring-boot-starter   当中,像上述需要的配置有很多,那么它是怎么处理的呢?
  它把需要的配置放到了一个 Java 类里  @ConfigurationProperties(prefix = "fastdfs.boot") public class FastDfsProperties {     private String trackerServerHosts;     private int trackerHttpPort = 80;     private int connectTimeout = 5000;     private int networkTimeout = 30000;     private boolean antiStealToken = false;     private String charset = "ISO8859-1";     private String secretKey;      //省略字段 get set 方法 }
  其中,   @ConfigurationProperties   指定了配置的  prefix   ,上述配置相当于 fastdfs:   boot:     trackerServerHosts: xxx     trackerHttpPort: 80     connectTimeout: 5000     networkTimeout: 30000     antiStealToken: false     charset: ISO8859-1     secretKey: xxx
  这种类到现在为止还不可以和 Spring 结合起来,尚需要把它声明为   Spring bean   才生效
  声明为   Spring bean   有两种形式 在类本身上添加   @Component   注解,标识这是一个  Spring bean  在   @Configuration   类上使用  @EnableConfigurationProperties   来启用配置
  通常的,在开发组件的时候,我们使用第二种方式,把 Properties 的启用,交给   @Configuration   配置类来管理,大家可以想想为什么 @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(FastDfsClient.class) // 当 Spring 容器中不存在 FastDfsClient 时才加载这个类 @EnableConfigurationProperties(FastDfsProperties.class) //启用上面的 FastDfsProperties public class FastDfsAutoConfiguration {      /**     * 创建 FastDfsClient 放到 Spring 容器当中     */     @Bean     @ConditionalOnProperty("fastdfs.boot.trackerServerHosts")     FastDfsClient fastDFSClient(FastDfsProperties fastDfsProperties) {         globalInit(fastDfsProperties);         return new FastDfsClient();     }      /**     * 根据 properties 来配置 fastdfs     */     private void globalInit(FastDfsProperties fastDFSProperties) {         // 省略部分代码     }      //省略部分代码 }
  @EnableConfigurationProperties(FastDfsProperties.class)   启用了括号内的 Properties 类,并把它们注入到 Spring 容器当中,使其可以被其他  Spring bean   导入 六、使用 META-INF/spring.factories 文件来代替扫描
  有时候,我们开发的组件的类路径和应用的类路径不同,比如,应用类路径常常为   com.xxx.xxx   ,而组件的类路径常常为  com.xxx.yyy   ,这时候,经常需要为 Spring 指定扫描路径,才能把我们的组件加载进去,如果在自己项目当中加载上述  quartz-configable   组件,组件类路径为  com.xxx.yyy   : @ComponentScan({"com.xxx.xxx", "com.xxx.yyy"}) @SpringBootApplication public class GrjrFundBatch {      public static void main(String[] args) {         SpringApplication.run(GrjrFundBatch.class);     } }
  如果新增了类似这样的   quartz-configable   组件,就需要改动  @ComponentScan   代码,这对启动类是有侵入性的,也是繁琐的,也极有可能写错,当组件路径有改动的时候也需要跟着改动
  怎样避免这种硬编码形式的注入呢?
  Springboot 在加载类的时候,会扫描   classpath   下的  META-INF/spring.factories   文件,当发现了  spring.factories   文件后,根据文件中的配置来加载类
  其中一项配置为   org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx.xxx.xxx.xxxx   ,它声明了 Springboot 要加载的自动配置类,Springboot根据配置自动去加载配置类
  借用这个规则,现在来升级我们的   quartz-configable   组件
  我们在组件项目   resources   目录下添加  META-INF/spring.factories   文件,文件内容如下 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.grjr.quartz.config.GjSchedulerAutoConfiguration
  然后在应用启动类当中删除已经无用的   @Component   注解即可 @SpringBootApplication public class Application {      public static void main(String[] args) {         SpringApplication.run(Application.class);     } }
  此时,   quartz-configable   依然能生效
  使用   META-INF/spring.factories   虽然带来了简洁和便利,但是它总是去自动加载配置类,所以我们在设计组件的时候,一定要搭配  @ConditionOnxxxx   注解,有条件的去加载我们的组件 七、使用自定义 @EnableXxxx 注解的形式开启组件功能
  就像上面说的一样,使用   META-INF/spring.factories   总会去加载配置类,自定义扫描路径有可能会写错类路径,那么,还有没有其他方式呢?
  有,使用自定义注解来注入自己的组件,就像   dubbo   的 starter 组件一样,我们自己造一个  @EnableXxx   注解 7.1 自定义注解的核心
  自定义注解的核心是 Spring 的   @Import   注解,它基于  @Import   注解来注入组件自身需要的资源和初始化组件自身 7.2 @Import 注解解析
  @Import   注解是 Spring 用来注入  Spring bean   的一种方式,可以用来修饰别的注解,也可以直接在 Springboot 配置类上使用。
  它只有一个value属性需要设置,来看一下源码  public @interface Import {     Class<?>[] value(); }
  这里的 value属性只接受三种类型的Class:  @Configuration org.springframework.context.annotation.ImportBeanDefinitionRegistrar org.springframework.context.annotation.ImportSelector
  下面针对三种类型的 Class 分别做简单介绍,中间穿插自定义注解与外部配置的结合使用方式。  7.2.1 被@Configuration修饰的配置类
  像 Springboot 中的配置类一样正常使用,需要注意的是,如果该类的包路径已在 Springboot 启动类上配置的扫描路径下,则不需要再重新使用   @Import   导入了,因为  @Import   的目的是注入 bean,但是 Springboot 启动类自动扫描已经可以注入你想通过  @Import   导入的 bean 了。 7.2.2 接口org.springframework.context.annotation.ImportBeanDefinitionRegistrar的实现类
  当   @Import   修饰自定义注解时候,通常会导入这个接口的实现类。
  来看一下接口定义  public interface ImportBeanDefinitionRegistrar {     default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,             BeanNameGenerator importBeanNameGenerator) {         registerBeanDefinitions(importingClassMetadata, registry);     }      /**     * importingClassMetadata 被@Import修饰的自定义注解的元信息,可以获得属性集合     * registry Spring bean注册中心     **/     default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {     }
  通过这种方式,我们可以根据自定义注解配置的属性值来注入 Spring Bean 信息。
  来看如下案例,我们通过一个注解,启动 RocketMq 的消息发送器:  @SpringBootApplication @EnableMqProducer(group="xxx") public class App {     public static void main(String[] args) {         SpringApplication.run(App.class);     } }
  这是一个服务项目的启动类,这个服务开启了 RocketMq 的一个发送器,并且分到 xxx 组里。
  来看一下   @EnableMqProducer   注解 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({XXXRegistrar.class,XXXConfig.class}) public @interface EnableMqProducer {      String group() default "DEFAULT_PRODUCER_GROUP";      String instanceName() default "defaultProducer";      boolean retryAnotherBrokerWhenNotStoreOK() default true; }
  这里使用   @Import   导入了两个配置类,第一个是接口  org.springframework.context.annotation.ImportBeanDefinitionRegistrar   的实现类,第二个是被  @Configuration   修饰的配置类
  我们看第一个类   XXXRegistrar   ,这个类的功能是注入一个自定义的  DefaultMQProducer   到Spring 容器中,使业务方可以直接通过  @Autowired   注入  DefaultMQProducer   使用 public class XXXRegistrar implements ImportBeanDefinitionRegistrar {       @Override     public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {         //获取注解里配置的属性         AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableMqProducer.class.getName()));         //根据配置的属性注入自定义 bean 到 spring 容器当中         registerBeanDefinitions(attributes, registry);     }      private void registerBeanDefinitions(AnnotationAttributes attributes, BeanDefinitionRegistry registry) {         //获取配置         String group = attributes.getString("group");         //省略部分代码...         //添加要注入的类的字段值         Map values = new HashMap<>();         //这里有的同学可能不清楚为什么key是这个         //这里的key就是DefaultMQProducer的字段名         values.put("producerGroup", group);         //省略部分代码          //注册到Spring中         BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, DefaultMQProducer.class.getName(), DefaultMQProducer.class, values);     }
  到这里,我们已经注入了一个   DefaultMQProducer   的实例到 Spring 容器中,但是这个实例,还不完整,比如: 还没有启动  nameServer地址还没有配置  外部配置的属性还没有覆盖实例已有的值(nameServer地址建议外部配置)。
  但是好消息是,我们已经可以通过注入来使用这个未完成的实例了。
  上面遗留的问题,就是第二个类接下来要做的事。
  来看第二个配置类  @Configuration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @EnableConfigurationProperties(XxxProperties.class)  //Spring提供的配置自动映射功能,配置后可直接注入  public class XXXConfig {      @Resource //直接注入     private XxxProperties XxxProperties;      @Autowired //注入上一步生成的实例     private DefaultMQProducer producer;      @PostConstruct     public void init() {         //省略部分代码         //获取外部配置的值         String nameServer = XxxProperties.getNameServer();         //修改实例         producer.setNamesrvAddr(nameServer);         //启动实例         try {             this.producer.start();         } catch (MQClientException e) {             throw new RocketMqException("mq消息发送实例启动失败", e);         }     }      @PreDestroy     public void destroy() {         producer.shutdown();     }
  到这里,通过自定义注解和外部配置的结合,一个完整的消息发送器就可以使用了,但方式有取巧之嫌,因为在消息发送器启动之前,不知道还有没有别的类使用了这个实例,这是不安全的。  7.2.3 接口org.springframework.context.annotation.ImportSelector的实现类
  首先看一下接口  public interface ImportSelector {      /**      * importingClassMetadata 注解元信息,可获取自定义注解的属性集合      * 根据自定义注解的属性,或者没有属性,返回要注入Spring的Class全限定类名集合      如:XXX.class.getName(),Spring会自动注入XXX的一个实例      */     String[] selectImports(AnnotationMetadata importingClassMetadata);      @Nullable     default Predicate getExclusionFilter() {         return null;     }  }
  这个接口的实现类如果没有进行 Spring   Aware   接口拓展,功能比较单一,因为我们无法参与 Spring Bean 的构建过程,只是告诉 Spring 要注入的 Bean 的名字。不再详述。 八、总结
  综上所述,我们一共聊了三种形式的组件创建方式  @Configuration META-INF/spring.factories @Import
  其中穿插了   @ConditionOnXxxx   选择性启动、  Properties   封装的技术,快去试一下吧
  原文地址:   https://www.cnblogs.com/qnlcy/p/15905682.html

引领原创真乐创新表达REALME动感地带2021来电之夜圆满落幕10月23日晚,由中国移动支持咪咕公司主办咪咕音乐承办的原创音乐盛典REALME动感地带2021来电之夜在五粮液成都金融城演艺中心盛大开演。当晚,来电嘉宾李宇春毛不易,携手来电唱作凭什么骂联想?凭什么骂联想?联想又办了一件挨骂的事。2021年9月30日,港股上市的联想集团(00992。HK)向上交所递交登陆科创板的招股书获受理。10月8日,上交所公告,联想集团和保荐人中金一览众山小,比亚迪登顶新能源车最高峰好消息传来!比亚迪乘用车8月新能源乘用车销售60508辆,同比增长331。9,实现中国市场月销三连冠。事实上,7月份比亚迪新能源汽车销量就超过5万辆,同比增长234。4。EVsal中国移动杨杰董事长分享数智生产力新观点本届大会将数字文明作为主题,精准地概括了时代发展的新脉络。古往今来,每一次文明的演进,本质都是技术驱动下人类文明之树的突破性成长。在这一过程中,新基础设施的普及新要素的应用是根茎,一个APP,运营商再怎么难都要搞下去最近,某运营商在推动一个APP的行动,希望整合旗下的所有资源,给未来发展打下基础。就一些网络舆论来看,也有部分不满反应,但总体上依然处在相对正常的程度,可以想见,随着活动的深化,引加速美国车队智能电动化比亚迪携手Levo部署5000辆纯电动车洛杉矶时间2021年10月5日,比亚迪和LevoMobilityLLC(下文简称Levo)共同宣布,将整合NuvveHolding(下文简称Nuvve)的车到电网(V2G)技术与比京东会不会收购沃尔玛?据美媒报道沃尔玛10月9日突然宣布,将其全球供应商业务部从中国搬到印度,立即生效。沃尔玛中国方面对此回应称,将供应商业务部迁址这一消息理解为沃尔玛全球供应商总部从中国搬到印度是完全拉闸限电,5G基站怎么办?运营商们业绩扛得住各地在拉闸限电了,这一波好像来的很出人意料。毕竟,后疫情时代,经济仍没有达到火热的最高峰。8月份,我国经济持续稳定恢复,全社会用电量持续增长,达到7607亿千瓦时。18月,全社会用从Facebook到Meta,元宇宙因何替代了大社交?元宇宙已经被炒了起来,虽然并不如比特币那样突然爆红,而是一直存在争议,但毫无疑问是这两年的一个热点。这不,扎克伯克宣布将Facebook公司名称改为了Meta,全力进军元宇宙,甚至双十一超值新品上线,轻薄5G荣耀X30i超大屏荣耀X30Max正式发布10月28日,荣耀举办11。11新品发布会,正式推出荣耀X30i以及荣耀X30Max两款新机。其中,荣耀X30i定位轻薄,是目前主流厂商6。7英寸及以上手机中最轻的产品荣耀X30M互联网券商怎会无法无天?10月15日,接近监管部门的人士向媒体表态称,按照所有金融活动均应纳入监管的要求,证监会等监管部门正着力完善相关监管规则,将依法对(富途老虎等)此类活动予以规范,加强监管执法,全面
两万用户的信心来源,第二代CS55PLUS如何给予?有一款车,2017年上市时,仅用半年就突破10万销量还是这款车,四年后换代预售,一个月就有超2万的订单。不得不说,这样的成绩,在如今汽车产品层出不穷的时代里,绝对称得上出色。这就是老年人也能看懂的电脑知识第一篇什么是主板?大家好,我是兰州老张,从事电脑行业20余年,写此系列文章的初心在于让广大老年人也能搞明白各类复杂的电脑知识,文章里不会出现生涩的专业名词和硬核知识,希望大家喜欢关注支持收藏点赞。虽老年人也能看懂的电脑知识第十篇什么是鼠标键盘?大家好,我是兰州老张,从事电脑行业20余年,写此系列文章的初心在于让广大老年人也能搞明白各类复杂的电脑知识,文章里不会出现生涩的专业名词和硬核知识,希望大家喜欢关注支持收藏点赞。这第十三篇什么是电脑一体机?老年人也能看懂的电脑知识大家好,我是兰州老张,从事电脑行业20余年,写此系列文章的初心在于让广大老年人也能搞明白各类复杂的电脑知识,文章里不会出现生涩的专业名词和硬核知识,希望大家喜欢关注支持收藏点赞。这第十一篇什么是电脑操作系统?老年人也能看懂的电脑知识大家好,我是兰州老张,从事电脑行业20余年,写此系列文章的初心在于让广大老年人也能搞明白各类复杂的电脑知识,文章里不会出现生涩的专业名词和硬核知识,希望大家喜欢关注支持收藏点赞。这老年人也能看懂的电脑知识第六篇什么是电源?大家好,我是兰州老张,从事电脑行业20余年,写此系列文章的初心在于让广大老年人也能搞明白各类复杂的电脑知识,文章里不会出现生涩的专业名词和硬核知识,希望大家喜欢关注支持收藏点赞。这老年人也能看懂的电脑知识第三篇什么是内存?大家好,我是兰州老张,从事电脑行业20余年,写此系列文章的初心在于让广大老年人也能搞明白各类复杂的电脑知识,文章里不会出现生涩的专业名词和硬核知识,希望大家喜欢关注支持收藏点赞。这年轻人应早逃离嫌你穷怕你富的丑陋圈子年轻人都该有个理想的圣地,解放前年轻人奔赴延安,冒着不畏牺牲生命的风险去追求人生理想,而今时代的变革一日千里,人生只要还有拼搏的资本,就一定要走出去,到大城市去。离开那个天定的,原欧尚X7PLUS宣言人生第一桩事,永远是生活现代社会里,满载着琳琅满目的商品,诱惑不断也充斥了错综复杂的人际关系,令人自顾不暇。在纷繁复杂的世界里,过得简单,才是最好的生存之道。这是哲人教育我们的智慧。而让我们相信,人生第一东莞生产意大利拿去贴牌,3万收购卖30万!中国制造正奋起反击一套所谓的进口家具在东莞生产,3万元收购,意大利贴牌卖出,能卖出多少价?答案是30万元。也就是说,一套靠国内代工贴牌的进口货可以赚取10倍暴利!这并非虚构,而是曾经发生在中国的达芬蔚来跌落,哪吒杀入,新势力格局还未定论随着蔚小理御三家的格局逐渐明确,新势力造车跑马圈地的战火纷飞时代仿佛已经结束。然而,似乎,真正的战争才刚刚开始。8月交付量看点满满刚刚过去的8月,新势力交付量榜单看点多多一边是理想