springboot+自定義注解實現(xiàn)功能增強

需求

原各個業(yè)務模塊有對應的批量導入、批量修改、批量導出功能,但是批量操作屬于大量耗費系統(tǒng)資源的操作,并且沒有統(tǒng)一的流程,不便于管理,于是考慮將其加入到定時任務中,上傳文件時只是創(chuàng)建任務信息,在系統(tǒng)空閑時進行文件解析、驗證、入庫操作。
由于定時任務需要根據(jù)任務去調(diào)用不同模塊的文件處理細節(jié)并記錄處理結(jié)果,考慮使用動態(tài)代理的方式,將文件解析、數(shù)據(jù)入庫等業(yè)務細節(jié)分發(fā)到具體業(yè)務模塊,定時任務只負責調(diào)用業(yè)務處理方法及記錄處理結(jié)果。
流程:

  1. 自定義注解標記業(yè)務處理方法
  2. 依賴springboot掃描功能,將注解標記的方法加載到內(nèi)存中
  3. 定時任務執(zhí)行時,使用反射調(diào)用具體方法

開始

一、自定義注解

  1. 準備準備知識
    1.1. 元注解
    元注解是由java提供的,所有java中的注解都依賴于元注解。有如下四個注解:
  • @Target: 描述注解的使用范圍,可多選
public enum ElementType {
    TYPE, // 類、接口、枚舉類
    FIELD, // 成員變量(包括:枚舉常量)
    METHOD, // 成員方法
    PARAMETER, // 方法參數(shù)
    CONSTRUCTOR, // 構造方法
    LOCAL_VARIABLE, // 局部變量
    ANNOTATION_TYPE, // 注解類
    PACKAGE, // 可用于修飾:包
    TYPE_PARAMETER, // 類型參數(shù),JDK 1.8 新增
    TYPE_USE // 使用類型的任何地方,JDK 1.8 新增
}

使用示例:

// 該注解可標記在方法和類上
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface SysTaskExecuteMethod {}
  • @Retention: 描述注解保留的時間范圍
public enum RetentionPolicy {
    SOURCE,    // 源文件保留:僅在源文件中保留,編譯時將被忽略
    CLASS,       // 編譯期保留(默認值):編譯階段保留,運行期不可見
    RUNTIME   // 運行期保留,可通過反射去獲取注解信息
}
  • @Documented: 描述在使用 javadoc 工具為類生成幫助文檔時是否要保留其注解信息
  • @Inherited: 描述使被它修飾的注解具有繼承性:被他修飾的類,其子類自動繼承該注解
    1.2. 創(chuàng)建自定義注解
    創(chuàng)建自定義注解時,使用 @interface標識注解類即可
public @interface CustomerAnnotation {
    // coding
}
  1. 定義注解
    2.1. 定義類標識注解
    因為使用的是springboot的自動掃描及裝配實現(xiàn)實例的注冊及持有,springboot提供的API只能掃描到類級注解,故此處需要定義一個類注解參與,該注解僅僅作為標記該類有參與細節(jié)處理
package com.customer.common.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassAnnotation{
}

2.2. 定義業(yè)務處理注解
因為使用反射調(diào)用具體的方法,所以使用范圍為ElementType.METHOD、保留時間為RetentionPolicy.RUNTIME
為了便于區(qū)分標識的方法對應的任務分類增加了注解參數(shù)

package com.customer.common.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DongingSomething {
    /**
     * 任務名稱 
     */
    String taskName();// 不加detault的參數(shù)表示為必填參數(shù)

    /**
     * 任務所屬分類 
     */
    String type();
}

二、注解掃描

使用springboot提供的API實現(xiàn)自定義注解掃描:增加自定義配置,實現(xiàn)org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor接口

@Configuration // springboot啟動時會自動識別其為配置項
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));
        // 定義掃描的包 若不在該包及其子包中,將無法被掃描到
        scanner.scan("com.customer");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    }

    // 使用springboot上下文獲取注解實例,并添加到上下文中,后期使用直接注入即可使用
    @Bean("customerExecuteInstance")
    public List<CustomerExecuteInstance> getAllCustomerExecuteInstance(ApplicationContext applicationContext) {
        List<CustomerExecuteInstance> result = new ArrayList<>();
        // 使用springboot上下文方式獲取注解標識的實例
        // springbootAPI未提供直接掃描方法上的注解,故自定義注解有兩個,此處使用的是類上的注解
        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(ClassAnnotation.class);
        for (Map.Entry<String, Object> item : beansWithAnnotation.entrySet()) {
            // 一般方式使用此方法即可得到方法的注解,比如單元測試中能正常獲取到
            Method[] methods = item.getValue().getClass().getMethods();
            // 運行環(huán)境下,方法是在service實現(xiàn)類中的,中上下文中的實例其實都是被代理的,此時將無法通過此方法得到方法上的注解
            // 處理被代理的類
            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è)務處理結(jié)果,故此處增加返回參數(shù)驗證
                    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自動增加getter、setter方法
@Accessors(chain = true)// getter、setter方法返回當前對象,方便使用鏈式編程
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);
    }
}

三、注解使用

在需要的類及方法上添加注解
示例:

  1. 使用在service類中
@Service // service實現(xiàn)類標識
@ClassAnnotation
public class ModelDoingServiceImpl implements ModelDoingService {

    // Method[] methods = item.getValue().getClass().getMethods()將無法掃描到該注解
    @DongingSomething(taskName = "name1", type = "type1")
    public CustomerResult importMeterInfos(TaskInfo task) {
        // coding.....
       return null;
    }
}
  1. 使用在普通類中
@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";
    }
}

四、定時任務中調(diào)用細節(jié)處理

定時任務中使用如下:

@Slf4j // 使用日志框架,方便日志輸出
public class TaskExecution {
    @Resource // 直接注入bean,springboot上下文中已有他們的實例及相關信息
    private List<CustomerExecuteInstance> executeInstanceList;

    public void executeTask() {
        //查詢待執(zhí)行任務
        List<Task> taskList = new ArrayList();
        // 查詢?nèi)蝿疹惐?        for (Task task : taskList) {
            CustomerExecuteInstance currInst = executeInstanceList.stream()
                    // 使用實體類型中的自定義equles方法
                    .filter(ins -> ins.equals(task.getName(), task.getType()))
                    .findFirst()
                    .orElse(null);
            if (currInst == null) {
                log.warn(task.getType() + " 類型下的【" + task.getName() + "】任務無對應任務執(zhí)行邏輯。");
                continue;
            }

            try {
                CustomerResult result;
                // 使用反射,直接調(diào)用方法處理對應邏輯
                // 此處需要提前約定方法參數(shù)只能為 Task及其子類的對象,也可根據(jù)需求自行拓展
                result = (CustomerResult) currInst.getMethod().invoke(currInst.getInstance(), task);
                // 記錄執(zhí)行結(jié)果
                // doing...
            } catch (Exception e) {
                // 記錄執(zhí)行失敗結(jié)果
                // doing ...
            }
            // 任務執(zhí)行結(jié)束后的后續(xù)操作
            // doing...
        }
    }
}

定時任務如何實現(xiàn)。。。。
這個問題請關注后期補充上 [手動狗頭]

五、寫在最后

至此,整個業(yè)務貫穿打通,實現(xiàn)了業(yè)務的分離解耦

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容