需求
原各個(gè)業(yè)務(wù)模塊有對(duì)應(yīng)的批量導(dǎo)入、批量修改七蜘、批量導(dǎo)出功能达椰,但是批量操作屬于大量耗費(fèi)系統(tǒng)資源的操作,并且沒(méi)有統(tǒng)一的流程堤尾,不便于管理,于是考慮將其加入到定時(shí)任務(wù)中迁客,上傳文件時(shí)只是創(chuàng)建任務(wù)信息郭宝,在系統(tǒng)空閑時(shí)進(jìn)行文件解析辞槐、驗(yàn)證、入庫(kù)操作粘室。
由于定時(shí)任務(wù)需要根據(jù)任務(wù)去調(diào)用不同模塊的文件處理細(xì)節(jié)并記錄處理結(jié)果榄檬,考慮使用動(dòng)態(tài)代理的方式,將文件解析衔统、數(shù)據(jù)入庫(kù)等業(yè)務(wù)細(xì)節(jié)分發(fā)到具體業(yè)務(wù)模塊鹿榜,定時(shí)任務(wù)只負(fù)責(zé)調(diào)用業(yè)務(wù)處理方法及記錄處理結(jié)果。
流程:
- 自定義注解標(biāo)記業(yè)務(wù)處理方法
- 依賴springboot掃描功能锦爵,將注解標(biāo)記的方法加載到內(nèi)存中
- 定時(shí)任務(wù)執(zhí)行時(shí)舱殿,使用反射調(diào)用具體方法
開(kāi)始
一、自定義注解
- 準(zhǔn)備準(zhǔn)備知識(shí)
1.1. 元注解
元注解是由java提供的险掀,所有java中的注解都依賴于元注解怀薛。有如下四個(gè)注解:
- @Target: 描述注解的使用范圍,可多選
public enum ElementType {
TYPE, // 類迷郑、接口枝恋、枚舉類
FIELD, // 成員變量(包括:枚舉常量)
METHOD, // 成員方法
PARAMETER, // 方法參數(shù)
CONSTRUCTOR, // 構(gòu)造方法
LOCAL_VARIABLE, // 局部變量
ANNOTATION_TYPE, // 注解類
PACKAGE, // 可用于修飾:包
TYPE_PARAMETER, // 類型參數(shù),JDK 1.8 新增
TYPE_USE // 使用類型的任何地方嗡害,JDK 1.8 新增
}
使用示例:
// 該注解可標(biāo)記在方法和類上
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface SysTaskExecuteMethod {}
- @Retention: 描述注解保留的時(shí)間范圍
public enum RetentionPolicy {
SOURCE, // 源文件保留:僅在源文件中保留焚碌,編譯時(shí)將被忽略
CLASS, // 編譯期保留(默認(rèn)值):編譯階段保留,運(yùn)行期不可見(jiàn)
RUNTIME // 運(yùn)行期保留霸妹,可通過(guò)反射去獲取注解信息
}
- @Documented: 描述在使用 javadoc 工具為類生成幫助文檔時(shí)是否要保留其注解信息
- @Inherited: 描述使被它修飾的注解具有繼承性:被他修飾的類十电,其子類自動(dòng)繼承該注解
1.2. 創(chuàng)建自定義注解
創(chuàng)建自定義注解時(shí),使用 @interface標(biāo)識(shí)注解類即可
public @interface CustomerAnnotation {
// coding
}
- 定義注解
2.1. 定義類標(biāo)識(shí)注解
因?yàn)槭褂玫氖莝pringboot的自動(dòng)掃描及裝配實(shí)現(xiàn)實(shí)例的注冊(cè)及持有叹螟,springboot提供的API只能掃描到類級(jí)注解鹃骂,故此處需要定義一個(gè)類注解參與,該注解僅僅作為標(biāo)記該類有參與細(xì)節(jié)處理
package com.customer.common.annotation;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassAnnotation{
}
2.2. 定義業(yè)務(wù)處理注解
因?yàn)槭褂梅瓷湔{(diào)用具體的方法罢绽,所以使用范圍為ElementType.METHOD畏线、保留時(shí)間為RetentionPolicy.RUNTIME
為了便于區(qū)分標(biāo)識(shí)的方法對(duì)應(yīng)的任務(wù)分類增加了注解參數(shù)
package com.customer.common.annotation;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DongingSomething {
/**
* 任務(wù)名稱
*/
String taskName();// 不加detault的參數(shù)表示為必填參數(shù)
/**
* 任務(wù)所屬分類
*/
String type();
}
二、注解掃描
使用springboot提供的API實(shí)現(xiàn)自定義注解掃描:增加自定義配置良价,實(shí)現(xiàn)org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor接口
@Configuration // springboot啟動(dòng)時(shí)會(huì)自動(dòng)識(shí)別其為配置項(xiàng)
public class CustomAnnotationScanner implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry);
scanner.setBeanNameGenerator(new AnnotationBeanNameGenerator());
// 定義需要掃描的注解 -- 自定義注解
scanner.addIncludeFilter(new AnnotationTypeFilter(ClassAnnotation.class));
// 定義掃描的包 若不在該包及其子包中寝殴,將無(wú)法被掃描到
scanner.scan("com.customer");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
// 使用springboot上下文獲取注解實(shí)例,并添加到上下文中明垢,后期使用直接注入即可使用
@Bean("customerExecuteInstance")
public List<CustomerExecuteInstance> getAllCustomerExecuteInstance(ApplicationContext applicationContext) {
List<CustomerExecuteInstance> result = new ArrayList<>();
// 使用springboot上下文方式獲取注解標(biāo)識(shí)的實(shí)例
// springbootAPI未提供直接掃描方法上的注解蚣常,故自定義注解有兩個(gè),此處使用的是類上的注解
Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(ClassAnnotation.class);
for (Map.Entry<String, Object> item : beansWithAnnotation.entrySet()) {
// 一般方式使用此方法即可得到方法的注解痊银,比如單元測(cè)試中能正常獲取到
Method[] methods = item.getValue().getClass().getMethods();
// 運(yùn)行環(huán)境下抵蚊,方法是在service實(shí)現(xiàn)類中的,中上下文中的實(shí)例其實(shí)都是被代理的,此時(shí)將無(wú)法通過(guò)此方法得到方法上的注解
// 處理被代理的類
if (AopUtils.isJdkDynamicProxy(item.getValue())){
Object singletonTarget = AopProxyUtils.getSingletonTarget(item.getValue());
if (singletonTarget != null) {
methods = singletonTarget.getClass().getMethods();
}
} else if (AopUtils.isCglibProxy(item.getValue())) {
methods = item.getValue().getClass().getSuperclass().getMethods();
}
for (Method method : methods) {
SysTaskExecuteMethod annotation = method.getAnnotation(DongingSomething.class);
if (annotation != null) {
// 檢查方法返回值 只能是CustomerResult及其子類
// 因?yàn)樾枰龊笾锰幚碚晟液笾锰幚硪蕾嚇I(yè)務(wù)處理結(jié)果谷醉,故此處增加返回參數(shù)驗(yàn)證
if (!CustomerResult.class.isAssignableFrom(method.getReturnType())) {
log.error("\n\t\t*******************" + item.getValue().getClass().getName() + "." + method.getName() + "方法返回值類型不符合規(guī)范!*************************");
continue;
}
result.add(
new CustomerExecuteInstance()
.setTaskName(annotation.taskName())
.setTaskType(annotation.type())
.setMethod(method)
.setInstance(item.getValue()));
}
}
}
return result;
}
}
CustomerExecuteInstance類
@Data // 使用lombok自動(dòng)增加getter熔酷、setter方法
@Accessors(chain = true)// getter、setter方法返回當(dāng)前對(duì)象豺裆,方便使用鏈?zhǔn)骄幊?public class CustomerExecuteInstance {
private String taskName;
private String taskType;
private Method method;
private Object instance;
public boolean equals(String name, String type) {
if (name == null || type == null) {
return false;
}
return name.equals(this.taskName) && type.equals(this.taskType);
}
}
三拒秘、注解使用
在需要的類及方法上添加注解
示例:
- 使用在service類中
@Service // service實(shí)現(xiàn)類標(biāo)識(shí)
@ClassAnnotation
public class ModelDoingServiceImpl implements ModelDoingService {
// Method[] methods = item.getValue().getClass().getMethods()將無(wú)法掃描到該注解
@DongingSomething(taskName = "name1", type = "type1")
public CustomerResult importMeterInfos(TaskInfo task) {
// coding.....
return null;
}
}
- 使用在普通類中
@ClassAnnotation
class A1{
// Method[] methods = item.getValue().getClass().getMethods()可掃描到該注解
@DongingSomething(taskName = "test1", type = "type1")
public CustomerResult testxxx(Long projectId, String fileBusinessId, JSONObject paramsJson) {
System.out.println("execute method:"+projectId + fileBusinessId+ paramsJson);
return "execute success";
}
}
四、定時(shí)任務(wù)中調(diào)用細(xì)節(jié)處理
定時(shí)任務(wù)中使用如下:
@Slf4j // 使用日志框架臭猜,方便日志輸出
public class TaskExecution {
@Resource // 直接注入bean躺酒,springboot上下文中已有他們的實(shí)例及相關(guān)信息
private List<CustomerExecuteInstance> executeInstanceList;
public void executeTask() {
//查詢待執(zhí)行任務(wù)
List<Task> taskList = new ArrayList();
// 查詢?nèi)蝿?wù)類表
for (Task task : taskList) {
CustomerExecuteInstance currInst = executeInstanceList.stream()
// 使用實(shí)體類型中的自定義equles方法
.filter(ins -> ins.equals(task.getName(), task.getType()))
.findFirst()
.orElse(null);
if (currInst == null) {
log.warn(task.getType() + " 類型下的【" + task.getName() + "】任務(wù)無(wú)對(duì)應(yīng)任務(wù)執(zhí)行邏輯。");
continue;
}
try {
CustomerResult result;
// 使用反射蔑歌,直接調(diào)用方法處理對(duì)應(yīng)邏輯
// 此處需要提前約定方法參數(shù)只能為 Task及其子類的對(duì)象羹应,也可根據(jù)需求自行拓展
result = (CustomerResult) currInst.getMethod().invoke(currInst.getInstance(), task);
// 記錄執(zhí)行結(jié)果
// doing...
} catch (Exception e) {
// 記錄執(zhí)行失敗結(jié)果
// doing ...
}
// 任務(wù)執(zhí)行結(jié)束后的后續(xù)操作
// doing...
}
}
}
定時(shí)任務(wù)如何實(shí)現(xiàn)。次屠。园匹。。
這個(gè)問(wèn)題請(qǐng)關(guān)注后期補(bǔ)充上 [手動(dòng)狗頭]
五劫灶、寫在最后
至此裸违,整個(gè)業(yè)務(wù)貫穿打通,實(shí)現(xiàn)了業(yè)務(wù)的分離解耦