【SpringBoot技术指南】「开发实战系列」动态化Quartz任务调度机制+实时推送任务数据到前端

简介: 【SpringBoot技术指南】「开发实战系列」动态化Quartz任务调度机制+实时推送任务数据到前端

前提介绍


SpringBoot2.0整合quartz实现多定时任务动态配置,实现任务增删改,生成Cron表达式




动态化任务调度


添加依赖包


配置化相关的Maven的配置机制控制


<!-- quartz -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
复制代码



DynamicSchedulerConfig


动态化任务调度的配置服务类机制控制

import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
@Configuration
public class DynamicSchedulerConfig implements SchedulerFactoryBeanCustomizer{
  @Override
  public void customize(SchedulerFactoryBean schedulerFactoryBean) {
          schedulerFactoryBean.setStartupDelay(2); 
          schedulerFactoryBean.setAutoStartup(true);
          schedulerFactoryBean.setOverwriteExistingJobs(true);
        }
}
复制代码



application.yml配置


  • 这里是配置druid连接池,以下都是druid的配置信息
  • 配置相关的quartz任务调度的配置服务以及相关的数据库相关的维护操作机制控制
server:
    port: 8101
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource   
    url: jdbc:mysql://127.0.0.1:3306/task?useUnicode=true&characterEncoding=utf-8&useSSL=false
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
#quartz相关属性配置
 quartz:
    properties:
      org:
        quartz:
          scheduler:
            instanceName: clusteredScheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_
            isClustered: true
            clusterCheckinInterval: 10000
            useProperties: false
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
    #数据库方式
    job-store-type: jdbc
# mybatisplus配置
mybatis-plus:
  mapper-locations: classpath*:/mapper/**Mapper.xml
  #把xml文件放在com.XX.mapper.*中可能会出现找到的问题,这里把他放在resource下的mapper中
  typeAliasesPackage: com.task.entity
  #这里是实体类的位置,#实体扫描,多个package用逗号或者分号分隔
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false
logging:
  file: task-info.log
  level:
    com.task: debug
复制代码



TaskInfoService业务服务类


任务相关的实现接口


@Service
public class TaskInfoService extends IService<TaskInfoBO> {
    /**
    * @Title: getPageJob
    * @Description: TODO(查询定时任务,分页)
    * @param @param search
    * @param @return    参数
    * @return Map<String,Object>    返回类型
    * @throws
    */
    IPage<TaskInfoBO> getPageJob(Pageable pageable, MultiValueMap queryParam);
    /**
    * @Title: getPageJobmod
    * @Description: TODO(查询定时任务)
    * @param @return    参数
    * @return TaskInfoBO    返回类型
    * @throws
    */
    TaskInfoBO getPageJobmod();
    /**
    * @Title: addJob
    * @Description: TODO(添加任务)
    * @param @param jobClassName 任务路径名称
    * @param @param jobGroupName 任务分组
    * @param @param cronExpression cron时间规则
    * @param @throws Exception    参数
    * @return void    返回类型
    * @throws
    */
    void addJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception;
    /**
    * @Title: addJob
    * @Description: TODO(添加动态任务)
    * @param @param jobClassName 任务路径名称
    * @param @param jobGroupName 任务分组
    * @param @param cronExpression cron时间规则
    * @param @param jobDescription 参数
    * @param @param params
    * @param @throws Exception  参数说明
    * @return void    返回类型
    * @throws
    */
    void addJob(String jobClassName, String jobGroupName, String cronExpression, String jobDescription, Map<String, Object> params) throws Exception;
    /**
    * @Title: updateJob
    * @Description: TODO(更新定时任务)
    * @param @param jobClassName 任务路径名称
    * @param @param jobGroupName 任务分组
    * @param @param cronExpression cron时间规则
    * @param @throws Exception    参数
    * @return void    返回类型
    * @throws
    */
    void updateJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception;
    /**
    * @Title: deleteJob
    * @Description: TODO(删除定时任务)
    * @param @param jobClassName 任务路径名称
    * @param @param jobGroupName 任务分组
    * @param @throws Exception    参数
    * @return void    返回类型
    * @throws
    */
    void deleteJob(String jobClassName, String jobGroupName) throws Exception;
    /**
    * @Title: pauseJob
    * @Description: TODO(暂停定时任务)
    * @param @param jobClassName 任务路径名称
    * @param @param jobGroupName 任务分组
    * @param @throws Exception    参数
    * @return void    返回类型
    * @throws
    */
    void pauseJob(String jobClassName, String jobGroupName) throws Exception;
    /**
    * @Title: resumejob
    * @Description: TODO(恢复任务)
    * @param @param jobClassName 任务路径名称
    * @param @param jobGroupName 任务分组
    * @param @throws Exception    参数
    * @return void    返回类型
    * @throws
    */
    void resumejob(String jobClassName, String jobGroupName) throws Exception;
}
复制代码



TaskInfoServiceImpl业务服务类


任务相关的实现接口实现类

@Slf4j
@Service
@Transactional
public class TaskInfoServiceImpl  extends ServiceImpl<JobAndTriggerMapper, TaskInfoBO> implements TaskInfoService {
    @Autowired
    private Scheduler scheduler;
    @Override
    public IPage<TaskInfoBO> getPageJob(Pageable pageable, MultiValueMap queryParam) {
        IPage<TaskInfoBO> page = new Page<>(pageable.getPageNumber(), pageable.getPageSize());
        return baseMapper.getJobAndTriggerDetails(page);
    }
    @Override
    public TaskInfoBO getPageJobmod() {
        return baseMapper.getJobAndTriggerDto();
    }
    @Override
    public void addJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception {
        // 启动调度器
        scheduler.start();
        // 构建job信息
        JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass())
                .withIdentity(jobClassName, jobGroupName).build();
        // 表达式调度构建器(即任务执行的时间)
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
        // 按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
                .withSchedule(scheduleBuilder).build();
        try {
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            throw new Exception("创建定时任务失败");
        }
    }
    @Override
    public void addJob(String jobClassName, String jobGroupName, String cronExpression, String jobDescription,
                      Map<String, Object> params) throws Exception {
        // 启动调度器
        scheduler.start();
        // 构建job信息
        JobDetail jobDetail = JobBuilder.newJob(TaskInfoServiceImpl.getClass(jobClassName).getClass())
                .withIdentity(jobClassName, jobGroupName).withDescription(jobDescription).build();
        Iterator<Map.Entry<String, Object>> var7 = params.entrySet().iterator();
        while(var7.hasNext()) {
            Map.Entry<String, Object> entry = var7.next();
            jobDetail.getJobDataMap().put((String)entry.getKey(), entry.getValue());
        }
        // 表达式调度构建器(即任务执行的时间)
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
        // 按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = (CronTrigger)TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
                .withSchedule(scheduleBuilder).build();
        try {
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            throw new Exception("创建定时任务失败");
        }
    }
    @Override
    public void updateJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
            // 表达式调度构建器(动态修改后不立即执行)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            // 按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            // 按新的trigger重新设置job执行
            scheduler.rescheduleJob(triggerKey, trigger);
        } catch (SchedulerException e) {
            throw new Exception("更新定时任务失败");
        }
    }
    @Override
    public void deleteJob(String jobClassName, String jobGroupName) throws Exception {
        scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
        scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
        scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
    }
    @Override
    public void pauseJob(String jobClassName, String jobGroupName) throws Exception {
        scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
    }
    @Override
    public void resumejob(String jobClassName, String jobGroupName) throws Exception {
        scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
    }
    public static BaseJob getClass(String classname) throws Exception {
        Class<?> class1 = Class.forName(classname);
        return (BaseJob) class1.newInstance();
    }
}
复制代码

TaskInfoMapper

任务信息控制的数据访问层接口实现机制

public interface JobAndTriggerMapper extends BaseMapper<TaskInfoBO> {
    IPage<TaskInfoBO> getJobAndTriggerDetails(IPage<TaskInfoBO> page);
    TaskInfoBO getTaskInfoModel;
}
复制代码



TaskInfoMapper.xml


任务信息控制的数据访问层接口实现机制xml文件的配置机制控制

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.south.data.mapper.JobAndTriggerMapper">
    <select id="getJobAndTriggerDetails" resultType="com.data.vo.TaskInfoBO">
    SELECT
    jd.JOB_NAME AS jobName,
    jd.DESCRIPTION AS jobDescription,
    jd.JOB_GROUP AS jobGroupName,
    jd.JOB_CLASS_NAME AS jobClassName,
    t.TRIGGER_NAME AS triggerName,
    t.TRIGGER_GROUP AS triggerGroupName,
    FROM_UNIXTIME(t.PREV_FIRE_TIME/1000,'%Y-%m-%d %T') AS prevFireTime,
    FROM_UNIXTIME(t.NEXT_FIRE_TIME/1000,'%Y-%m-%d %T') AS nextFireTime,
    ct.CRON_EXPRESSION AS cronExpression,
    t.TRIGGER_STATE AS triggerState
    FROM
    qrtz_job_details jd
    JOIN qrtz_triggers t
    JOIN qrtz_cron_triggers ct ON jd.JOB_NAME = t.JOB_NAME
    AND t.TRIGGER_NAME = ct.TRIGGER_NAME
    AND t.TRIGGER_GROUP = ct.TRIGGER_GROUP
</select>
    <select id="getJobAndTriggerDto" resultType="com.south.data.vo.JobAndTriggerDto">
SELECT
jd.JOB_NAME AS jobName,
jd.DESCRIPTION AS jobDescription,
jd.JOB_GROUP AS jobGroupName,
jd.JOB_CLASS_NAME AS jobClassName,
t.TRIGGER_NAME AS triggerName,
t.TRIGGER_GROUP AS triggerGroupName,
FROM_UNIXTIME(t.PREV_FIRE_TIME/1000,'%Y-%m-%d %T') AS prevFireTime,
FROM_UNIXTIME(t.NEXT_FIRE_TIME/1000,'%Y-%m-%d %T') AS nextFireTime,
ct.CRON_EXPRESSION AS cronExpression,
t.TRIGGER_STATE AS triggerState
FROM
qrtz_job_details jd
JOIN qrtz_triggers t
JOIN qrtz_cron_triggers ct ON jd.JOB_NAME = t.JOB_NAME
AND t.TRIGGER_NAME = ct.TRIGGER_NAME
AND t.TRIGGER_GROUP = ct.TRIGGER_GROUP
    </select>
</mapper>
复制代码



BaseJob类


与Quartz服务的任务调度直接关联的Job服务机制控制,执行的基础!

public interface BaseJob extends Job {
  public void execute(JobExecutionContext context) throws JobExecutionException;
}
复制代码



TaskController控制器


与页面相关的执行业务控制器

@RestController
@RequestMapping(value = "/job")
public class JobController  {
  @Autowired
    private TaskInfoService taskInfoService;
    public JobController(TaskInfoService taskInfoService){
        this.taskInfoService = taskInfoService;
    }
    @PostMapping(value = "/page")
    public ResponseEntity<List<TaskInfoBO>> queryjob(Pageable pageable, @RequestParam MultiValueMap<String, String> queryParams, UriComponentsBuilder uriBuilder) {
        IPage<TaskInfoBO> page = taskInfoService.getPageJob(pageable, queryParams);
        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(uriBuilder.queryParams(queryParams), page);
        return ResponseEntity.ok().headers(headers).body(page.getRecords());
    }
    /**
    * @Title: addJob
    * @Description: TODO(添加Job)
    * @param jobClassName
    * 类名
    * @param jobGroupName
    * 组名
    * @param cronExpression
    * 表达式,如:0/5 * * * * ? (每隔5秒)
    */
    @PostMapping(value = "/add")
    public ResponseEntity addJob(
            @RequestParam(value = "jobClassName") String jobClassName,
            @RequestParam(value = "jobGroupName") String jobGroupName,
            @RequestParam(value = "cronExpression") String cronExpression){
        try {
            jobAndTriggerService.addJob(jobClassName, jobGroupName, cronExpression);
            return ResponseEntity.ok().body("操作成功");
        } catch (Exception e) {
            return ResponseEntity.ok().body("操作失败");
        }
    }
    /**
    * @Title: pauseJob
    * @Description: TODO(暂停Job)
    * @param jobClassName
    *            类名
    * @param jobGroupName
    *            组名
    */
    @PostMapping(value = "/pause")
    public ResponseEntity pauseJob(
            @RequestParam(value = "jobClassName") String jobClassName,
            @RequestParam(value = "jobGroupName") String jobGroupName) {
        try {
            taskInfoService.pauseJob(jobClassName, jobGroupName);
            return ResponseEntity.ok().body("操作成功");
        } catch (Exception e) {
            return ResponseEntity.ok().body("操作失败");
        }
    }
    /**
    * @Title: resumeJob
    * @Description: TODO(恢复Job)
    * @param jobClassName
    *            类名
    * @param jobGroupName
    *            组名
    */
    @PostMapping(value = "/resume")
    public ResponseEntity resumeJob(
            @RequestParam(value = "jobClassName") String jobClassName,
            @RequestParam(value = "jobGroupName") String jobGroupName) {
        try {
            taskInfoService.resumejob(jobClassName, jobGroupName);
            return ResponseEntity.ok().body("操作成功");
        } catch (Exception e) {
            return ResponseEntity.ok().body("操作失败");
        }
    }
    /**
    * @Title: rescheduleJob
    * @Description: TODO(重新设置Job)
    * @param jobClassName
    *            类名
    * @param jobGroupName
    *            组名
    * @param cronExpression
    *            表达式
    */
    @PostMapping(value = "/reschedule")
    public ResponseEntity rescheduleJob(
            @RequestParam(value = "jobClassName") String jobClassName,
            @RequestParam(value = "jobGroupName") String jobGroupName,
            @RequestParam(value = "cronExpression") String cronExpression) {
        try {
            taskInfoService.updateJob(jobClassName, jobGroupName, cronExpression);
            return ResponseEntity.ok().body("操作成功");
        } catch (Exception e) {
            return ResponseEntity.ok().body("操作失败");
        }
    }
    /**
    * @Title: deleteJob
    * @Description: TODO(删除Job)
    * @param jobClassName
    *            类名
    * @param jobGroupName
    *            组名
    */
    @RequestMapping(value = "/del", method = RequestMethod.POST)
    public ResponseEntity deleteJob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName) {
        try {
            taskInfoService.deleteJob(jobClassName, jobGroupName);
            return ResponseEntity.ok().body("操作成功");
        } catch (Exception e) {
            return ResponseEntity.ok().body("操作失败");
        }
  }
}
复制代码

image.png




DeferredResult实现实时推送


  • 浏览器要实时展示服务端计算出来的数据。一种可能的实现是:浏览器频繁向服务端发起请求以获得服务端数据。
  • 若定时周期为S,则数据延迟周期最大即为S。若想缩短数据延迟周期,则应使S尽量小,而S越小,浏览器向服务端发起请求的频率越高,又造成网络握手次数越多,影响了效率。因此,此场景应使用服务端实时推送技术。
  • 这里说是推送,其实还是基于请求-响应机制,只不过发起的请求会在服务端挂起,直到请求超时或服务端有数据推送时才会做出响应,响应的时机完全由服务端控制。所以,整体效果看起来就像是服务端真的在“实时推送”一样。


可以利用DeferredResult来实现异步长连接的服务端实时推送。



使用案例
@RequestMapping("/call")
@ResponseBody
public DeferredResult<Object> call() {
  // 泛型Object表示返回结果的类型
    DeferredResult<Object> response = new DeferredResult<Object>(10000, // 请求的超时时间
  null); // 超时后响应的结果
    response.onCompletion(new Runnable() {
        @Override
        public void run() {
            // 请求处理完成后所做的一些工作
        }
    });
    // 设置响应结果
    // 调用此方法时立即向浏览器发出响应;未调用时请求被挂起
    response.setResult(new Object());
    return response;
}
复制代码




执行逻辑


  1. 浏览器发起异步请求
  2. 请求到达服务端被挂起(使用浏览器查看请求状态,此时为pending)
  3. 向浏览器进行响应,分为两种情况:
  • 服务端调用DeferredResult.setResult(),请求被唤醒,返回结果。
  • 超时,返回一个你设定的结果。
  1. 浏览得到响应,再次重复1,处理此次响应结果



实现DeferResult传输模型

public interface DeferredData {
    String getId(); // 唯一标识
}
复制代码



DeferredResult的持有者

public interface IDeferredResultHolder<DeferredData> {
    DeferredResult<DeferredData> newDeferredResult(String key, long timeout, Object timeoutResult);
    void add(String key, DeferredResult<DeferredData> deferredResult);
    DeferredResult<DeferredData> get(String key);
    void remove(String key);
    void handleDeferredData(DeferredData deferredData);
}
复制代码



DeferredResult的持有者实现

public class DeferredResultHolder implements IDeferredResultHolder<DeferredData> {
    private Map<String, DeferredResult<DeferredData>> deferredResults = new ConcurrentHashMap<String, DeferredResult<DeferredData>>();
    public DeferredResult<DeferredData> newDeferredResult(String key) {
        return new DeferredResult(key, 30 * 1000L, null);
    }
    public DeferredResult<DeferredData> newDeferredResult(String key, long timeout) {
        return new DeferredResult(key, timeout, null);
    }
    public DeferredResult<DeferredData> newDeferredResult(String key, Object timeoutResult) {
        return new DeferredResult(key, 30 * 1000L, timeoutResult);
    }
    @Override
    public DeferredResult<DeferredData> newDeferredResult(String key, long timeout, Object timeoutResult) {
        DeferredResult<DeferredData> deferredResult = newDeferredResult<DeferredData>(timeout, timeoutResult);
        add(key, deferredResult);
        deferredResult.onCompletion(new Runnable() {
            @Override
            public void run() {
                remove(key);
            }
        });
        return deferredResult;
    }
    @Override
    public void add(String key, DeferredResult<DeferredData> deferredResult) {
        deferredResults.put(key, deferredResult);
    }
    @Override
    public DeferredResult<DeferredData> get(String key) {
        return deferredResults.get(key);
    }
    @Override
    public void remove(String key) {
        deferredResults.remove(key);
    }
    @Override
    public void handleDeferredData(DeferredData deferredData) {
        String key = deferredData.getId();
        DeferredResult<DeferredData> deferredResult = get(key);
        if (deferredResult != null) {
            deferredResult.setResult(deferredData);
        }
    }
}
复制代码



调用端


@RequestMapping
@Controller
public class CallController {
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    @RequestMapping("/call")
    @ResponseBody
    public DeferredResult<DeferredData> call() {
        String id = "abc";
        return deferredResultHolder.newDeferredResult(id, 10 * 1000L, null);
    }
}
复制代码



触发返回端


此处的CustomerDeferredData为实现了DeferredData接口的实现模型

@RequestMapping
@Controller
public class CallController {
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    @RequestMapping("/finished")
    @ResponseBody
    public void finished() {
        String id = "abc";
        DeferredData defdatq = new CustomerDeferredData(id); 、
        return deferredResultHolder.handleDeferredData(defdatq);
    }
}
复制代码


补充一个Spring针对于泛型的小tips


随着泛型用的越来越多,获取泛型实际类型信息的需求也会出现,如果用原生API,需要很多步操作才能获取到泛型,比如:

ParameterizedType parameterizedType =   
      (ParameterizedType) ABService.class.getGenericInterfaces()[0];  
Type genericType = parameterizedType.getActualTypeArguments()[1];  
复制代码

 

Spring提供的ResolvableType API,提供了更加简单易用的泛型操作支持,如:

ResolvableType resolvableType1 = ResolvableType.forClass(ABService.class);  
resolvableType1.as(Service.class).getGeneric(1).resolve()  
复制代码



对于获取更复杂的泛型操作ResolvableType更加简单。

假设我们的API是:

public interface Service<N, M> {    }  
@org.springframework.stereotype.Service  
public class ABService implements Service<A, B> {}  
@org.springframework.stereotype.Service  
public class CDService implements Service<C, D> {}
复制代码



如上泛型类非常简单。  

ResolvableType resolvableType1 = ResolvableType.forClass(ABService.class);  
复制代码



通过如上API,可以得到类型的ResolvableType,如果类型被Spring AOP进行了CGLIB代理,请使用ClassUtils.getUserClass(ABService.class)得到原始类型。

 可以通过如下得到泛型参数的第1个位置(从0开始)的类型信息 resolvableType1.getInterfaces()[0].getGeneric(1).resolve()。

因为我们泛型信息放在 Service<A, B> 上,所以需要resolvableType1.getInterfaces()[0]得到;通过getGeneric(泛型参数索引)得到某个位置的泛型,resolve()把实际泛型参数解析出来




得到字段级别的泛型信息


假设我们的字段如下:

@Autowired  
  private Service<A, B> abService;  
  @Autowired  
  private Service<C, D> cdService;      
  private List<List<String>> list;      
  private Map<String, Map<String, Integer>> map;      
  private List<String>[] array;  
复制代码



通过如下API可以得到字段级别的ResolvableType

ResolvableType resolvableType2 =  
ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "cdService"));  
复制代码



然后通过如下API得到Service<C, D>的第0个位置上的泛型实参类型,即C:resolvableType2.getGeneric(0).resolve(),比如List<List> list;是一种嵌套的泛型用例,我们可以通过如下操作获取String类型:

ResolvableType resolvableType3 =  
ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "list"));  
resolvableType3.getGeneric(0).getGeneric(0).resolve();  
复制代码



更简单的写法


resolvableType3.getGeneric(0, 0).resolve(),List<List<String>> 即String   
复制代码

比如Map<String, Map<String, Integer>> map;我们想得到Integer,可以使用:ResolvableType resolvableType4 =   ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "map"));  

resolvableType4.getGeneric(1).getGeneric(1).resolve(); 
复制代码


更简单的写法  
resolvableType4.getGeneric(1, 1).resolve()  
复制代码



 

得到方法返回值的泛型信息**


假设我们的方法如下:

private HashMap<String, List<String>> method() {  
      return null;  
}  
复制代码


得到Map中的List中的String泛型实参:

ResolvableType resolvableType5 =  ResolvableType.forMethodReturnType(ReflectionUtils.findMethod(GenricInjectTest.class, "method"));  
resolvableType5.getGeneric(1, 0).resolve();  
复制代码




得到构造器参数的泛型信息


假设我们的构造器如下:

public Const(List<List<String>> list, Map<String, Map<String, Integer>> map) {  }  
复制代码


我们可以通过如下方式得到第1个参数( Map<String, Map<String, Integer>>)中的Integer:

ResolvableType resolvableType6 = ResolvableType.forConstructorParameter(ClassUtils.getConstructorIfAvailable(Const.class, List.class, Map.class), 1);  
resolvableType6.getGeneric(1, 0).resolve();  
复制代码



 

得到数组组件类型的泛型信息


如对于private List[] array; 可以通过如下方式获取List的泛型实参String:

ResolvableType resolvableType7 =  ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "array"));  
resolvableType7.isArray();//判断是否是数组  
resolvableType7.getComponentType().getGeneric(0).resolve();  
复制代码

 



自定义泛型类型

ResolvableType resolvableType8 = ResolvableType.forClassWithGenerics(List.class, String.class);   ResolvableType resolvableType9 = ResolvableType.forArrayComponent(resolvableType8);  
resolvableType9.getComponentType().getGeneric(0).resolve();  
复制代码

ResolvableType.forClassWithGenerics(List.class, String.class)相当于创建一个List类型;



ResolvableType.forArrayComponent(resolvableType8);:相当于创建一个List[]数组;



resolvableType9.getComponentType().getGeneric(0).resolve():得到相应的泛型信息;



 从如上操作可以看出其泛型操作功能十分完善,尤其在嵌套的泛型信息获取上相当简洁。目前整个Spring4环境都使用这个API来操作泛型信息。




相关文章
|
JSON 监控 前端开发
AMIS:百度开源的前端低代码神器,18.4k star 背后的开发效率提升利器
AMIS(前端低代码框架)是百度开源的低代码前端框架,基于纯 JSON 配置即可生成完整后台页面,包括表单、表格、图表、CRUD 列表,支持可视化拖拽编辑。,星标数已达 18.4k,百度内部已沉淀超过 5 万个页面,广泛应用于审核系统、数据管理后台、模型监控等落地场景
2040 0
|
JavaScript 前端开发 Java
制造业ERP源码,工厂ERP管理系统,前端框架:Vue,后端框架:SpringBoot
这是一套基于SpringBoot+Vue技术栈开发的ERP企业管理系统,采用Java语言与vscode工具。系统涵盖采购/销售、出入库、生产、品质管理等功能,整合客户与供应商数据,支持在线协同和业务全流程管控。同时提供主数据管理、权限控制、工作流审批、报表自定义及打印、在线报表开发和自定义表单功能,助力企业实现高效自动化管理,并通过UniAPP实现移动端支持,满足多场景应用需求。
1440 1
|
人工智能 前端开发 JavaScript
前端工程化演进之路:从手工作坊到AI驱动的智能化开发
前端工程化演进之路:从手工作坊到AI驱动的智能化开发
1138 19
前端工程化演进之路:从手工作坊到AI驱动的智能化开发
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——封装统一返回的数据结构
本文介绍了在Spring Boot中封装统一返回的数据结构的方法。通过定义一个泛型类`JsonResult&lt;T&gt;`,包含数据、状态码和提示信息三个属性,满足不同场景下的JSON返回需求。例如,无数据返回时可设置默认状态码&quot;0&quot;和消息&quot;操作成功!&quot;,有数据返回时也可自定义状态码和消息。同时,文章展示了如何在Controller中使用该结构,通过具体示例(如用户信息、列表和Map)说明其灵活性与便捷性。最后总结了Spring Boot中JSON数据返回的配置与实际项目中的应用技巧。
1161 0
|
JSON Java fastjson
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——使用 fastJson 处理 null
本文介绍如何使用 fastJson 处理 null 值。与 Jackson 不同,fastJson 需要通过继承 `WebMvcConfigurationSupport` 类并覆盖 `configureMessageConverters` 方法来配置 null 值的处理方式。例如,可将 String 类型的 null 转为 &quot;&quot;,Number 类型的 null 转为 0,避免循环引用等。代码示例展示了具体实现步骤,包括引入相关依赖、设置序列化特性及解决中文乱码问题。
890 0
|
JSON Java fastjson
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——Spring Boot 默认对Json的处理
本文介绍了在Spring Boot中返回Json数据的方法及数据封装技巧。通过使用`@RestController`注解,可以轻松实现接口返回Json格式的数据,默认使用的Json解析框架是Jackson。文章详细讲解了如何处理不同数据类型(如类对象、List、Map)的Json转换,并提供了自定义配置以应对null值问题。此外,还对比了Jackson与阿里巴巴FastJson的特点,以及如何在项目中引入和配置FastJson,解决null值转换和中文乱码等问题。
1951 0
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
868 70
|
存储 消息中间件 前端开发
PHP后端与uni-app前端协同的校园圈子系统:校园社交场景的跨端开发实践
校园圈子系统校园论坛小程序采用uni-app前端框架,支持多端运行,结合PHP后端(如ThinkPHP/Laravel),实现用户认证、社交关系管理、动态发布与实时聊天功能。前端通过组件化开发和uni.request与后端交互,后端提供RESTful API处理业务逻辑并存储数据于MySQL。同时引入Redis缓存热点数据,RabbitMQ处理异步任务,优化系统性能。核心功能包括JWT身份验证、好友系统、WebSocket实时聊天及活动管理,确保高效稳定的用户体验。
925 4
PHP后端与uni-app前端协同的校园圈子系统:校园社交场景的跨端开发实践
|
JSON Java 数据格式
Spring Boot返回Json数据及数据封装
在Spring Boot中,接口间及前后端的数据传输通常使用JSON格式。通过@RestController注解,可轻松实现Controller返回JSON数据。该注解是Spring Boot新增的组合注解,结合了@Controller和@ResponseBody的功能,默认将返回值转换为JSON格式。Spring Boot底层默认采用Jackson作为JSON解析框架,并通过spring-boot-starter-json依赖集成了相关库,包括jackson-databind、jackson-datatype-jdk8等常用模块,简化了开发者对依赖的手动管理。
962 3
|
Java 数据库连接 API
Java 8 + 特性及 Spring Boot 与 Hibernate 等最新技术的实操内容详解
本内容涵盖Java 8+核心语法、Spring Boot与Hibernate实操,按考试考点分类整理,含技术详解与代码示例,助力掌握最新Java技术与应用。
446 2