Spring実戦シリーズ(四)-ダイナミックインジェクションインタフェースBean
13193 ワード
「Springフレームワークについては、現実の会社では非常に広く使われていますが、業務の複雑さの違いから、Spring開発を利用してSpringを利用したIOCが多く、AOPでもあまり使われていないことがわかりました.それから、Springの生態は実はすでに全面的で、だからここでシリーズを開いてSpringが私たちに提供したいくつかの普段はあまり実用的ではない内容を研究します.
前回はBeanPostProcessorの基本的な使用を分析しました.次に、このクラスを使用して動的なインタフェース注入を実現する方法を分析します.例を説明します.BeetlSQLフレームワークでは、自動スキャン注入を使用する場合、スキャンするパッケージパスを構成し、そのパスの下で対応するDaoインタフェースクラスを宣言するだけです.これらのインタフェースクラスはBaseMapperインタフェースクラスをデフォルトで継承し、これらのDaoクラスを使用する場合は、タイプ注入(@Autowired)に直接従って使用できますが、これはMybatisのセットと似ていて、Spring自体のSpring-dataフレームワークとも似ています.これはフレームワークの開発によく使われています.では、私はこの部分の実現について説明します.この3つのフレームワークの具体的な実現には差があるかもしれません.興味のあるパートナーは自分でソースコードを見て、簡単な例で大まかな実現ロジックを説明します.
問題の説明:
Springフレームワークを継承し、カスタムインタフェース(UserMapper)を宣言し、インタフェースを変更して汎用インタフェースBaseMapperを継承します(汎用インタフェースBaseMapperにはデフォルトの実装クラスがあります)、タイプによってUserMapperクラスに注入され、Springフレームワークのコンテキストクラス(ApplicationContext実装クラス)によるgetBean()を実現します.メソッドは、内部で提供されるメソッドを呼び出すためにUserMapperクラスを取得します.
1、BaseMapperインタフェースクラスを宣言する
2、デフォルトのBaseMapper実装クラス:CustomBaseMapper
次にSpringのコアコードを継承します
3、まず、あるパスをスキャンするクラスを定義します.このクラスはClassPathBeanDefinitionScannerを継承します.カスタムスキャンクラス:DefaultClassPathScanner
4、コアのインタフェース実装クラス:BaseMapperFactoryBean
5、デフォルトのBaseMapperのFactoryBean-MapperManagerFactoryBeanを定義する
6、コアのjava動的代理クラス:MapperJavaProxy
7、呼び出し時のコア構成クラス:D e faultClassRegistryBeanFactory
8、呼び出しテスト
8.1、もしあなたがパッケージディレクトリにいるとしたら:colin.spring.basic.advanced.inject.daoでカスタムクラスUserMapperを宣言
8.2、構成クラスの宣言:C l a ssRegistryBeanScannerConfig
8.3、テストコール
まとめ:
ここでBeanPostProcessorインタフェースの呼び出しは、拡張または統合Springフレームワークを解決するためによく使用される高度なアプリケーションであるべきであり、その核心的な考え方は以下のステップに分けることができる.
1、カスタムインプリメンテーションクラスパススキャンクラスで、Springコンテナに注入すべきクラスを決定します.
2、Javaダイナミックエージェントを用いて、宣言インタフェースクラスへの注入を動的に実現する.
3、BeanDefinitionRegistryPostProcessorを実現し、Spring初期化の初期にSpring容器に導入したクラスをスキャンして注入する.
(PS:SpringのBeanDefinitionクラスについてある程度理解する必要があります.次のページではこのクラスを分析します)
前回はBeanPostProcessorの基本的な使用を分析しました.次に、このクラスを使用して動的なインタフェース注入を実現する方法を分析します.例を説明します.BeetlSQLフレームワークでは、自動スキャン注入を使用する場合、スキャンするパッケージパスを構成し、そのパスの下で対応するDaoインタフェースクラスを宣言するだけです.これらのインタフェースクラスはBaseMapperインタフェースクラスをデフォルトで継承し、これらのDaoクラスを使用する場合は、タイプ注入(@Autowired)に直接従って使用できますが、これはMybatisのセットと似ていて、Spring自体のSpring-dataフレームワークとも似ています.これはフレームワークの開発によく使われています.では、私はこの部分の実現について説明します.この3つのフレームワークの具体的な実現には差があるかもしれません.興味のあるパートナーは自分でソースコードを見て、簡単な例で大まかな実現ロジックを説明します.
問題の説明:
Springフレームワークを継承し、カスタムインタフェース(UserMapper)を宣言し、インタフェースを変更して汎用インタフェースBaseMapperを継承します(汎用インタフェースBaseMapperにはデフォルトの実装クラスがあります)、タイプによってUserMapperクラスに注入され、Springフレームワークのコンテキストクラス(ApplicationContext実装クラス)によるgetBean()を実現します.メソッドは、内部で提供されるメソッドを呼び出すためにUserMapperクラスを取得します.
1、BaseMapperインタフェースクラスを宣言する
public interface BaseMapper {
/**
* @param value
*/
public void add(String value);
/**
* @param key
*/
public void remove(String key);
}
2、デフォルトのBaseMapper実装クラス:CustomBaseMapper
public class CustomBaseMapper implements BaseMapper {
private final Logger logger = Logger.getLogger(this.getClass().getName());
private List dataList = new CopyOnWriteArrayList<>();
/**
* @param value
*/
@Override
public void add(String value) {
logger.info(" :" + value);
dataList.add(value);
}
/**
* @param key
*/
@Override
public void remove(String key) {
if (dataList.isEmpty())
throw new IllegalArgumentException("Can't remove because the list is Empty!");
}
}
次にSpringのコアコードを継承します
3、まず、あるパスをスキャンするクラスを定義します.このクラスはClassPathBeanDefinitionScannerを継承します.カスタムスキャンクラス:DefaultClassPathScanner
public class DefaultClassPathScanner extends ClassPathBeanDefinitionScanner {
private final String DEFAULT_MAPPER_SUFFIX = "Mapper";
public DefaultClassPathScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
private String mapperManagerFactoryBean;
/**
* - Bean
*
* @param basePackages
* @return
*/
@Override
protected Set doScan(String... basePackages) {
Set beanDefinitions = super.doScan(basePackages);
// ,
if (beanDefinitions.isEmpty()) {
logger.warn(" '" + Arrays.toString(basePackages) + "' Mapper, ");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
/**
* -
*/
protected void registerFilters() {
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
//TODO - ,
return className.endsWith(DEFAULT_MAPPER_SUFFIX);
}
});
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}
/**
* - isCandidateComponent
* :
* bean 。
* , 。
* 。
*
* @param beanDefinition
* @return
*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// ( - , )
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
/**
* - BeetlSqlFactoryBean Bean
*
* @param beanDefinitions
*/
void processBeanDefinitions(Set beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String mapperClassName = definition.getBeanClassName();
// , spring
definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClassName);
//
definition.getPropertyValues().add("mapperInterface", mapperClassName);
// BaseMapper
definition.getPropertyValues().add("mapperManagerFactoryBean", new RuntimeBeanReference(this.mapperManagerFactoryBean));
definition.setBeanClass(BaseMapperFactoryBean.class);
// Mapper
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
logger.info(" '" + holder.getBeanName() + "'.");
}
}
public void setMapperManagerFactoryBean(String mapperManagerFactoryBean) {
this.mapperManagerFactoryBean = mapperManagerFactoryBean;
}
}
4、コアのインタフェース実装クラス:BaseMapperFactoryBean
public class BaseMapperFactoryBean implements FactoryBean, InitializingBean, ApplicationListener, ApplicationContextAware {
/**
*
*/
private Class mapperInterface;
/**
* Spring
*/
private ApplicationContext applicationContext;
//
private BaseMapper mapperManagerFactoryBean;
public BaseMapperFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
// ,
return (T) Proxy.newProxyInstance(applicationContext.getClassLoader(), new Class[]{mapperInterface}, new MapperJavaProxy(mapperManagerFactoryBean, mapperInterface));
}
@Override
public Class> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
//TODO - mapperInterface
if (null == mapperInterface)
throw new IllegalArgumentException("Mapper Interface Can't Be Null!!");
}
/**
* Handle an application event.
*
* @param event the event to respond to
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
//TODO
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void setMapperInterface(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
public void setMapperManagerFactoryBean(BaseMapper mapperManagerFactoryBean) {
this.mapperManagerFactoryBean = mapperManagerFactoryBean;
}
}
5、デフォルトのBaseMapperのFactoryBean-MapperManagerFactoryBeanを定義する
public class MapperManagerFactoryBean implements FactoryBean, InitializingBean, ApplicationListener {
@Override
public BaseMapper getObject() throws Exception {
return new CustomBaseMapper();
}
@Override
public Class> getObjectType() {
return BaseMapper.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
//TODO
}
/**
* Handle an application event.
*
* @param event the event to respond to
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println(event.toString());
}
}
6、コアのjava動的代理クラス:MapperJavaProxy
public class MapperJavaProxy implements InvocationHandler {
private BaseMapper baseMapper;
private Class> interfaceClass;
public MapperJavaProxy(BaseMapper baseMapper, Class> interfaceClass) {
this.baseMapper = baseMapper;
this.interfaceClass = interfaceClass;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException("mapperInterface is not interface.");
}
if (baseMapper == null) {
baseMapper = new CustomBaseMapper();
}
return method.invoke(baseMapper, args);
}
}
7、呼び出し時のコア構成クラス:D e faultClassRegistryBeanFactory
public class DefaultClassRegistryBeanFactory implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor, BeanNameAware {
private String scanPackage;
private String beanName;
private String mapperManagerFactoryBean;
private ApplicationContext applicationContext;
public String getScanPackage() {
return scanPackage;
}
public void setScanPackage(String scanPackage) {
this.scanPackage = scanPackage;
}
public String getMapperManagerFactoryBean() {
return mapperManagerFactoryBean;
}
public void setMapperManagerFactoryBean(String mapperManagerFactoryBean) {
this.mapperManagerFactoryBean = mapperManagerFactoryBean;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
if (StringUtils.isEmpty(this.scanPackage)) {
throw new IllegalArgumentException("scanPackage can't be null");
}
String basePackage2 = this.applicationContext.getEnvironment().resolvePlaceholders(this.scanPackage);
String[] packages = StringUtils.tokenizeToStringArray(basePackage2, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
DefaultClassPathScanner defaultClassPathScanner = new DefaultClassPathScanner(beanDefinitionRegistry);
defaultClassPathScanner.setMapperManagerFactoryBean(mapperManagerFactoryBean);
defaultClassPathScanner.registerFilters();
defaultClassPathScanner.doScan(packages);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
}
8、呼び出しテスト
8.1、もしあなたがパッケージディレクトリにいるとしたら:colin.spring.basic.advanced.inject.daoでカスタムクラスUserMapperを宣言
public interface UserMapper extends BaseMapper {
}
8.2、構成クラスの宣言:C l a ssRegistryBeanScannerConfig
@Configuration
public class ClassRegistryBeanScannerConfig {
@Bean(name = "mapperManagerFactoryBean")
public MapperManagerFactoryBean configMapperManagerFactoryBean() {
MapperManagerFactoryBean mapperManagerFactoryBean = new MapperManagerFactoryBean();
return mapperManagerFactoryBean;
}
@Bean
public DefaultClassRegistryBeanFactory configDefaultClassRegistryBeanFactory() {
DefaultClassRegistryBeanFactory defaultClassRegistryBeanFactory = new DefaultClassRegistryBeanFactory();
defaultClassRegistryBeanFactory.setScanPackage("colin.spring.basic.advanced.inject.dao");
defaultClassRegistryBeanFactory.setMapperManagerFactoryBean("mapperManagerFactoryBean");
return defaultClassRegistryBeanFactory;
}
}
8.3、テストコール
public static void main(String[] args) {
AnnotationConfigApplicationContext acApplicationCOntext = new AnnotationConfigApplicationContext("colin.spring.basic.advanced.inject");
UserMapper userMapper = acApplicationCOntext.getBean(UserMapper.class);
userMapper.add("lalaldsf");
acApplicationCOntext.stop();
}
まとめ:
ここでBeanPostProcessorインタフェースの呼び出しは、拡張または統合Springフレームワークを解決するためによく使用される高度なアプリケーションであるべきであり、その核心的な考え方は以下のステップに分けることができる.
1、カスタムインプリメンテーションクラスパススキャンクラスで、Springコンテナに注入すべきクラスを決定します.
2、Javaダイナミックエージェントを用いて、宣言インタフェースクラスへの注入を動的に実現する.
3、BeanDefinitionRegistryPostProcessorを実現し、Spring初期化の初期にSpring容器に導入したクラスをスキャンして注入する.
(PS:SpringのBeanDefinitionクラスについてある程度理解する必要があります.次のページではこのクラスを分析します)