文章内容
自定义注解,项目启动时进行注解扫描并存储,便于后面生成对象。类似于@Controller,@Service,自定义的原因是,整个项目会初始化很多流程,每个流程由好多个小模块(对象)串联起来。同一个类会被创建多个对象,被使用在不同的流程里面。以后会根据流程的配置对应切换对象。
1、项目结构

2、pom.xml依赖添加
01 02 03 04 05 06 07 08 09 10 11 12 | < parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > < version >1.5.9.RELEASE</ version > < relativePath /> <!-- lookup parent from repository --> </ parent > < dependencies > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > </ dependencies > |
3、配置文件application.yml
1 2 | server: port: 8083 |
4、注解类定义
01 02 03 04 05 06 07 08 09 10 11 12 | /** * 在类上面写 @Section(id = "xxx", remark = "xxx") * 类似于@Controller的使用 */ @Retention (RetentionPolicy.RUNTIME) @Documented @Target (ElementType.TYPE) public @interface Section { String id(); String remark(); String v() default "1" ; } |
01 02 03 04 05 06 07 08 09 10 11 | /** * 启动类上面@SectionScan(basePackages={"扫描的类路径"}) * 类似@ComponentScan(basePackages = {"com.ntan520.*"}) */ @Retention (RetentionPolicy.RUNTIME) @Documented @Target (ElementType.TYPE) @Import ({SectionRegister. class }) public @interface SectionScan { String[] basePackages() default {}; } |
5、启动类
1 2 3 4 5 6 7 8 9 | @SpringBootApplication @ComponentScan (basePackages = { "com.ntan520.*" }) // springboot 扫描指定包下面的注解 @SectionScan (basePackages={ "com.ntan520.test.section.*" , "com.ntan520.test.sectionV2.*" }) // 扫描2个指定包的下的注解 public class Application { public static void main(String[] args) { SpringApplication.run(Application. class , args); } } |
6、被扫描的类
1 2 3 4 | @Section (id = "dealParameter" , remark = "参数处理" ) public class DealParameterSection { } |
7、注册类
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | public class SectionRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { Logger LOG = LoggerFactory.getLogger(SectionRegister. class ); private static Map<String, Section> SECTION_MAP = new HashMap<String, Section>(); private ResourceLoader resourceLoader; private ClassLoader classLoader; private Environment environment; public void setSectionMap(Map<String, Section> sectionMap) { SECTION_MAP = sectionMap; } public void setEnvironment(Environment environment) { this .environment = environment; } public void setBeanClassLoader(ClassLoader classLoader) { this .classLoader = classLoader; } public void setResourceLoader(ResourceLoader resourceLoader) { this .resourceLoader = resourceLoader; } public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { logPackageScan(importingClassMetadata); registerSections(importingClassMetadata, registry); } private void logPackageScan(AnnotationMetadata metadata) { Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(SectionScan. class .getName(), true ); if (defaultAttrs != null && defaultAttrs.size() > 0 ) { LOG.info( "section package scan: " + buildPackages((String[]) defaultAttrs.get( "basePackages" ))); } } private String buildPackages(String[] basePackages) { if (basePackages == null || basePackages.length == 0 ) { return "null" ; } StringBuilder stringBuilder = new StringBuilder(); for (String s : basePackages) { stringBuilder.append(s).append( "," ); } stringBuilder.substring( 0 , stringBuilder.length() - 2 ); return stringBuilder.toString(); } public void registerSections(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader( this .resourceLoader); Map<String, Object> attrs = metadata.getAnnotationAttributes(SectionScan. class .getName()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(Section. class ); scanner.addIncludeFilter(annotationTypeFilter); Set<String> basePackages = getBasePackages(metadata); Map<String, Section> sectionMap = new HashMap<String, Section>(); for (String basePackage : basePackages) { Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>(); ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); try { // 这里特别注意一下类路径必须这样写 // 获取指定包下的所有类 basePackage = basePackage.replace( "." , "/" ); Resource[] resources = resourcePatternResolver.getResources( "classpath*:" + basePackage); MetadataReaderFactory metadata1 = new SimpleMetadataReaderFactory(); for (Resource resource : resources) { MetadataReader metadataReader = metadata1.getMetadataReader(resource); ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); candidates.add(sbd); } for (BeanDefinition beanDefinition : candidates) { String classname = beanDefinition.getBeanClassName(); // 扫描Section注解 Section s = Class.forName(classname).getAnnotation(Section. class ); if (s != null ) { sectionMap.put(classname, s); } } } catch (Exception e) { e.printStackTrace(); } } //使用容器存储扫描出来的对象(类全限定名:section对象) setSectionMap(sectionMap); } protected ClassPathScanningCandidateComponentProvider getScanner() { return new ClassPathScanningCandidateComponentProvider( false , this .environment) { @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { if (beanDefinition.getMetadata().isIndependent()) { if (beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().getInterfaceNames().length == 1 && Annotation. class .getName().equals(beanDefinition.getMetadata().getInterfaceNames()[ 0 ])) { try { Class<?> target = ClassUtils.forName(beanDefinition.getMetadata().getClassName(), SectionRegister. this .classLoader); return !target.isAnnotation(); } catch (Exception ex) { this .logger.error( "Could not load target class: " + beanDefinition.getMetadata().getClassName(), ex); } } return true ; } return false ; } }; } protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) { Map<String, Object> attributes = importingClassMetadata .getAnnotationAttributes(SectionScan. class .getCanonicalName()); Set<String> basePackages = new HashSet<String>(); for (String pkg : (String[]) attributes.get( "basePackages" )) { if (pkg != null && ! "" .equals(pkg)) { basePackages.add(pkg); } } if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName())); } return basePackages; } } |
8、总结
- 项目启动后,会自动扫描指定包下面带有@Section注解的类,放入到Map中,key是全限定性包名,value是一个Section对象(包含id属性值,remark备注,v版本号)
- Map内容如下:{com.ntan520.test.sectionV2.DealParameterSection=@com.ntan520.test.annotations.Section(v=2, id=dealParameter.2, remark=参数处理), com.ntan520.test.section.DealParameterSection=@com.ntan520.test.annotations.Section(v=1, id=dealParameter, remark=参数处理)}
- 将Map重新整理成为一个Map,把value中的id作为新key,其余属性重新封装到SectionConfigObj对象中。
- 初始化的时候会读取流程配置的id名(可替换),从Map中获得SectionConfigObj对象放入流程里