Spring生命周期 Constructor > @PostConstruct > InitializingBean > init-method

Spring 容器中的 Bean 是有生命周期的,Spring 允许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操作,常用的设定方式有以下三种:

(1) 通过实现 InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;
(2) 通过 <bean> 元素的 init-method/destroy-method属性指定初始化之后 /销毁之前调用的操作方法;
(3) 在指定方法上加上@PostConstruct 或@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用。

具体参考:Spring生命周期 Constructor > @PostConstruct > InitializingBean > init-method

项目中有个需求 读取xml文件,然后 对xml文件进行解析,比如如果是 Gender=0/1的话,分别代表男女。

所以需要在构造函数之后,初始化bean之前进行过滤解析

xml文件:

 <interface name="网约车乘客基本信息(CKJB)" message="baseInfoPassenger">
<field name="interfaceType" valExpr="BASIC" desc="接口类型标识" serialize="false"/>
<field name="command" valExpr="CKJB" serialize="false" desc="接口操作命令"/>
<field name="symbol" valExpr="PassengerPhone"
desc="唯一标识 公司标识(见2.6)加平台自编号(将根据唯一标识执行操作标识对应操作)"/>
<field name="companyId" valExpr="MEITUANDACHE" desc="公司标识,与交通部一致。 见2.6"/>
<field name="registerDate" valExpr="${RegisterDate}" must="false" dataType="long"
desc="注册时间 乘客在平台的注册日期YYYYMMDD"/>
<field name="passengerPhone" valExpr="${PassengerPhone}" desc="乘客电话 "/>
<field name="passengerSex" valExpr="M_genderConvertToNumber(PassengerGender)" dataType="int"
desc="乘客性别 见JT/T 697.7-2014中,与平台发送交通部一致。"/>
<field name="state" valExpr="${State}" dataType="int" desc="状态 0:有效 1:失效"/>
<field name="flag" valExpr="${Flag}" dataType="int" desc="操作标识 1:新增 2:更新 3:删除"/>
<field name="updateTime" valExpr="${UpdateTime}" dataType="long"
desc="更新时间 网约车平台完成数据更新的时间,格式YYYYMMDDHHMMSS"/>
</interface>

注意里面的方法:valExpr="M_genderConvertToNumber(PassengerGender)"

可以继承 InitializingBean 这个接口,然后重写方法:


@Component
public class CityRepositoryImpl implements CityRepository, InitializingBean {
/**
* 模板方法的扫描路径
*/
private static final String TEMPLATE_METHOD_SCAN_LOCATION = "com.sankuai";
/**
* 模板方法
*/
private static final Map<String, TemplateMethod> TEMPLATE_METHOD_MAP = new HashMap<>();
 @Override
public void afterPropertiesSet() {
synchronized (CityRepositoryImpl.class) {
if (TEMPLATE_METHOD_MAP.size() == ) {
loadTemplateMethod();
}
}
}
}

其实方法:afterPropertiesSet 就是设置属性,initializingBean 具体参考:Spring生命周期 Constructor > @PostConstruct > InitializingBean > init-method

然后在这方法里面实现了一个自己定义的类扫描类,只要是继承TempleMethod.class 就 扫描出来

  /**
* 加载模板方法(基于spring的扫描器)
*/
private void loadTemplateMethod() {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AssignableTypeFilter(TemplateMethod.class));
for (BeanDefinition beanDefinition : scanner.findCandidateComponents(TEMPLATE_METHOD_SCAN_LOCATION)) {
try {
TemplateMethod templateMethod = (TemplateMethod) Class.forName(beanDefinition.getBeanClassName()).newInstance(); String name = templateMethod.getMethodName();
if (name == null || name.length() == ) {
throw QcsFactErrorEnum.TEMPLATE_METHOD_ERROR.formException(String.format("模板方法:%s 名称不能为空!", beanDefinition.getBeanClassName()));
} /* 模板方法追加前缀标识 */
String realName = "M_" + name;
if (TEMPLATE_METHOD_MAP.containsKey(realName)) {
throw QcsFactErrorEnum.TEMPLATE_METHOD_ERROR.formException(String.format("模板方法:%s重复定义", name));
}
TEMPLATE_METHOD_MAP.put(realName, templateMethod);
} catch (QcsFactException e) {
throw e;
} catch (Exception e) {
throw QcsFactErrorEnum.TEMPLATE_METHOD_ERROR.formException(e, String.format("模板方法:%s加载失败", beanDefinition.getBeanClassName()));
}
} }

然后方法:

public class GenderTempleMethod {
/**
* 字符型转数字
* 例如
* 男 convert to 1
* 女 convert to 2
*/
public static final class GenderConvertNumber implements TemplateMethod {
@Override
public String getMethodName() {
return "genderConvertToNumber";
} @Override
public Object exec(List list) {
String val = list.get().toString();
Integer result = Gender.UNKNOWN.getValue(); for (Gender gender : Gender.values()) {
if (gender.getName().equals(val)) {
result = gender.getValue();
break;
}
}
return result;
}
}

Gender类:

public enum Gender {
/**
* 男性
*/
MALE("男", ),
/**
* 女性
*/
FEMALE("女", ),
/**
* 未说明
*/
UNKNOWN("未知", ),
/**
* 未解释
*/
OTHER("其他", ); private String name;
private Integer value; Gender(String name, Integer value) {
this.name = name;
this.value = value;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getValue() {
return value;
} public void setValue(Integer value) {
this.value = value;
}
}

具体的spring 自定义扫描器的实现参考:

在我们刚开始接触Spring的时候,要定义bean的话需要在xml中编写,比如:

<bean id="myBean" class="your.pkg.YourClass"/>

后来发现如果bean比较多,会需要写很多的bean标签,太麻烦了。于是出现了一个component-scan注解。这个注解直接指定包名就可以,它会去扫描这个包下所有的class,然后判断是否解析:

<context:component-scan base-package="your.pkg"/>

再后来,由于注解Annotation的流行,出现了@ComponentScan注解,作用跟component-scan标签一样,跟@Configuration注解配合使用:

@ComponentScan(basePackages = {"your.pkg", "other.pkg"})
public class Application { ... }

不论是component-scan标签,还是@ComponentScan注解。它们扫描或解析的bean只能是Spring内部所定义的,比如@Component、@Service、@Controller或@Repository。如果有一些自定义的注解,比如@Consumer、这个注解修饰的类是不会被扫描到的。这个时候我们就得自定义扫描器完成这个操作。
 
Spring内置的扫描器
 
component-scan标签底层使用ClassPathBeanDefinitionScanner这个类完成扫描工作的。@ComponentScan注解配合@Configuration注解使用,底层使用ComponentScanAnnotationParser解析器完成解析工作。

ComponentScanAnnotationParser解析器内部使用了ClassPathBeanDefinitionScanner扫描器,ClassPathBeanDefinitionScanner扫描器内部的处理过程整理如下:

1. 遍历basePackages,根据每个basePackage找出这个包下的所有的class。比如basePackage为your/pkg,会找出your.pkg包下所有的class。找出之后封装成Resource接口集合,这个Resource接口是Spring对资源的封装,有FileSystemResource、ClassPathResource、UrlResource实现等
2. 遍历找到的Resource集合,通过includeFilters和excludeFilters判断是否解析。这里的includeFilters和excludeFilters是TypeFilter接口类型的集合,是ClassPathBeanDefinitionScanner内部的属性。TypeFilter接口是一个用于判断类型是否满足要求的类型过滤器。excludeFilters中只要有一个TypeFilter满足条件,这个Resource就会被过滤。includeFilters中只要有一个TypeFilter满足条件,这个Resource就不会被过滤
3. 如果没有被过滤。把Resource封装成ScannedGenericBeanDefinition添加到BeanDefinition结果集中
4. 返回最后的BeanDefinition结果集
 
TypeFilter接口的定义:

public interface TypeFilter {
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException;
}

TypeFilter接口目前有AnnotationTypeFilter实现类(类是否有注解修饰)、RegexPatternTypeFilter(类名是否满足正则表达式)等。

ClassPathBeanDefinitionScanner继承ClassPathScanningCandidateComponentProvider类。

ClassPathScanningCandidateComponentProvider内部的构造函数提供了一个useDefaultFilters参数:

public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
this(useDefaultFilters, new StandardEnvironment());
}

useDefaultFilters这个参数表示是否使用默认的TypeFilter,如果设置为true,会添加默认的TypeFilter:

protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}

我们看到这里includeFilters加上了AnnotationTypeFilter,并且对应的注解是@Component。@Service、@Controller或@Repository注解它们内部都是被@Component注解所修饰的,所以它们也会被识别。
 
自定义扫描功能
 
一般情况下,我们要自定义扫描功能的话,可以直接使用ClassPathScanningCandidateComponentProvider完成,加上一些自定义的TypeFilter即可。或者写个自定义扫描器继承ClassPathScanningCandidateComponentProvider,并在内部添加自定义的TypeFilter。后者相当于对前者的封装。

我们就以一个简单的例子说明一下自定义扫描的实现,直接使用ClassPathScanningCandidateComponentProvider。

项目结构如下:
./
└── spring
└── study
└── componentprovider
├── annotation
│   └── Consumer.java
├── bean
│   ├── ConsumerWithComponentAnnotation.java
│   ├── ConsumerWithConsumerAnnotation.java
│   ├── ConsumerWithInterface.java
│   ├── ConsumerWithNothing.java
│   └── ProducerWithInterface.java
└── interfaze
   ├── IConsumer.java
   └── IProducer.java
我们直接使用ClassPathScanningCandidateComponentProvider扫描spring.study.componentprovider.bean包下的class:

ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); // 不使用默认的TypeFilter
provider.addIncludeFilter(new AnnotationTypeFilter(Consumer.class));
provider.addIncludeFilter(new AssignableTypeFilter(IConsumer.class));
Set<BeanDefinition> beanDefinitionSet = provider.findCandidateComponents("spring.study.componentprovider.bean");

这里扫描出来的类只有2个,分别是ConsumerWithConsumerAnnotation(被@Consumer注解修饰)和ConsumerWithInterface(实现了IConsumer接口)。ConsumerWithComponentAnnotation使用@Component注解,ConsumerWithNothing没实现任何借口,没使用任何注解,ProducerWithInterface实现了IProducer接口;所以这3个类不会被识别。

如果我们要自定义ComponentProvider,继承ClassPathScanningCandidateComponentProvider类即可。

RepositoryComponentProvider这个类是SpringData模块提供的,继承自ClassPathScanningCandidateComponentProvider,主要是为了识别SpringData相关的类。

它内部定义了一些自定义TypeFilter,比如InterfaceTypeFilter(识别接口的TypeFilter,目标比较是个接口,而不是实现类)、AllTypeFilter(保存存储TypeList集合,这个集合内部所有的TypeFilter必须全部满足条件才能被识别)等。

参考:Spring自定义类扫描器

Spring自定义类扫描器 ClassPathScanningCandidateComponentProvider的更多相关文章

  1. spring自定义类中@AutoWired标识的元素注入为null

    最近在做项目的时候,发现程序运行的时候有一个nullpointer exception,一脸懵逼因为感觉程序没什么逻辑.后来发现是因为new出来的component不会自动注入它的元素. 现象:@Co ...

  2. spring boot自定义类配置绑定在配置文件中自动提示

    在spring boot的日常使用中,我们可能需要使用配置绑定的方式动态配置自定义类的成员变量. 这个时候,我们在配置文件中配置spring默认已有的配置时,只需要输入部分关键字即可自动提示,如下图: ...

  3. 深入Spring:自定义注解加载和使用

    前言 在工作中经常使用Spring的相关框架,免不了去看一下Spring的实现方法,了解一下Spring内部的处理逻辑.特别是开发Web应用时,我们会频繁的定义@Controller,@Service ...

  4. spring自定义schema学习

    [转载请注明作者和原文链接,欢迎讨论,相互学习.] 一.前言 1. 最近在学习dubbo,里边很多如provider.consumer.registry的配置都是通过spring自定义Schema来实 ...

  5. 自定义session扫描器

    为何要自定义session扫描器 由于服务器来管理session的销毁不怎么靠谱,因此很多网站都会自己定义一个session扫描器来管理session的创建和销毁. 实现思路 首先,创建一个sessi ...

  6. spring 自定义标签的实现

    在我们进行Spring 框架开发中,估计用到最多的就是bean 标签吧,其实在Spring中像<mvc/><context/>这类标签以及在dubbo配置的标签都是属于自定义的 ...

  7. spring基础---->spring自定义初始化(二)

    这里新增了对ref属性的支持,并且过滤了已经解析的元素.人生有两个词很棒,一言不合和不提也罢. spring自定义对ref属性支持 项目的结构如下:新增一个ThirdBean类,修改了ParseXml ...

  8. spring基础---->spring自定义标签(一)

    Spring具有一个基于架构的扩展机制,可以使用xml文件定义和配置bean.本博客将介绍如何编写自定义XML bean的解析器,并用实例来加以说明.其实我一直相信 等你出现的时候我就知道是你. Sp ...

  9. spring自定义标签之 自我实现

     引言: 最近心情比较难以平静,周末的两天就跑出去散心了,西湖边上走走,看日落,还是不错的.回来博客上发现,在自定义标签上,最后一步实现忘记加上了.其实,人生的路程中,我们总是实现着自我的价值,让自己 ...

随机推荐

  1. 设计模式之Factory工厂模式的好处

    最最直观的好处就是吹牛逼,看着要比普通创建对象要屌 好看 一般情况下,我们创建对象使用的是new. Sample sample=new Sample(); 然而,实际情况会比这样复杂的多,比如说 Sa ...

  2. arcgis api 3.x for js 入门开发系列十三地图最短路径分析(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  3. 从.Net到Java学习第十篇——Spring Boot文件上传和下载

    从.Net到Java学习系列目录 图片上传 Spring Boot中的文件上传就是Spring MVC中的文件上传,将其集成进来了. 在模板目录创建一个新的页面 profile/uploadPage. ...

  4. asp.net core根据用户权限控制页面元素的显示

    asp.net core根据用户权限控制页面元素的显示 Intro 在 web 应用中我们经常需要根据用户的不同允许用户访问不同的资源,显示不同的内容,之前做了一个 AccessControlHelp ...

  5. linux下可执行bin程序提示not found/no such file or directory/not executable

    我们经常在执行二进制bin程序时,会遇到提示not found/no such file or directory/not executable等错误信息,在什么情况下会出现这种问题呢,我们一起罗列下 ...

  6. UDK命令

    UDK命令行参数与控制台命令都是大小写不敏感的 命令行  udn中文  udn英文 全词大小写匹配,正则表达式,在c++代码中搜索减号开头的命令行参数(如:-BENCHMARK.-onethread等 ...

  7. 在Windows 10上利用seafile搭建个人云服务

    参考seafile官方文档 安装Python 2.7.11 32位版 下载地址:https://www.python.org/downloads/release/python-2711/ 选择 32位 ...

  8. Linux之Samba部署

    1.Samba介绍 Samba 是在 Linux 和 UNIX 系统上实现 SMB 协议的一个免费软件,由服务器及客户端程序构成,SMB(Server Messages Block,信息服务块)是一种 ...

  9. LeetCode算法题-Base 7(Java实现)

    这是悦乐书的第247次更新,第260篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第114题(顺位题号是504).给定一个整数,返回其基数为7的字符串表示.例如: 输入: ...

  10. Thread中yield方法

    先上一段代码 public class YieldExcemple { public static void main(String[] args) { Thread threada = new Th ...