Spring 案例

1. (策略模式)一个接口多个实现类,如何根据外部条件来实时替换具体实现类

  • 涉及的ApplicationContext的方法,语法如下: ``` Map getBeansOfType(Class var1) throws BeansException;
  1. - 该方法用于获取 Spring 容器中指定类型的所有 JavaBean 对象。
  2. 1. 传入一个接口的Class 类型,获取这个class 的所有具体实现,不包括抽象类
  3. 1. 还可以将 applicationContext 单独设置一个值,写成一个工具类,结合ApplicationContext 类的其他方法,比如: getBean(String var1)
  4. ### 1.1. 案例1:网络资料案例(待整理与优化)
  5. #### 1.1.1. 需求
  6. 定义了一个接口,来对外提供服务,这个接口下的方法不能随便改变,而接口有一系列实现,且实现还在不断添加,如何在传入外部不同的条件下,实现实时更换接口的实现类
  7. #### 1.1.2. 实现步骤
  8. 1. 任何类实现 ApplicationContextAware 接口,实现setApplicationContext 方法,就会在启动时,向实现类的实现方法注入applicationContext对象。定义工具类,使用枚举,将所有实现类使用map进行封装

package com.util;

import com.service.TestService; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component;

import java.util.HashMap; import java.util.Map;

@Component public class TestServiceFactory implements ApplicationContextAware {

private static Map<TypeEnum, TestService> testServiceMap;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    Map<String,TestService> map = applicationContext.getBeansOfType(TestService.class);
    testServiceMap = new HashMap<>();
    map.forEach((key,value) -> testServiceMap.put(value.getCode(),value));
}

public TestService getTestService(TypeEnum typeEnum) {
    return testServiceMap.get(typeEnum);
}

}


2. 可以在接口中加一个getCode方法,实现类实现这个方法,然后返回一个定义的枚举类型,然后将getBeansOfType获得map进行转换
- 定义枚举

package com.util;

public enum TypeEnum { impl1, impl2 }


- 定义接口

package com.service;

import com.util.TypeEnum; import org.springframework.stereotype.Service;

@Service public interface TestService { public TypeEnum getCode(); public String test(); }


- 定义对应类型的实现类

package com.service.impl;

import com.service.TestService; import com.util.TypeEnum; import org.springframework.stereotype.Service;

@Service public class TestServiceImpl1 implements TestService { @Override public TypeEnum getCode() { return TypeEnum.impl1; }

@Override
public String test() {
    return this.toString();
}

}

package com.service.impl;

import com.service.TestService; import com.util.TypeEnum; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service;

@Service public class TestServiceImpl2 implements TestService { @Override public TypeEnum getCode() { return TypeEnum.impl2; }

@Override
public String test() {
    return this.toString();
}

}


- 定义controller类

package com.controller;

import com.util.TestServiceFactory; import com.util.TypeEnum; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.service.TestService;

import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;

@Controller @RequestMapping(“test”) public class TestController {

@Autowired
TestServiceFactory testServiceFactory;

private TestService testService;

@ResponseBody
@RequestMapping("test")
    public String test(HttpServletRequest request, HttpServletResponse response){
    String type = request.getParameter("type");
    testService = getTestService(type);
    return testService.test();
}

public TestService getTestService(String type) {
    TypeEnum typeEnum = null;
    if(type.equals("1")) typeEnum = TypeEnum.impl1;
    if(type.equals("2")) typeEnum = TypeEnum.impl2;
    return testServiceFactory.getTestService(typeEnum);
}

}

### 1.2. 案例2:个人项目MoonSystem项目
#### 1.2.1. 需求
定义一个公共查询数据字典查询接口,该接口有多个不同的实现类,根据请求参数中字典类型,调用不同的实现类中的查询不同的数据转换成数据字典返回
#### 1.2.2. 实现步骤

1. 定义字典枚举类,设置类型字符串值

package com.moon.system.common.enums;

/**

  • 数据字典类型枚举 */ public enum DictType {

    JAV_CATEGORY(“JAV_CATEGORY”), JAV_STORAGE(“JAV_STORAGE”);

    private String type;

    DictType(String type) {

     this.type = type;
    

    } }


2. 自定义数据字典注解,用于标签查询字典接口的相关实现类的名称

package com.moon.system.common.annotaion;

import com.moon.system.common.enums.DictType;

import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;

/**

  • 数据字典注解
  • @description 使用该注解的类,里面都有查询所有数据做为数据字典的方法 / @Target(ElementType.TYPE) // 标识注解使用范围:用在类和接口上 @Retention(RetentionPolicy.RUNTIME) // 标识注解的生命周期(有效作用范围) @Documented // 注解会出现在帮忙文档(javadoc)中 @Inherited // 该注解可以被子类继承 public @interface Dict { /*
    • 定义该类的数据字典类型名称 *
    • @return 字典类型名称枚举 */ DictType value(); }

3. 定义yml配置文件,设置指定需要扫描的包路径

存放自定义配置(属性),用于javMain module存放一些公共的配置参数

params: dictModule: dictScanBasePackages: com.moon.jav.service


4. 创建数据字典处理的接口,定义一个查询数据字典方法

package com.moon.system.api.common;

import com.moon.system.common.model.response.DictVO;

import java.util.List;

/**

  • 数据字典处理接口 / public interface DictHandlerApi { /*
    • 从缓存中获取所有分类 *
    • @return 数据字典集合 */ List getDictList(); }

5. 创建两个数据字典处理接口实现类,使用`@Dict`自定义注解标识实现类的名称

@Service(“JavCategoryService”) @Dict(DictType.JAV_CATEGORY) public class JavCategoryServiceImpl implements JavCategoryService, DictHandlerApi { ……

/**
 * 从缓存中获取所有分类
 *
 * @return 数据字典集合
 */
@Override
public List<DictVO> getDictList() {
    // 查询redis中JavCategory分类key是否过期时间
    Long keyTime = redisService.getExpire(REDIS_DICT_KEY, TimeUnit.MINUTES);
    List<DictVO> categoryList;
    // 判断key是否过期
    if (keyTime > 0) {
        // key 未过期,获取数据
        categoryList = (List<DictVO>) redisService.get(REDIS_DICT_KEY);
    } else {
        // key已过期,查询数据库所有分类
        categoryList = javCategoryMapper.getAllCategoryDict(DictType.JAV_CATEGORY);
        // 设置数据到redis
        redisService.set(REDIS_DICT_KEY, categoryList, REDIS_KEY_TIMEOUT, TimeUnit.MINUTES);
    }
    return categoryList;
}

}

```
@Service("JavStorageService")
@Dict(DictType.JAV_STORAGE)
public class JavStorageServiceImpl implements JavStorageService, DictHandlerApi {
    ......

    /**
     * 从缓存中获取所有存储位置
     *
     * @return 数据字典集合
     */
    @Override
    public List<DictVO> getDictList() {
        // 查询redis中JavStorage存储位置数据key是否过期时间
        Long keyTime = redisService.getExpire(REDIS_DICT_KEY, TimeUnit.MINUTES);
        List<DictVO> storageList;
        // 判断key是否过期
        if (keyTime > 0) {
            // key 未过期,获取数据
            storageList = (List<DictVO>) redisService.get(REDIS_DICT_KEY);
        } else {
            // key已过期,查询数据库所有存储位置
            storageList = javStorageMapper.getAllStorageDict(DictType.JAV_STORAGE);
            // 设置数据到redis
            redisService.set(REDIS_DICT_KEY, storageList, REDIS_KEY_TIMEOUT, TimeUnit.MINUTES);
        }
        return storageList;
    }
}
  1. 创建DictHandler类,注册到spring容器中,在类加载到Spring容器前,扫描指定的包下所有@Dict注解的类,将存储数据字典类型与对应的字典实例对象放入的map中 ``` package com.moon.jav.handler;

import com.google.common.collect.Maps; import com.moon.system.api.common.DictHandlerApi; import com.moon.system.common.annotaion.Dict; import com.moon.system.common.enums.DictType; import com.moon.system.utils.ClassScaner; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct; import java.util.Map;

/**

  • 数据字典接口相关实现的处理类 *
  • @author MoonZero
  • @version 1.0
  • @date 2019-9-15 16:02
  • @description */ @Component public class DictHandler implements ApplicationContextAware {

    / 注入配置文件中需要扫描的包路径 / @Value(“${params.dictModule.dictScanBasePackages}”) private String basePackages;

    /**

    • 存储数据字典类型与对应的字典实例对象的map *
    • Maps.newHashMapWithExpectedSize(DictType.values().length);这里使用了google的一个工具类

    • 初始化一个大小合适的map集合,避免在向集合添加元素的时候,因为大小不合适而resize,

    • 每次resize都得执行以下步骤:再次去分配空间,再次去计算所以元素的hashcode,再次根据hashcode计算数组的分配位置,然后数组拷贝。
    • 这样就可以大大提升 在使用hashmap时候的性能。和不必要的空间浪费。

    • 注:因为字典的类型名称都定义了一个枚举类,因为类实例的名称不能重复,所以枚举的长度就等于将会创建的map的个数大小 */ private Map handlerMap = Maps.newHashMapWithExpectedSize(DictType.values().length);

      /**

    • 根据字典类型获取DictHandlerApi字典接口对应的实例对象 *
    • @param dictType 数据字典类型
    • @return 对应的字典 */ public DictHandlerApi getDictHandlerInstance(DictType dictType) { return handlerMap.get(dictType); }

      /**

    • 在类加载到Spring容器前,扫描指定的包下所有@Dict注解的类,将存储数据字典类型与对应的字典实例对象放入的map中 */ @PostConstruct // PostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。此方法必须在将类放入服务之前调用。 public void register() { // 使用类扫描工具类,查询指定的包下所有带有@Dict注解的类,遍历所以class对象 ClassScaner.scan(basePackages, Dict.class).forEach(clazz -> {

       // 获取注解中定义的DictType名称值,用作存储实例对象map的key值
       DictType dictType = clazz.getAnnotation(Dict.class).value();
      
       // 根据字节码对象获取容器中相关的bean对象
       Map<String, ?> beansOfType = context.getBeansOfType(clazz);
       // 迭代bean对象
       for (Object value : beansOfType.values()) {
           // 将注解中DictType名称值做为key,class对象做为value,存储到Map中
           handlerMap.put(dictType, (DictHandlerApi) value);
       }
      

      }); }

      / 注入spring 容器ApplicationContext对象 / private ApplicationContext context;

      @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } }


7. 创建controller,定义查询的方法。注入字典处理类,根据请求参数中的字典类型值,生成不同的查询字典实现类,执行不同的查询的逻辑

package com.moon.jav.controller;

import com.moon.jav.handler.DictHandler; import com.moon.system.api.common.DictHandlerApi; import com.moon.system.api.jav.CommonControllerApi; import com.moon.system.common.enums.DictType; import com.moon.system.common.model.dto.DictDTO; import com.moon.system.common.model.response.CommonCode; import com.moon.system.common.model.response.DictVO; import com.moon.system.common.model.response.QueryResult; import com.moon.system.common.model.response.QueryResultResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList; import java.util.List;

/**

  • 通用请求处理控制层 */ @RestController @RequestMapping(“/common”) public class CommonController implements CommonControllerApi {

    / 注入字典处理类 / @Autowired private DictHandler dictHandler;

    /**

    • 根据请求参数中的字典分类,查询对应的数据字典集合
    • 因为前端直接上送数组对象后端不能直接使用集合接收,需要使用包装类DTO定义一个集合的属性来,前端发送POST请求,用@RequestBody来接收 *
    • @return 字典响应对象 */ @Override @PostMapping(“/getDictList”) public QueryResultResponse getDictList(@RequestBody DictDTO dictDTO) { // 定义返回的集合 List dictVOList = new ArrayList<>(); // 获取请求查询的数据字典类型集合 List dictTypeList = dictDTO.getDictTypeList();

      // 判断字典类型集合是否为空 if (!CollectionUtils.isEmpty(dictTypeList)) {

       dictTypeList.forEach(dictType -> {
           // 调用字典处理类,根据字典类型获取DictHandlerApi字典接口对应的实例对象
           DictHandlerApi dictHandlerInstance = dictHandler.getDictHandlerInstance(dictType);
           // 调用实例对象的查询数据字典方法
           List<DictVO> dictList = dictHandlerInstance.getDictList();
           // 将查询的内容增加到返回的集合中
           dictVOList.addAll(dictList);
       });
      

      }

      // 返回数据 QueryResult queryResult = new QueryResult<>(); queryResult.setRows(dictVOList); queryResult.setTotal(dictVOList.size());

      return new QueryResultResponse(CommonCode.SUCCESS, queryResult); } }

## 2. 自定义扫描器

> 此示例源码位置:spring-note\spring-analysis-note\spring-source-study-2021\00-spring-extension\custom-scanner

> - 其他相同的示例(spring注解驱动开发案例工程)源码位置:spring-note\spring-analysis-note\spring-sample-annotation\07-annotation-import-importbeandefinitionregistrar
> - 相应笔记的详见《02-Spring注解汇总.md》中的《自定义 ImportBeanDefinitionRegistrar》章节


### 2.1. 需求

- 案例实现的需求:如果出现大量类需要注册到Spring进行管理,但又不想每个类上都加上`@Component`等注解,实现指定包的路径即可将此包下所有bean扫描到并且实例化
- 实现思路:可以通过实现`ImportBeanDefinitionRegistrar`(或者`ImportSelector`)接口来批量注册bean对象到容器中,再使用`@Import`注解引入其实现类即可
### 2.2. 实现步骤
准备工作:创建一些测试使用的实体类
#### 2.2.1. 创建自定义扫描器注解

package com.moon.spring.extenstion.annotation;

import com.moon.spring.extenstion.registrar.BeansScannerRegistrar; import com.moon.spring.extenstion.registrar.CustomBeanDefinitionScanner; import org.springframework.context.annotation.Import; import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;

/**

  • 自定义实体扫描注解,用于指定扫描的包路径 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(BeansScannerRegistrar.class) // 通过@Import注解导入自定义包扫描的处理逻辑类 public @interface BeansScanner {

    @AliasFor(“basePackages”) String[] value() default {};

    @AliasFor(“value”) String[] basePackages() default {};

    Class<? extends Annotation> annotationClass() default Annotation.class; }

#### 2.2.2. 创建自定义扫描器
自定义扫描器,继承Spring框架的扫描器`ClassPathBeanDefinitionScanner`,重写`doScan`方法,可以对其进行扩展(_但本示例没有做扩展_)

package com.moon.spring.extenstion.registrar;

import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;

import java.util.Set;

/**

  • 自定义类扫描器,继承Spring框架的ClassPathBeanDefinitionScanner */ public class CustomBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

    public CustomBeanDefinitionScanner(BeanDefinitionRegistry registry) {

     super(registry, false);
    

    }

    // 重写扫描的方法,即可以做相应的扩展,但此示例暂无做扩展 @Override protected Set doScan(String… basePackages) {

     return super.doScan(basePackages);
    

    } }

#### 2.2.3. 创建自定义注册器
自定义注册器,须实现 `ImportBeanDefinitionRegistrar` 接口,在`registerBeanDefinitions`方法中实现注册的相关逻辑

package com.moon.spring.extenstion.registrar;

import com.moon.spring.extenstion.annotation.BeansScanner; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.List;

/**

  • 自定义 bean 注册器,实现 ImportBeanDefinitionRegistrar 接口 */ public class BeansScannerRegistrar implements ImportBeanDefinitionRegistrar {

    /**

    • 注册 BeanDefinition 的处理逻辑 *
    • @param importingClassMetadata 注解元数据
    • @param registry BeanDefinition 注册中心 */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 是否扫描所有类标识 boolean scanAllBeans = true;

      // 获取自定义的扫描器注解 AnnotationAttributes annotationAttributes = AnnotationAttributes

           .fromMap(importingClassMetadata.getAnnotationAttributes(BeansScanner.class.getName()));
      

      // 创建自定义包扫描器(其实没有做扩展) CustomBeanDefinitionScanner scanner = new CustomBeanDefinitionScanner(registry);

      // 判断自定义BeansScaner注解annotationClass属性 Class<? extends Annotation> clazz = annotationAttributes.getClass(“annotationClass”); // 判断是否设置扫描指定的注解,如有,则设置全部类扫描标识为false if (!Annotation.class.equals(clazz)) {

       scanAllBeans = false;
       scanner.addIncludeFilter(new AnnotationTypeFilter(clazz));
      

      }

      // 分别获取value与basePackages属性,收集所有配置的包路径 List basePackages = new ArrayList<>(); for (String pkg : annotationAttributes.getStringArray(“value”)) {

       if (StringUtils.hasText(pkg)) {
           basePackages.add(pkg);
       }
      

      } for (String pkg : annotationAttributes.getStringArray(“basePackages”)) {

       if (StringUtils.hasText(pkg)) {
           basePackages.add(pkg);
       }
      

      }

      // 判断是否整个包扫描 if (scanAllBeans) {

       // 方法的入参TypeFilter是函数式接口,直接使用lambda表达,直接返回true就是扫描所有的类
       scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
      

      }

      // 扫描包 scanner.doScan(StringUtils.toStringArray(basePackages)); } }


> 示例中也可以直接使用Spring提供的包扫描器


#### 2.2.4. spring的配置类中使用
在配置类中,标识自定义扫描器注解

package com.moon.spring.extenstion.config;

import com.moon.spring.extenstion.annotation.BeansScanner;

/**

  • Spring 项目的配置类 */ // 标识自定义扫描规则注解,指定要批量扫描的包路径 @BeansScanner(“com.moon.spring.extenstion.bean”) public class SpringConfiguration { }
#### 2.2.5. 测试

@Test public void testBeansScanner() { // 加载spring配置类 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class); // 输出容器中所有实例 for (String beanName : context.getBeanDefinitionNames()) { Object bean = context.getBean(beanName); System.out.println(bean); } }

``` 04-Spring案例 - 图1