一、背景描述 我构想了一个类似于mybatis的简化场景如下:
假设当前我有一个组件,名字是Mine。其功能是读取XML文件封装为内存对象,并提供一些操作,如读写等。现在我想通过Spring集成该组件。达到通过自定义注解的属性指定其绑定的XML文件,然后通过FactoryBean解析生成真正的目标对象,然后被纳入Spring的Bean管理中。使其可以通过Spring的注解(如Autowire等)注入到其他Bean里。
二、实践 1. 流程如下图 比较简单,不做解释
2. 具体代码 Ø EnableMineScan.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MineBeanDefinitionRegistrar.class) public @interface EnableMineScan { String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; }
Ø MineBeanDefinitionRegistrar.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class MineBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar , ResourceLoaderAware { private ResourceLoader resourceLoader; @Override public void setResourceLoader (ResourceLoader resourceLoader) { this .resourceLoader = resourceLoader; } @Override public void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { MineBeanDefinitionScanner scanner = new MineBeanDefinitionScanner (registry); if (resourceLoader != null ) { scanner.setResourceLoader(resourceLoader); } AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap( importingClassMetadata.getAnnotationAttributes(EnableMineScan.class.getName())); List<String> basePackages = new ArrayList <>(); for (String pkg : annoAttrs.getStringArray("value" )) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (String pkg : annoAttrs.getStringArray("basePackages" )) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses" )) { basePackages.add(ClassUtils.getPackageName(clazz)); } scanner.doScan(StringUtils.toStringArray(basePackages)); } }
Ø MineBeanDefinitionScanner.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class MineBeanDefinitionScanner extends ClassPathBeanDefinitionScanner { public MineBeanDefinitionScanner (BeanDefinitionRegistry registry) { super (registry, false ); } @Override public Set<BeanDefinitionHolder> doScan (String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super .doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn("No MineService was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration." ); } else { processBeanDefinitions(beanDefinitions); } return beanDefinitions; } private void processBeanDefinitions (Set<BeanDefinitionHolder> beanDefinitions) { for (BeanDefinitionHolder holder : beanDefinitions) { ScannedGenericBeanDefinition definition = (ScannedGenericBeanDefinition) holder.getBeanDefinition(); Map<String, Object> annotationAttributes = definition.getMetadata() .getAnnotationAttributes(MineBean.class.getName()); definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); definition.setBeanClass(MineFactoryBean.class); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); definition.getPropertyValues().add("xmlFile" , annotationAttributes.get("xmlFile" )); } } @Override protected boolean isCandidateComponent (AnnotatedBeanDefinition beanDefinition) { return isCandidateComponent(beanDefinition.getMetadata()); } @Override protected boolean isCandidateComponent (MetadataReader metadataReader) { return isCandidateComponent(metadataReader.getClassMetadata()); } private boolean isCandidateComponent (ClassMetadata classMetadata) { return classMetadata.isInterface() && classMetadata.isIndependent() && ArrayUtils.contains(classMetadata.getInterfaceNames(), AbstractMineService.class.getName()); } @Override protected boolean checkCandidate (String beanName, BeanDefinition beanDefinition) { if (super .checkCandidate(beanName, beanDefinition)) { return true ; } else { logger.warn("Skipping MineFactoryBean with name '" + beanName + "' and '" + beanDefinition.getBeanClassName() + ". Bean already defined with the same name!" ); return false ; } } }
Ø MineBean.java 1 2 3 4 5 public @interface MineBean { String value () default "" ; String xmlFile () default "" ; }
Ø AbstractMineService.java 1 2 3 4 public interface AbstractMineService { Object read (String xpath) ; void write (String xpath, Object obj) ; }
Ø MineFactoryBean.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class MineFactoryBean <T extends AbstractMineService > implements FactoryBean <T> { private Class<? extends AbstractMineService > mapperInterface; private String xmlFile; public MineFactoryBean () { } public MineFactoryBean (Class<? extends AbstractMineService> mapperInterface) { this .mapperInterface = mapperInterface; } @Override public T getObject () throws Exception { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); return (T) Proxy.newProxyInstance(contextClassLoader, new Class []{mapperInterface}, new MineBeanInvoker ()); } @Override public Class<?> getObjectType() { return mapperInterface; } @Override public boolean isSingleton () { return true ; } public String getXmlFile () { return xmlFile; } public void setXmlFile (String xmlFile) { this .xmlFile = xmlFile; } class MineBeanInvoker implements InvocationHandler { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { } } }
3. 使用示例 1 2 3 4 @EnableMineScan(basePackageClasses = SpringConfiguration.class) @Configuration public class SpringConfiguration {}
1 2 3 @MineFactoryBean(xmlFile = "any.xml") public interface MineDemoRunner extends AbstractMineService {}
1 2 3 4 5 6 7 8 9 10 11 @RunWith(value = SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class SpringMain { @Autowired private MineDemoRunner mineService; @Test public void test () { mineService.read("/root/sub" ) } }