1. 程式人生 > >利用Spring掃包實現發現具體的註解類

利用Spring掃包實現發現具體的註解類

編輯此區域

需求描述:

在RocketMQ客戶端開發過程中,發現對於消費端的消費程式客戶端在開發過程中是無法提前預知的,即消費端的消費程式需要具體到業務中去實現。
因此,發現具體的消費方法是RocketMQ客戶端設計過程中一個不得不考慮的問題。為了降低客戶端對上層業務系統的侵入性,計劃採用業務消費類
新增特定“註解”+客戶端“掃包”的方式來發現業務消費程式。由於Spring的掃包方法是經受過普遍考驗的,因此決定在Spring原始碼的基礎上進行
修改實現。

測試程式碼:

package com.abc.lottery.easymq;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.SystemPropertyUtils;

import com.google.common.collect.Sets;
import com.abc.lottery.easymq.handler.MQConsumerAnnotation;

public class ScanPackageTest
{
    private final static Log log = LogFactory.getLog(ScanPackageTest.class);
    //掃描  scanPackages 下的檔案匹配符
    public static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    /**
     * 找到scanPackages下帶註解annotation的全部類資訊
     * @param scanPackages 掃包路徑,多個路徑用","分割
     * @param annotation 註解類
     */
    public static Set<String> findPackageAnnotationClass(String scanPackages, Class<? extends Annotation> annotation)
    {
        if (StringUtils.isEmpty(scanPackages))
        {
            return Sets.newHashSet();
        }

        // 排重包路徑,避免父子路徑重複掃描
        Set<String> packages = checkPackages(scanPackages);
        //獲取Spring資源解析器
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        //建立Spring中用來讀取resource為class的工具類
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

        Set<String> fullClazzSet = Sets.newHashSet();
        for (String basePackage : packages)
        {
            if (StringUtils.isEmpty(basePackage))
            {
                continue;
            }
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                    + ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage))
                    + "/" + DEFAULT_RESOURCE_PATTERN;
            try
            {
                //獲取packageSearchPath下的Resource,這裡得到的Resource是Class資訊
                Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
                for (Resource resource : resources)
                {
                    //檢查resource,這裡的resource都是class
                    String fullClassName = loadClassName(metadataReaderFactory, resource);
                    if (isAnnotationClass(fullClassName, annotation))
                    {
                        fullClazzSet.add(fullClassName);
                    }
                }
            }
            catch (Exception e)
            {
                log.error("獲取包下面的類資訊失敗,package:" + basePackage, e);
            }
        }
        return fullClazzSet;
    }

    /**
     * 排重、檢測package父子關係,避免多次掃描
     * @param scanPackages 掃包路徑
     * @return 返回全部有效的包路徑資訊
     */
    private static Set<String> checkPackages(String scanPackages)
    {
        if (StringUtils.isEmpty(scanPackages))
        {
            return Sets.newHashSet();
        }
        Set<String> packages = Sets.newHashSet();
        //排重路徑
        Collections.addAll(packages, scanPackages.split(","));
        for (String packageStr : packages.toArray(new String[packages.size()]))
        {
            if (StringUtils.isEmpty(packageStr) || packageStr.equals(".") || packageStr.startsWith("."))
            {
                continue;
            }
            if (packageStr.endsWith("."))
            {
                packageStr = packageStr.substring(0, packageStr.length() - 1);
            }
            Iterator<String> packageIte = packages.iterator();
            boolean needAdd = true;
            while (packageIte.hasNext())
            {
                String pack = packageIte.next();
                if (packageStr.startsWith(pack + "."))
                {
                    //如果待加入的路徑是已經加入的pack的子集,不加入
                    needAdd = false;
                }
                else if (pack.startsWith(packageStr + "."))
                {
                    //如果待加入的路徑是已經加入的pack的父集,刪除已加入的pack
                    packageIte.remove();
                }
            }
            if (needAdd)
            {
                packages.add(packageStr);
            }
        }
        return packages;
    }

    /**
     * 載入資源,根據resource獲取className
     * @param metadataReaderFactory spring中用來讀取resource為class的工具
     * @param resource 這裡的資源就是一個Class
     */
    private static String loadClassName(MetadataReaderFactory metadataReaderFactory, Resource resource)
            throws IOException
    {
        try
        {
            if (resource.isReadable())
            {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                if (metadataReader != null)
                {
                    return metadataReader.getClassMetadata().getClassName();
                }
            }
        }
        catch (Exception e)
        {
            log.error("根據Spring resource獲取類名稱失敗", e);
        }
        return null;
    }

    /**
     * 判斷類是否包含特定的註解
     * @param fullClassName
     * @param annotationClass
     * @return
     * @throws ClassNotFoundException
     */
    private static boolean isAnnotationClass(String fullClassName, Class<? extends Annotation> annotationClass)
            throws ClassNotFoundException
    {
        //利用反射,根據類名獲取類的全部資訊
        Class<?> clazz = Class.forName(fullClassName);
        //獲取該類的註解資訊
        Annotation annotation = clazz.getAnnotation(annotationClass);
        if (annotation != null)
        {//包含annotationClass註解
            return true;
        }
        return false;
    }
}

輸出結果

public static void main(String[] args)
    {
        String packages = "com.abc.lottery.easymq";
        System.out.println("annotation class:" + findPackageAnnotationClass(packages, MQConsumerAnnotation.class));
    }

輸出結果:annotation class:[com.abc.lottery.easymq.handler.PaySuccessMQHandler]

結果說明

自己定義了一個註解MQConsumerAnnotation
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MQConsumerAnnotation
{
    String topic();

    String tags();

}

PaySuccessMQHandler類是com.abc.lottery.easymq包路徑下唯一一個新增MQConsumerAnnotation註解的類
@MQConsumerAnnotation(topic = "paySuccessTopic", tags = "tagA")
public class PaySuccessMQHandler implements MQRecMsgHandler
{
    public void handle(List<String> msg) throws Exception
    {
        System.out.println(msg);
    }
}

因此,掃描com.abc.lottery.easymq包路徑得到唯一的一個類PaySuccessMQHandler的全路徑資訊。後面,可以根據PaySuccessMQHandler的類資訊,通過反射獲取類例項,完成掃包獲取消費方法。

相關推薦

利用Spring實現發現具體註解

編輯此區域 需求描述: 在RocketMQ客戶端開發過程中,發現對於消費端的消費程式客戶端在開發過程中是無法提前預知的,即消費端的消費程式需要具體到業務中去實現。 因此,發現具體的消費方法是RocketMQ客戶端設計過程中一個不得不考慮的問題。為了降低客戶端對上層業務

利用Spring IOC DI 實現軟體分層解耦

1.軟體分層思想 在軟體領域有MVC軟體設計思想,指導著軟體開發過程。在javaee開發領域,javaee的經典三層架構MVC設計思想的經典應用。而在軟體設計思想中,追求的是"高內聚 低耦合"的目標,利用Spring的IOC 和 DI 可以非常方便的實現這個需求。 2

SpringBoot第二講利用Spring Data JPA實現資料庫的訪問(二)_分頁和JpaSpecificationExecutor介面介紹

我們繼續研究spring jpa data,首先看看分頁和排序的實現,在原來的程式碼中,我們如果希望實現分頁,首先得建立一個Pager的物件,在這個物件中記錄total(總數),totalPager(總頁數),pageSize(每頁多少條記錄),pageInde

SpringBoot第二講 利用Spring Data JPA實現資料庫的訪問(一)

在基本瞭解了springboot的執行流程之後,我們需要逐個來突破springboot的幾個關鍵性問題,我們首先解決的是springboot訪問資料庫的問題。Java訪問資料庫經歷了幾個階段,第一個階段是直接通過JDBC訪問,這種方式工作量極大,而且會做大量的重複勞動,之

利用php-java-bridge實現PHP呼叫JAVA

在工作中需要將office文件軟體轉為pdf檔案,查找了很多技術發現使用openoffice可以完成這個要求,但呼叫它的功能全部是java寫的,所有需要在php中呼叫java類,就有了下面除錯過程。 1、安裝java,這個過程我就不寫了,網上有很多,講的也很細。(其實可以不

掃描自定義註解並例項化

  1.  新建Maven 專案   annotation   2.   pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi

利用java反射機制實現javaweb自動呼叫的方法

public class BookServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequ

Spring @Qualifier 註解,一個service 介面,有多個實現時,如何在controller 只引入service 介面,進而實現引入具體bean

參考: https://blog.csdn.net/mazuyu408/article/details/79629846 @Qualifier 作用 在只引入service 介面的情況下在,指示spring 引入value 為那個別名的具體service 實現類 場景: 假設有一個場

Spring MVC 註解 initMethod

springMVC詳解以及註解說明 基於註釋(Annotation)的配置有越來越流行的趨勢,Spring 2.5 順應這種趨勢,提供了完全基於註釋配置 Bean、裝配 Bean 的功能,您可以使用基於註釋的 Spring IoC 替換原來基於 XML 的配置。本文通過例項

spring mvc實現自定義註解

poi org param 運行時 onf dha ogg logs exec 實現方式:使用@Aspect實現: 1. 新建註解接口:CheckSign package com.soeasy.web.utils; import org.springframework.

利用Spring Cloud實現微服務(八)- 熔斷機制

1. 熔斷機制介紹 在介紹熔斷機制之前,我們需要了解微服務的雪崩效應。在微服務架構中,微服務是完成一個單一的業務功能,這樣做的好處是可以做到解耦,每個微服務可以獨立演進。但是,一個應用可能會有多個微服務組成,微服務之間的資料互動通過遠端過程呼叫完成。這就帶來一個問題,假設微服務A呼叫微服務

Spring的AOP(xml和註解實現AOP,以及代理模式)

AOP術語:    連線點(Joinpoint):程式執行的某一個特定位置,如類初始前後,方法的執行前後。而Spring只支援方法的連線點。     切點(Pointcut):切點可以定位到相應的連線點,一個切點可以定位多個連線點。&

Spring Aop底層原理詳解(利用spring後置處理器實現AOP)

寫在前面:對於一個java程式設計師來說,相信絕大多數都有這樣的面試經歷,面試官問:你知道什麼是aop嗎?談談你是怎麼理解aop的?等等諸如此類關於aop的問題。當然對於一些小白可能會一臉懵逼;對於一些工作一兩年的,可能知道,哦!aop就是面向切面變成,列印日誌啊,什麼什麼的,要是有點學

利用xslt與xml實現具體字段字母的大小寫轉換

大小 xsl encoding lca var htm 大小寫 template llc 定義一個全局的變量 <xsl:variable name="smallcase" select="‘abcdefghijklmnopqrstuvwxyz‘" /> <

實現Telnet遠端登入,利用Wireshark抓分析

一、實驗環境 伺服器:windows server 2008 客戶機:windows 2007 網絡卡連線:NAT(Vmnet 8) 實現客戶機遠端登入伺服器 二、實驗步驟 1、設定IP地址 伺服器:windows server 2008 IP 地 址:192.168.10.1 子

利用spring的AOP來實現Redis快取

為什麼使用Redis 資料查詢時每次都需要從資料庫查詢資料,資料庫壓力很大,查詢速度慢,因此設定快取層,查詢資料時先從redis中查詢,如果查詢不到,則到資料庫中查詢,然後將資料庫中查詢的資料放到redis中一份,下次查詢時就能直接從redis中查到,不需要查

Spring基礎學習(四 AOP 註解實現

配置XML  applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"

Spring-boot mongodb ID自增長註解實現 適用於JDK 1.7和JDK 1.8

開發工具Idea ,JDK1.8 Entity類 SeqInfo.java package com.gl.springbootdao.mongodb.entity; import lombok.Getter; import lombok.Setter; import lombok.T

利用Spring框架在前端實現對資料庫的增刪改查

在前端頁面上顯示購物資料庫資料,並且可以這增、刪、改、查 1.首先在WEB 配置檔案 <!-- 配置DispatcherServlet --> <servlet> <servlet-name>springmvc</serv

利用spring-security實現登入

首先建立maven工程(war) pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:sc