SpringBoot如何动态代理接口并注入到IOC容器?方式 一:BeanDefinitionRegistryPostProcessor
SpringBoot动态代理接口需求:在类似数据库等一些场景下,我们根据一定规则去操作数据,但是操作内容大部分内容是相同的,如果为每一个操作都写代码实现,会造成大量冗余。此时就可以对共性抽取,并通过动态代理的方式进行代码简化,比如Mybatis。需要将接口动态代理,同时注入到IOC容器。准备工作编写注解,用来识别哪些接口需要被动态代理(类似于@Repository)import java.lang
SpringBoot动态代理接口
需求:在类似数据库等一些场景下,我们根据一定规则去操作数据,但是操作内容大部分内容是相同的,如果为每一个操作都写代码实现,会造成大量冗余。此时就可以对共性抽取,并通过动态代理的方式进行代码简化,比如Mybatis。
需要将接口动态代理,同时注入到IOC容器。
准备工作
-
编写注解,用来识别哪些接口需要被动态代理(类似于
@Repository
)import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ClassName NeedProxy * @Description TODO * @Author Silwings * @Date 2021/3/7 15:57 * @Version V1.0 **/ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface NeedProxy { String value() default ""; }
-
编写公共接口(类似于
@Mapper
)/** * @ClassName Repository * @Description 泛型用来声明实体类类型(参考MyBatis) * @Author Silwings * @Date 2021/3/7 15:58 * @Version V1.0 **/ public interface Repository<T> { // 示例方法. String print(); }
-
编写公共接口的默认实现
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; /** * @ClassName DefaultRepository * @Description TODO * @Author Silwings * @Date 2021/3/7 16:00 * @Version V1.0 **/ public class DefaultRepository<T> implements Repository<T> , InvocationHandler { // 这里声明一个Class,用来接收接口声明的泛型实际类型的class,T是声明的实体类类型 private Class<T> clazz; public DefaultRepository(Class<T> interfaceType) { // 获取当前类上的泛型类型 ParameterizedType parameterizedType = (ParameterizedType) interfaceType.getGenericInterfaces()[0]; // 获取泛型对应的真实类型(泛型真实类型在很多场合需要使用) Type[] actualType = parameterizedType.getActualTypeArguments(); // 取数组的第一个,肯定是T的类型,即实体类类型(如果有多个,递增角标即可) this.clazz = (Class<T>) actualType[0]; } @Override public String print() { // 示例方法的默认实现 System.out.println(clazz); return clazz.getName(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Object 方法,走原生方法,比如hashCode() if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this,args); } // 其它走本地代理 return method.invoke(this, args); } }
-
默认实现完成后,需要使用
FactoryBean
来构建它.import org.springframework.beans.factory.FactoryBean; import java.lang.reflect.Proxy; /** * @ClassName RepositoryFactory * @Description FactoryBean是一种特殊的Bean,其返回的对象不是指定类的一个实例,其返回的是该工厂Bean的getObject方法所返回的对象 * @Author Silwings * @Date 2021/3/7 16:01 * @Version V1.0 **/ public class RepositoryFactory<T> implements FactoryBean<T> { /** * 构建DefaultRepository需要使用的参数 */ private Class<T> interfaceType; public RepositoryFactory(Class<T> interfaceType) { this.interfaceType = interfaceType; } @Override public T getObject() throws Exception { // 因为DefaultRepository需要Class<T>作为参数,所以该类包含一个Claa<T>的成员,通过构造函数初始化 return (T) Proxy.newProxyInstance(interfaceType.getClassLoader(), new Class[]{interfaceType}, new DefaultRepository<>(interfaceType)); } @Override public Class<?> getObjectType() { // 该方法返回的getObject()方法返回对象的类型,这里是基于interfaceType生成的代理对象,所以类型就是interfaceType return interfaceType; } }
-
至此准备工作完成.我们完成了标识需要代理接口的注解和动态代理的代码.接下来的问题是,如何获取到添加了
@NeedProxy
注解的接口,以及如何将其实例化并注入到IOC容器 -
注意事项: @NeedProxy注解并不是必须的,可以直接通过是否继承了Repository接口来判断.这里为了演示的更全面,使用了标识注解,在 一些业务情况下,也是需要添加一个注解来传递一些信息的.
核心工作
-
要完成接下来的工作需要实现三个接口
BeanDefinitionRegistryPostProcessor
,ResourceLoaderAware
,ApplicationContextAware
- BeanDefinitionRegistryPostProcessor
- 该接口是实现将代理类注入到容器最关键的部分.
- ResourceLoaderAware和ApplicationContextAware
- Spring中,以
Aware
结尾的接口都是感知接口,实现了这些接口的bean在满足条件时可以感知到自身的一些属性,比如ApplicationContextAware,体现在代码上就是在合适的时候Spring会调用所有实现了ApplicationContextAware接口的bean的setApplicationContext(ApplicationContext applicationContext)
方法,并将ApplicationContext 作为参数传递过来,那么使用者就可以在这个地方将ApplicationContext 进行存储. - 在我们的实现中,需要借助ResourceLoader和ApplicationContext,所以这里需要实现这两个接口,让我们方便的获取到这两个类的实例
- Spring中,以
- BeanDefinitionRegistryPostProcessor
-
编写实现类RepositoryScanner
import com.silwings.demo.anno.NeedProxy; import com.silwings.demo.repository.RepositoryFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.ClassUtils; import java.io.IOException; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; /** * @ClassName RepositoryScanner * @Description 扫描类并将其实例化注入IOC容器 * @Author Silwings * @Date 2021/3/7 16:09 * @Version V1.0 **/ public class RepositoryScanner implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, ApplicationContextAware { private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; private MetadataReaderFactory metadataReaderFactory; private ResourcePatternResolver resourcePatternResolver; private ApplicationContext applicationContext; @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { // 获取启动类所在包 List<String> packages = AutoConfigurationPackages.get(applicationContext); // 开始扫描包,获取字节码 Set<Class<?>> beanClazzSet = scannerPackages(packages.get(0)); for (Class beanClazz : beanClazzSet) { // 判断是否是需要被代理的接口 if (isNotNeedProxy(beanClazz)) { continue; } // BeanDefinition构建器 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz); GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition(); //在这里,我们可以给该对象的属性注入对应的实例。 definition.getConstructorArgumentValues() .addGenericArgumentValue(beanClazz); // 如果构造函数中不止一个值,可以使用这个api,指定是第几个参数 // definition.getConstructorArgumentValues() // .addIndexedArgumentValue(1,null); // 定义Bean工程(最终会用上面add的构造函数参数值作为参数调用RepositoryFactory的构造方法) definition.setBeanClass(RepositoryFactory.class); //这里采用的是byType方式注入,类似的还有byName等 definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE); String simpleName = beanClazz.getSimpleName(); // 首字母小写注入容器 simpleName = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1); beanDefinitionRegistry.registerBeanDefinition(simpleName, definition); } } /** * description: 是否是需要被代理的接口 * version: 1.0 * date: 2021/3/7 17:35 * author: Silwings * * @param beanClazz 类对象 * @return boolean 如果不是需要被代理的接口返回true */ private boolean isNotNeedProxy(Class beanClazz) { // 如果不是接口,或者其实现的接口小于等于0,或者其实现的第一个接口不是Repository,或者没有添加@NeedProxy注解,则说明不是需要被代理的接口 return !beanClazz.isInterface() || beanClazz.getInterfaces().length <= 0 || beanClazz.getInterfaces()[0] != Repository.class || null == AnnotatedElementUtils.findMergedAnnotation(beanClazz, NeedProxy.class); } /** * description: 根据包路径获取包及子包下的所有类 * version: 1.0 * date: 2021/3/7 17:34 * author: Silwings * * @param basePackage 需要扫描的包 * @return java.util.Set<java.lang.Class < ?>> */ private Set<Class<?>> scannerPackages(String basePackage) { Set<Class<?>> set = new LinkedHashSet<>(); // 此处固定写法即可,含义就是包及子包下的所有类 String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN; try { Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); for (Resource resource : resources) { if (resource.isReadable()) { MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); String className = metadataReader.getClassMetadata().getClassName(); Class<?> clazz; try { clazz = Class.forName(className); set.add(clazz); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } catch (IOException e) { e.printStackTrace(); } return set; } /** * description: 解析包名 * version: 1.0 * date: 2021/3/7 17:31 * author: Silwings * * @param basePackage 需要解析的路径 * @return java.lang.String 解析后的路径 */ private String resolveBasePackage(String basePackage) { // 将类名转换为资源路径 return ClassUtils.convertClassNameToResourcePath( // 解析占位符 this.applicationContext.getEnvironment().resolveRequiredPlaceholders(basePackage)); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { // 该方法空实现即可,用不到 } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
-
如此,工作就全部完成.
测试
-
此时,如果我们编写一个接口,继承
Repository
接口,并添加@NeedProxy
注解,不用编写实现类,按理说就可以直接在Controller中注入并使用了. -
编写接口继承
Repository
@NeedProxy public interface Test02 extends Repository<String> { }
-
编写
controller
@RestController @RequestMapping("/my") public class TestController { private Test02 test02; @Autowired public TestController(Test02 test02) { this.test02 = test02; } @GetMapping("/test01") public String test01() { Objects.requireNonNull(test02, "你的代码怎么又报错啦!"); System.out.println("测试 getClass() = " + test02.getClass()); System.out.println("测试 hashCode() = " + test02.hashCode()); return test02.print(); } }
-
请求
localhost:8080/my/test01
.-
控制台打印
测试 getClass() = class com.sun.proxy.$Proxy50 测试 hashCode() = 208067420 class java.lang.String
-
请求响应结果
java.lang.String
说明已经成功对接口进行代理并注入到了Spring容器,同时像hashCode()这种Object的方法也可以正常执行.
-
更多推荐
所有评论(0)