springboot+自定義注解實(shí)現(xiàn)功能增強(qiáng)

需求

原各個(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é)果。
流程:

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

開(kāi)始

一、自定義注解

  1. 準(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
}
  1. 定義注解
    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);
    }
}

三拒秘、注解使用

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

  1. 使用在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;
    }
}
  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";
    }
}

四、定時(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ù)的分離解耦

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末本昏,一起剝皮案震驚了整個(gè)濱河市供汛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涌穆,老刑警劉巖怔昨,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異宿稀,居然都是意外死亡趁舀,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門祝沸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赫编,“玉大人,你說(shuō)我怎么就攤上這事奋隶±匏停” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵唯欣,是天一觀的道長(zhǎng)嘹吨。 經(jīng)常有香客問(wèn)我,道長(zhǎng)境氢,這世上最難降的妖魔是什么蟀拷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任碰纬,我火速辦了婚禮,結(jié)果婚禮上问芬,老公的妹妹穿的比我還像新娘悦析。我一直安慰自己,他們只是感情好此衅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布强戴。 她就那樣靜靜地躺著,像睡著了一般挡鞍。 火紅的嫁衣襯著肌膚如雪骑歹。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天墨微,我揣著相機(jī)與錄音道媚,去河邊找鬼。 笑死翘县,一個(gè)胖子當(dāng)著我的面吹牛最域,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锈麸,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼羡宙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了掐隐?” 一聲冷哼從身側(cè)響起狗热,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虑省,沒(méi)想到半個(gè)月后匿刮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡探颈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年熟丸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伪节。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡光羞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怀大,到底是詐尸還是另有隱情纱兑,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布化借,位于F島的核電站潜慎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铐炫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一垒手、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧倒信,春花似錦科贬、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至竞穷,卻和暖如春唐责,著一層夾襖步出監(jiān)牢的瞬間鳞溉,已是汗流浹背瘾带。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留熟菲,地道東北人看政。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像抄罕,于是被迫代替她去往敵國(guó)和親允蚣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容