• Spring源码运用,扩展Spring小功能,扩展Feign接口,改为调用所有机器的服务。


    前言

    学习了Spring源码之后如何自己扩展spring的功能呢?今天就举几个实用的小例子

    运用一、扩展feign功能,由负载均衡调用单服务改为调用全部服务。

    需求与目标

    我们知道通过Feign调用服务时,会根据配置的负载均衡策略调用微服务中的一台机器,这也正是微服务的作用之一。
    但是此时我们有一个需求,通过Feign调用时需要调用所有服务下的机器接口,用于刷新服务器上的静态缓存。

    实现方案

    通过Spring提供的BeanPostProcessor接口在初始化完成后对bean进行改造。具体代码如下:

    1.新增实现类,实现BeanPostProcessor接口

    //这个类本身也要注册为一个Spring的bean,这样才会被Spring调用
    @Component
    public class FeignPostProcessor implements BeanPostProcessor {
    
        private Logger logger = LoggerFactory.getLogger(FeignPostProcessor.class.getSimpleName());
    
    	/**
    	* 微服务基于Eureka作为注册中心,由于要调用微服务的所有机器,
    	* 需要通过该服务获取所有的微服务,用于多机	器调用
    	*/
        @Resource
        private DiscoveryClient discoveryClient;
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            logger.info("自定义后置处理器接口,beanName:{},beanClass:{}",beanName,bean.getClass());
            FeignClient annotation = ExpressFeign.class.getAnnotation(FeignClient.class);
            Class<?> fallback = annotation.fallback();
    
    		//这里定义好自己需要处理的feign,我这里是ExpressFeign
            if(bean instanceof ExpressFeign ){
                if(fallback != null && bean.getClass().isAssignableFrom(fallback) ){
                    logger.info("bean:{},是熔断限流的实现",bean.getClass());
                    return bean;
                }
                //这里真正的实现多调用时采用JDK动态代理重新生成bean,重点看InvokeAllFeignInvocationHandler的invoke方法
                return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ExpressFeign.class},
                        new InvokeAllFeignInvocationHandler(discoveryClient));
            }
            return bean;
        }
    }
    
    • 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

    2. InvokeAllFeignInvocationHandler实现Feign接口多机器调用

    public class InvokeAllFeignInvocationHandler implements InvocationHandler {
    
        private static final String EXPRESS_SERVICE_NAME = "express-service";
    
        private DiscoveryClient discoveryClient;
    
        public InvokeAllFeignInvocationHandler(DiscoveryClient discoveryClient) {
            this.discoveryClient = discoveryClient;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            List<ServiceInstance> instances = discoveryClient.getInstances(EXPRESS_SERVICE_NAME);
            if(CollUtil.isEmpty(instances)){
                return null;
            }
            //这里暂定返回值为String,数组,实际上多接口调用时不应该有返回值,我们只关心是否调用成功即可。
            List<String> strings = new ArrayList<>();
            for (ServiceInstance instance : instances) {
            	//这里假设Feign接口的请求都是使用的@RequestMapping注解,其他的注解如:@GetMapping都是一样的。
                RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                String path = annotation.value()[0];
                String forObject = new RestTemplate().getForObject(instance.getUri() + path, String.class,args);
                strings.add(forObject);
            }
            return Arrays.toString(strings.toArray());
        }
    }
    
    • 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

    运用二、自己集成mybaits到Spring,实现Mapper接口注入Spring IOC容器。

    需求与目标

    我们大多数人都在Spring环境中用过Mybatis的功能,通常的使用方式是直接注入Mapper接口即可实现数据库的操作,例如:

    public interface UserMapper{
       List<User> selectAll();
    
    }
    
    public class TestService{
    	
    	@Resource
    	private UserMapper userMapper;
    	
    	public void test(){
    		userMapper.selectAll();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    上面代码中的UserMapper是一个接口,我们并没有为这个接口提供实现类,那么为什么能使用@Resrouce注解注入到Spring中实现数据库的操作呢?这就是我们本次学习需要了解的内容:如何将Mybatis的Mapper接口注入到Spring Ioc容器中。

    代码实现

    一:公共部分

    POM文件

    除了常规的Spring相关的依赖,Mysql驱动外,我们只导入Mybatis包。

    
    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatisartifactId>
        <version>3.5.3version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    在resources目录下创建Mybatis配置文件【mybatis-config.xml】
    
    
    <configuration>
    	
        <properties resource="config.properties"/>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                dataSource>
            environment>
        environments>
        <mappers>
            
            <package name="org.chenglj.mybatis.mapper"/>
            
        mappers>
    configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    创建SqlSessionFactory (org.apache.ibatis.session.SqlSessionFactory)
    @Configuration
    public class DataSourceConfig {
    
        /**
         * 配置Mybatis的SqlSessionFactory
         * @return
         * @throws IOException
         */
        @Bean
        public SqlSessionFactory sqlSessionFactory() throws IOException {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            return sqlSessionFactory;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    采用注解的方式启动spring,创建Spring注解文件
    import org.springframework.context.annotation.ComponentScan;
    
    @ComponentScan("org.chenglj.mybatis")
    public class MybatisSpringDemo {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    二、整合版本V1.0

    2.1 创建Mybatis接口
    
    public interface UserMapper2 {
    
    	//这里先使用Mybatis提供的注解方式实现sql语句,后面的版本调整为我们最常用的xml文件
        @Select("select * from user limit 10;")
        List<User> selectAll();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    2.2 通过FactoryBean 注册bean对象。
    //这里的@Component注解一定要加上,表示这个类本身也会注册到springIOC容器中,这样Spring才会回调getObject方法注册目标bean对象。
    @Component
    public class UserMapperFactoryBean implements FactoryBean<UserMapper2> {
    
        @Resource
        private SqlSessionFactory sqlSessionFactory;
    
        public UserMapperFactoryBean(SqlSessionFactory sqlSessionFactory) {
            this.sqlSessionFactory = sqlSessionFactory;
        }
    
        @Override
        public UserMapper2 getObject() throws Exception {
            return sqlSessionFactory.openSession().getMapper(UserMapper2.class);
        }
    
        @Override
        public Class<?> getObjectType() {
            return UserMapper2.class;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    2.3 测试
    
    public class MybatisSpringDemoTest {
    
        private static AnnotationConfigApplicationContext context ;
        static {
            context = new AnnotationConfigApplicationContext();
            context.register(MybatisSpringDemo.class);
        }
        
    	@Test
        public void testV1(){
            context.refresh();
            UserMapper2 bean = context.getBean(UserMapper2.class);
            System.out.println(bean.selectAll());
        }
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    三、整合版本V2.0

    对V1.0中我们注册UserMapper2.java的方式采用Spring的FactoryBean 有一个弊端,当我们项目中有多张表时就需要创建多个Factorybean的实现吗?显然这种方式不符合实际需求?(但却能帮我们很好理解Spring)。

    3.1.创建一个通用的FactoryBean,用来注册所有的Mybatis的Mapper接口。
    public class MapperFactoryBean implements FactoryBean {
    
    	//重点是这里,我们不再写死一个UserMapper2.java文件
        private Class<?> mapperClass;
    
        public MapperFactoryBean(Class<?> mapperClass) {
            this.mapperClass = mapperClass;
        }
    
        @Resource
        private SqlSessionFactory sqlSessionFactory;
    
        @Override
        public Object getObject() throws Exception {
            return sqlSessionFactory.openSession().getMapper(mapperClass);
        }
    
        @Override
        public Class<?> getObjectType() {
            return mapperClass;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    3.2 引入一个hutool工具包

    有什么用我们等下说到

    <dependency>
        <groupId>cn.hutoolgroupId>
         <artifactId>hutool-allartifactId>
         <version>5.7.13version>
     dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    3.3 测试(注册beanDefinition对象)
    public class MybatisSpringDemoTest {
    
        private static AnnotationConfigApplicationContext context ;
        static {
            context = new AnnotationConfigApplicationContext();
            context.register(MybatisSpringDemo.class);
        }
        
    	@Test
        public void testV2(){
        	//1.定义扫描的Mybaits的Mapper接口路径
            String mapperPackage = "org.chenglj.mybatis.mapper";
            //2.使用hutool工具包、扫描包下所有的class文件。
            Set<Class<?>> mapperClasses = ClassUtil.scanPackage(mapperPackage);
            
    	        for (Class<?> mapperClazz : mapperClasses) {
    	        	//3. 循环创建BeanDefinition对象
    	            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
    	                    .genericBeanDefinition(MapperFactoryBean.class)
    	                    .getBeanDefinition();
    	//这里通过MapperFactoryBean的有参构造器方法将class文件传入
    		            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapperClazz);
    	            
    	//4.为Spring上下文循环注册BeanDefition对象
    	            context.registerBeanDefinition(mapperClazz.getSimpleName(),beanDefinition);
    	        }
    	
    	        context.refresh();
    
    		//此时我们在UserMapper2的同级包下创建多个Mapper接口均可被注册到Spring中。
    		UserMapper2 userMapper2Bean = context.getBean(UserMapper2.class);
            System.out.println(userMapper2Bean.selectAll());
    
            StudentMapper studentMapper = context.getBean(StudentMapper.class);
            System.out.println(studentMapper.getNameByPrimaryKey(2));
    	    }
    
    • 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

    四、整合版本V3.0

    上面的V2.0版本中有一个问题,我们扫描包的路径(代码:String mapperPackage = "org.chenglj.mybatis.mapper";)是在代码中写死的,这样作为公共组件显示是不符合需求的。

    4.1实现spring的ImportBeanDefinitionRegistrar接口来注册beanDefition
    public class MybatisMapperImportBeanDefinitionRegistrar2 implements ImportBeanDefinitionRegistrar {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
    
            Map<String, Object> annotationAttributes = annotationMetadata
                    .getAnnotationAttributes(MybatisMapperScan.class.getName());
            String mapperPackage = (String)annotationAttributes.get("value");
    
            Assert.notNull(mapperPackage,"@MybatisMapperScan value can not be null");
    
    		//这里的代码就是将版本V2.0中的test代码移动到这里
            Set<Class<?>> mapperClasses = ClassUtil.scanPackage(mapperPackage);
    
            for (Class<?> mapperClazz : mapperClasses) {
                AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                        .genericBeanDefinition(MapperFactoryBean.class)
                        .getBeanDefinition();
                beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapperClazz);
                beanDefinitionRegistry.registerBeanDefinition(mapperClazz.getSimpleName(),beanDefinition);
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    4.2 自定义注解,通过@Import注解注入Spring容器
    //这里这里的Class是我们刚刚写的
    @Import(MybatisMapperImportBeanDefinitionRegistrar2.class)
    
    @Documented
    @Inherited
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MybatisMapperScan {
        String value();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    4.3 在注解类上使用
    @ComponentScan("org.chenglj.mybatis")
    @MybatisMapperScan("org.chenglj.mybatis.mapper")
    public class MybatisSpringImportDemo2 {
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    4.4 测试
    public class MybatisSpringDemoTest {
    
        private static AnnotationConfigApplicationContext context ;
        static {
            context = new AnnotationConfigApplicationContext();
            context.register(MybatisSpringDemo.class);
        }
        
    	@Test
        public void testV3(){
            context = new AnnotationConfigApplicationContext();
            context.register(MybatisSpringImportDemo2.class);
            context.refresh();
            
    		UserMapper2 userMapper2Bean = context.getBean(UserMapper2.class);
            System.out.println(userMapper2Bean.selectAll());
    
            StudentMapper studentMapper = context.getBean(StudentMapper.class);
            System.out.println(studentMapper.getNameByPrimaryKey(2));
        }
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    五、代码整合V4.0

    在V2.0和V3.0的整合中我们使用了hutool的工具包、通过扫描指定包下的所有class文件实现了Spring beanDefinition的注册,其实Spring本身有比较成熟的包扫描实现了

    5.1继承ClassPathBeanDefinitionScanner实现包扫描
    
    public class MybatisMapperScanner extends ClassPathBeanDefinitionScanner {
    
        private static Logger logger = LoggerFactory.getLogger(MybatisMapperScanner.class);
    
        public MybatisMapperScanner(BeanDefinitionRegistry registry) {
            super(registry);
        }
    
    
    
        /*@Override
        public int scan(String... basePackages) {
            int scan = super.scan(basePackages);
            logger.info("scan packages {} nums is {}",basePackages,scan);
            return scan;
        }*/
    /*
        方案一、在scan结束后循环修改BeanDefinition中的集合内容
        @Override
        protected Set doScan(String... basePackages) {
            Set beanDefinitionHolders = super.doScan(basePackages);
            for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
                beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
                beanDefinition.setBeanClassName(MapperFactoryBean.class.getName());
            }
            return beanDefinitionHolders;
        }
    */
        /**
         * 重写2个isCandidateComponent方法  是否候选的组件,我们只需要扫描接口,默认接口不会被扫描的
         * @param metadataReader
         * @return
         * @throws IOException
         */
        @Override
        protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
            return metadataReader.getClassMetadata().isInterface();
        }
    
        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            boolean anInterface = beanDefinition.getMetadata().isInterface();
            return anInterface;
        }
    
        /**
         * 方案二:重新注册beanDefinition方法,此处修改下beanDefinition的ClassName为我们自定义的MapperFactoryBean即可
         * @param definitionHolder
         * @param registry
         */
        /*@Override
        protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
            BeanDefinition beanDefinition = definitionHolder.getBeanDefinition();
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
            beanDefinition.setBeanClassName(MapperFactoryBean.class.getName());
            super.registerBeanDefinition(definitionHolder, registry);
        }*/
    
        /**
         * 重新方案三 推荐
         * @param beanDefinition
         * @param beanName
         */
        @Override
        protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
            //BeanClass和BeanClassName二则设置其一即可,Spring会优先使用beanClassName,注意BeanClassName一定要是Class的全称
            beanDefinition.setBeanClass(MapperFactoryBean.class);
            beanDefinition.setBeanClassName(MapperFactoryBean.class.getName());
            super.postProcessBeanDefinition(beanDefinition, beanName);
        }
    }
    
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    5.2 重写ImportBeanDefinitionRegistrar
    public class MybatisMapperImportBeanDefinitionRegistrarV4 implements ImportBeanDefinitionRegistrar {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
    
            Map<String, Object> annotationAttributes = annotationMetadata
                    .getAnnotationAttributes(MybatisMapperScanV4.class.getName());
            String mapperPackage = (String)annotationAttributes.get("value");
    
            Assert.notNull(mapperPackage,"@MybatisMapperScan value can not be null");
    
            ClassPathBeanDefinitionScanner mapperScan = new MybatisMapperScanner(beanDefinitionRegistry);
            // 【重点】我们只需要调用scan方法即可。
            mapperScan.scan(mapperPackage);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    5.3注解类上使用
    @Import(MybatisMapperImportBeanDefinitionRegistrarV4.class)
    @Documented
    @Inherited
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MybatisMapperScanV4 {
        String value();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    5.4 测试
    public class MybatisSpringDemoTest {
    
        private static AnnotationConfigApplicationContext context ;
        static {
            context = new AnnotationConfigApplicationContext();
            context.register(MybatisSpringDemo.class);
        }
        
    	@Test
        public void testV4(){
            context = new AnnotationConfigApplicationContext();
            context.register(MybatisSpringImportDemoV4.class);
            context.refresh();
            
    		UserMapper2 userMapper2Bean = context.getBean(UserMapper2.class);
            System.out.println(userMapper2Bean.selectAll());
    
            StudentMapper studentMapper = context.getBean(StudentMapper.class);
            System.out.println(studentMapper.getNameByPrimaryKey(2));
        }
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    github源码地址

    上述的整合mybatis的完整源码均在github:https://github.com/jurnea/my-batis

    • V1.0分支:mybatis-spring
    • V2.0分支:mybatis-spring-v2
    • V3.0分支:mybatis-spring-v3
    • V4.0分支:mybatis-spring-v4
  • 相关阅读:
    2023转行要趁早!盘点网络安全的岗位汇总
    从零开始学习Dubbo1——互联网项目目标与架构发展史
    PX4模块设计之二十四:内部ADC模块
    学习c#的第二十一天
    【多目标跟踪】 TrackFormer 耗时三天 单句翻译!!!
    java面试题含答案总结七
    JavaScript之while和do while循环的用法
    携职教育:最新人力资源管理师报考条件政策解析
    golang将pcm格式音频转为mp3格式
    pyqt6 vscode
  • 原文地址:https://blog.csdn.net/weixin_48470176/article/details/126690632