第四十四章: 基于SpringBoot & AOP完成統(tǒng)一資源自動(dòng)查詢映射

本章內(nèi)容比較偏向系統(tǒng)設(shè)計(jì)方面,簡(jiǎn)單的封裝就可以應(yīng)用到系統(tǒng)中使用,從而提高我們的編碼效率以及代碼的可讀性。統(tǒng)一資源在系統(tǒng)內(nèi)是不可避免的模塊,資源分類也有很多種,比較常見(jiàn)如:圖片資源、文本資源、視頻資源等,那么資源統(tǒng)一處理的好處是什么呢?大家有可能會(huì)有疑問(wèn),我把資源存放到業(yè)務(wù)表內(nèi)豈不更好嗎?這樣查詢起來(lái)也方便,并不需要關(guān)聯(lián)資源信息表!當(dāng)然設(shè)計(jì)不分好壞,只有更適合、更簡(jiǎn)單!接下來(lái)帶著疑問(wèn)進(jìn)入本章的內(nèi)容。

免費(fèi)教程專題

恒宇少年在博客整理三套免費(fèi)學(xué)習(xí)教程專題,由于文章偏多特意添加了閱讀指南,新文章以及之前的文章都會(huì)在專題內(nèi)陸續(xù)填充,希望可以幫助大家解惑更多知識(shí)點(diǎn)。

本章目標(biāo)

基于SpringBoot平臺(tái)結(jié)合AOP完成統(tǒng)一資源的自動(dòng)查詢映射。

構(gòu)建項(xiàng)目

本章使用到的依賴相對(duì)來(lái)說(shuō)比較多,大致:Web、MapStruct、SpringDataJpa、LomBok等,數(shù)據(jù)庫(kù)方面采用MySQL來(lái)作為數(shù)據(jù)支持。

SpringBoot 企業(yè)級(jí)核心技術(shù)學(xué)習(xí)專題


專題 專題名稱 專題描述
001 Spring Boot 核心技術(shù) 講解SpringBoot一些企業(yè)級(jí)層面的核心組件
002 Spring Boot 核心技術(shù)章節(jié)源碼 Spring Boot 核心技術(shù)簡(jiǎn)書(shū)每一篇文章碼云對(duì)應(yīng)源碼
003 Spring Cloud 核心技術(shù) 對(duì)Spring Cloud核心技術(shù)全面講解
004 Spring Cloud 核心技術(shù)章節(jié)源碼 Spring Cloud 核心技術(shù)簡(jiǎn)書(shū)每一篇文章對(duì)應(yīng)源碼
005 QueryDSL 核心技術(shù) 全面講解QueryDSL核心技術(shù)以及基于SpringBoot整合SpringDataJPA
006 SpringDataJPA 核心技術(shù) 全面講解SpringDataJPA核心技術(shù)
007 SpringBoot核心技術(shù)學(xué)習(xí)目錄 SpringBoot系統(tǒng)的學(xué)習(xí)目錄,敬請(qǐng)關(guān)注點(diǎn)贊!!!

數(shù)據(jù)初始化

本章用到的數(shù)據(jù)表結(jié)構(gòu)以及初始化的數(shù)據(jù)之前都是放在項(xiàng)目的resources目錄下,為了大家使用方面我在這里直接貼出來(lái),如下所示:

--
-- Table structure for table `hy_common_resource`
--

DROP TABLE IF EXISTS `hy_common_resource`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `hy_common_resource` (
  `CR_ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵自增',
  `CR_TARGET_ID` varchar(36) DEFAULT 'NULL' COMMENT '所屬目標(biāo)編號(hào),關(guān)聯(lián)其他信息表主鍵,如:用戶頭像關(guān)聯(lián)用戶編號(hào)',
  `CR_TYPE_ID` varchar(36) DEFAULT NULL COMMENT '資源類型編號(hào)',
  `CR_URL` varchar(200) DEFAULT 'NULL' COMMENT '資源路徑,如:圖片地址',
  `CR_CREATE_TIME` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '資源添加時(shí)間',
  `CR_ORDER` int(11) DEFAULT 0 COMMENT '排序字段',
  PRIMARY KEY (`CR_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='系統(tǒng)資源信息表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `hy_common_resource`
--

LOCK TABLES `hy_common_resource` WRITE;
/*!40000 ALTER TABLE `hy_common_resource` DISABLE KEYS */;
INSERT INTO `hy_common_resource` VALUES (1,'bc4c8e38-edd6-11e7-969c-3c15c2e4a8a6','ce66916c-edd7-11e7-969c-3c15c2e4a8a6','https://upload.jianshu.io/users/upload_avatars/4461954/f09ba256-f6db-41ed-a4ac-b2d23737f0ac.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96','2017-12-31 03:08:46',0),(2,'bc4c8e38-edd6-11e7-969c-3c15c2e4a8a6','f84f12c4-edd7-11e7-969c-3c15c2e4a8a6','https://upload.jianshu.io/collections/images/358868/android.graphics.Bitmap_d88b4de.jpeg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240','2017-12-31 03:12:38',0),(3,'bc4c8e38-edd6-11e7-969c-3c15c2e4a8a6','f84f12c4-edd7-11e7-969c-3c15c2e4a8a6','https://upload.jianshu.io/collections/images/522928/kafka_diagram.png?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240','2017-12-31 09:13:32',0);
/*!40000 ALTER TABLE `hy_common_resource` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `hy_common_resource_type`
--

DROP TABLE IF EXISTS `hy_common_resource_type`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `hy_common_resource_type` (
  `CRT_ID` varchar(36) NOT NULL COMMENT '類型編號(hào)',
  `CRT_NAME` varchar(20) DEFAULT NULL COMMENT '類型名稱',
  `CRT_FLAG` varchar(30) DEFAULT NULL COMMENT '資源標(biāo)識(shí)',
  `CRT_CREATE_TIME` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '創(chuàng)建時(shí)間',
  PRIMARY KEY (`CRT_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='資源類型信息表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `hy_common_resource_type`
--

LOCK TABLES `hy_common_resource_type` WRITE;
/*!40000 ALTER TABLE `hy_common_resource_type` DISABLE KEYS */;
INSERT INTO `hy_common_resource_type` VALUES ('ce66916c-edd7-11e7-969c-3c15c2e4a8a6','用戶頭像','USER_HEAD_IMAGE','2017-12-31 03:07:59'),('f84f12c4-edd7-11e7-969c-3c15c2e4a8a6','用戶背景圖片','USER_BACK_IMAGE','2017-12-31 03:09:09');
/*!40000 ALTER TABLE `hy_common_resource_type` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `hy_user_info`
--

DROP TABLE IF EXISTS `hy_user_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `hy_user_info` (
  `UI_ID` varchar(36) NOT NULL COMMENT '主鍵',
  `UI_NAME` varchar(10) DEFAULT NULL COMMENT '名稱',
  `UI_NICK_NAME` varchar(20) DEFAULT NULL COMMENT '昵稱',
  `UI_AGE` int(11) DEFAULT NULL COMMENT '年齡',
  `UI_ADDRESS` varchar(50) DEFAULT NULL COMMENT '所居地',
  PRIMARY KEY (`UI_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶基本信息表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `hy_user_info`
--

LOCK TABLES `hy_user_info` WRITE;
/*!40000 ALTER TABLE `hy_user_info` DISABLE KEYS */;
INSERT INTO `hy_user_info` VALUES ('bc4c8e38-edd6-11e7-969c-3c15c2e4a8a6','hengboy','恒宇少年',23,'山東省濟(jì)南市');
/*!40000 ALTER TABLE `hy_user_info` ENABLE KEYS */;
UNLOCK TABLES;

用到的數(shù)據(jù)庫(kù)為resources,可以自行創(chuàng)建或者更換其他數(shù)據(jù)庫(kù)使用。

搭建項(xiàng)目

本章我們把統(tǒng)一資源單獨(dú)拿出來(lái)作為一個(gè)項(xiàng)目子模塊來(lái)構(gòu)建,而用戶服務(wù)作為另外一個(gè)單獨(dú)模塊構(gòu)建,下面先來(lái)貼出父項(xiàng)目的pom.xml配置文件內(nèi)容,如下所示:

....//
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
    </properties>

    <dependencies>
        <!--mapStruct-->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <!--Spring data jpa-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--MySQL-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--Lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>
....//

接下來(lái)我們開(kāi)始創(chuàng)建common-resource子模塊,將資源處理完全獨(dú)立出來(lái),在創(chuàng)建子模塊時(shí)要注意package命名要保證可以被SpringBoot運(yùn)行時(shí)掃描到?。?!

common-resource

我們需要先創(chuàng)建一個(gè)BaseEntity作為所有實(shí)體的父類存在,如下所示:

/**
 * 所有實(shí)體的父類
 * 作為類型標(biāo)識(shí)存在
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:下午3:35
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
public class BaseEntity
    implements Serializable{}

該類僅僅實(shí)現(xiàn)了Serializable接口,在創(chuàng)建業(yè)務(wù)實(shí)體時(shí)需要繼承該類,這也是基本的設(shè)計(jì)規(guī)則,方便后期添加全局統(tǒng)一的字段或者配置。

  • 資源實(shí)體
/**
 * 資源實(shí)體
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:上午11:21
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
@Entity
@Table(name = "hy_common_resource")
public class CommonResourceEntity
    extends BaseEntity
{
    /**
     * 資源編號(hào)
     */
    @Column(name = "CR_ID")
    @Id
    @GeneratedValue
    private Integer resourceId;
    /**
     * 資源所屬目標(biāo)編號(hào)
     */
    @Column(name = "CR_TARGET_ID")
    private String targetId;
    /**
     * 類型編號(hào)
     */
    @Column(name = "CR_TYPE_ID")
    private String typeId;
    /**
     * 資源路徑
     */
    @Column(name = "CR_URL")
    private String resourceUrl;
    /**
     * 創(chuàng)建時(shí)間
     */
    @Column(name = "CR_CREATE_TIME")
    private Timestamp createTime;
    /**
     * 排序
     */
    @Column(name = "CR_ORDER")
    private int order;
}
  • 資源類型實(shí)體
/**
 * 資源類型實(shí)體
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:上午11:22
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
@Entity
@Table(name = "hy_common_resource_type")
public class CommonResourceTypeEntity
    extends BaseEntity
{
    /**
     * 類型編號(hào)
     */
    @Id
    @Column(name = "CRT_ID")
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    private String id;
    /**
     * 類型名稱
     */
    @Column(name = "CRT_NAME")
    private String name;
    /**
     * 類型標(biāo)識(shí)
     */
    @Column(name = "CRT_FLAG")
    private String flag;
    /**
     * 類型添加時(shí)間
     */
    @Column(name = "CRT_CREATE_TIME")
    private Timestamp createTime;
}

下面我們來(lái)創(chuàng)建對(duì)應(yīng)實(shí)體的數(shù)據(jù)接口,我們采用SpringDataJPA的方法名查詢規(guī)則來(lái)查詢對(duì)應(yīng)的數(shù)據(jù)。

  • 資源數(shù)據(jù)接口
/**
 * 資源數(shù)據(jù)接口
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:上午11:31
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
public interface CommonResourceRepository
    extends JpaRepository<CommonResourceEntity,Integer>
{
    /**
     * 根據(jù)類型編號(hào) & 目標(biāo)編號(hào)查詢出資源實(shí)體
     * @param typeId 類型編號(hào)
     * @param targetId 目標(biāo)編號(hào)
     * @return
     */
    List<CommonResourceEntity> findByTypeIdAndTargetId(String typeId, String targetId);
}
  • 資源類型數(shù)據(jù)接口
/**
 * 資源類型數(shù)據(jù)接口
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:上午11:32
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
public interface CommonResourceTypeRepository
    extends JpaRepository<CommonResourceTypeEntity,String>
{
    /**
     * 根據(jù)類別標(biāo)識(shí)查詢
     * @param flag 資源類型標(biāo)識(shí)
     * @return
     */
    CommonResourceTypeEntity findTopByFlag(String flag);
}

接下來(lái)我們開(kāi)始編寫根據(jù)資源類型獲取指定目標(biāo)編號(hào)的資源列表業(yè)務(wù)邏輯方法,創(chuàng)建名為CommonResourceService統(tǒng)一資源業(yè)務(wù)邏輯實(shí)現(xiàn)類,如下所示:

/**
 * 公共資源業(yè)務(wù)邏輯實(shí)現(xiàn)類
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:下午4:18
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class CommonResourceService {
    /**
     * 資源類型數(shù)據(jù)接口
     */
    @Autowired
    private CommonResourceTypeRepository resourceTypeRepository;
    /**
     * 資源數(shù)據(jù)接口
     */
    @Autowired
    private CommonResourceRepository resourceRepository;

    /**
     * 根據(jù)資源標(biāo)識(shí) & 所屬目標(biāo)編號(hào)查詢資源路徑路邊
     *
     * @param resourceFlag 資源標(biāo)識(shí)
     * @param targetId     目標(biāo)編號(hào)
     * @return
     */
    public List<String> selectUrlsByFlag(CommonResourceFlag resourceFlag, String targetId) throws Exception {
        /**
         * 獲取資源類型
         */
        CommonResourceTypeEntity resourceType = selectResourceTypeByFlag(resourceFlag);
        /**
         * 查詢?cè)撃繕?biāo)編號(hào) & 類型的資源列表
         */
        List<CommonResourceEntity> resources = resourceRepository.findByTypeIdAndTargetId(resourceType.getId(), targetId);

        return convertUrl(resources);
    }

    /**
     * 轉(zhuǎn)換路徑
     * 通過(guò)實(shí)體集合轉(zhuǎn)換成路徑集合
     *
     * @param resources 資源實(shí)體列表
     * @return
     */
    List<String> convertUrl(List<CommonResourceEntity> resources) {
        List<String> urls = null;
        if (!ObjectUtils.isEmpty(resources)) {
            urls = new ArrayList();
            for (CommonResourceEntity resource : resources) {
                urls.add(resource.getResourceUrl());
            }
        }

        return urls;
    }

    /**
     * 根據(jù)資源類型標(biāo)識(shí)查詢資源類型基本信息
     *
     * @param resourceFlag 資源類型標(biāo)識(shí)
     * @return
     * @throws Exception
     */
    CommonResourceTypeEntity selectResourceTypeByFlag(CommonResourceFlag resourceFlag) throws Exception {
        /**
         * 查詢資源類型
         */
        CommonResourceTypeEntity resourceType = resourceTypeRepository.findTopByFlag(resourceFlag.getName());
        if (ObjectUtils.isEmpty(resourceFlag)) {
            throw new Exception("未查詢到資源");
        }
        return resourceType;
    }

}

CommonResourceService提供了對(duì)外的方法selectUrlsByFlag可以查詢指定目標(biāo)編號(hào) & 指定類型的多個(gè)資源地址。

統(tǒng)一資源映射

common-resource子模塊項(xiàng)目?jī)?nèi)添加統(tǒng)一資源的相關(guān)映射內(nèi)容,我們預(yù)計(jì)的目標(biāo)效果是根據(jù)我們自定義的注解結(jié)合AOP來(lái)實(shí)現(xiàn)指定方法的結(jié)果處理映射,我們需要?jiǎng)?chuàng)建兩個(gè)自定義的注解來(lái)完成我們的預(yù)想效果,注解分別為:ResourceFieldResourceMethod,下面我們來(lái)看看ResourceField注解的屬性定義,如下所示:


/**
 * 配置統(tǒng)一資源字段
 * 該注解配置在普通字段上,根據(jù)配置信息自動(dòng)查詢對(duì)應(yīng)的資源地址
 * Demo:
 *
 * @ResourceField(flag=CommonResourceFlagEnum.SHOP_COVER_IMG)
 * private String shopCoverImage;
 *
 * 其中multiple不需要配置,因?yàn)榉饷嬷挥幸粡垼褂媚J(rèn)值即可
 * flag設(shè)置為對(duì)應(yīng)的資源標(biāo)識(shí),資源類型不存在時(shí)不執(zhí)行查詢
 * @ResourceTargetId 如果注解不存在或目標(biāo)編號(hào)不存在或者為null、""時(shí)不執(zhí)行查詢資源
 *
 * @author:于起宇 <br/>
 * ===============================
 * Created with Eclipse.
 * Date:2017/12/31
 * Time:13:11
 * 簡(jiǎn)書(shū):http://www.itdecent.cn/u/092df3f77bca
 * ================================
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface ResourceField {

    /**
     * 讀取資源是單條或者多條
     * true:讀取多條資源地址,對(duì)應(yīng)設(shè)置到List<String>集合內(nèi)
     * false:讀取單條資源地址,對(duì)應(yīng)設(shè)置配置ResourceField注解的字段value
     * @return
     */
    boolean multiple() default false;

    /**
     * 配置讀取統(tǒng)一資源的標(biāo)識(shí)類型
     * @return
     */
    CommonResourceFlag flag();

    /**
     * 如果配置該字段則不會(huì)去找@Id配置的字段
     * 該字段默認(rèn)為空,則默認(rèn)使用@Id標(biāo)注的字段的值作為查詢統(tǒng)一資源的target_id
     * @return
     */
    String targetIdField() default "";
}

ResourceField注解用于配置在查詢結(jié)果的字段上,如:我們查詢用戶頭像時(shí)定義的字段為userHeadImage,我們這時(shí)僅僅需要在userHeadImage字段上添加ResourceField即可。
另外一個(gè)注解ResourceMethod的作用僅僅是為了AOP根據(jù)該注解切面方法,也是只有被該注解切面的方法才會(huì)去執(zhí)行AOP切面方法的返回值進(jìn)行處理,代碼如下所示:

/**
 * 配置指定方法將會(huì)被AOP切面類ResourceAspect所攔截
 * 攔截后會(huì)根據(jù)自定義注解進(jìn)行查詢資源 & 設(shè)置資源等邏輯
 * @author:于起宇 <br/>
 * ===============================
 * Created with Eclipse.
 * Date:2017/12/15
 * Time:14:04
 * 簡(jiǎn)書(shū):http://www.itdecent.cn/u/092df3f77bca
 * ================================
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface ResourceMethod { }

我們的自定義注解已經(jīng)編寫完成,轉(zhuǎn)過(guò)頭來(lái)我們先看看@Around切面方法所需要的邏輯實(shí)現(xiàn)方法,創(chuàng)建ResourcePushService接口添加如下兩個(gè)方法:


/**
 * 統(tǒng)一資源設(shè)置業(yè)務(wù)邏輯定義接口
 * @author:于起宇 <br/>
 * ===============================
 * Created with Eclipse.
 * Date:2017/12/15
 * Time:14:58
 * 簡(jiǎn)書(shū):http://www.itdecent.cn/u/092df3f77bca
 * ================================
 */
public interface ResourcePushService
{
    /**
     * 設(shè)置單個(gè)實(shí)例的資源信息
     * @param object 需要設(shè)置資源的實(shí)例
     */
    void push(Object object) throws Exception;

    /**
     * 設(shè)置多個(gè)實(shí)例的資源信息
     * @param objectList 需要設(shè)置資源的實(shí)例列表
     */
    void push(List<Object> objectList) throws Exception;
}

分別提供了設(shè)置單個(gè)、多個(gè)資源的方法,由于實(shí)現(xiàn)類內(nèi)容比較多這里就不貼出具體的實(shí)現(xiàn)代碼了,詳細(xì)請(qǐng)下載源碼進(jìn)行查看,源碼地址:spring-boot-chapter內(nèi)的Chapter44項(xiàng)目。

資源切面類

我們一直都在說(shuō)資源統(tǒng)一切面映射,那么我們的資源的切面該如何去配置切面切入點(diǎn)呢?在之前我們創(chuàng)建了ResourceMethod注解,我們就用它作為方法切入點(diǎn)完成切面的環(huán)繞實(shí)現(xiàn), ResourceAspect代碼如下所示:

/**
 * 統(tǒng)一資源Aop切面定義
 * 根據(jù)自定義注解配置自動(dòng)設(shè)置配置的資源類型到指定的字段
 * @author:于起宇 <br/>
 * ===============================
 * Created with Eclipse.
 * Date:2017/12/15
 * Time:14:05
 * 簡(jiǎn)書(shū):http://www.itdecent.cn/u/092df3f77bca
 * ================================
 */
@Component
@Aspect
public class ResourceAspect
{
    /**
     * logback
     */
    Logger logger = LoggerFactory.getLogger(ResourceAspect.class);

    /**
     * 資源處理業(yè)務(wù)邏輯
     */
    @Autowired
    @Qualifier("ResourcePushSupport")
    ResourcePushService resourcePushService;

    /**
     * 資源設(shè)置切面方法
     * 攔截配置了@ResourceMethod注解的class method,cglib僅支持class 方法切面,接口切面不支持
     * @param proceedingJoinPoint 切面方法實(shí)例
     * @param resourceMethod 方法注解實(shí)例
     * @return
     * @throws Throwable
     */
    @Around(value = "@annotation(resourceMethod)")
    public Object resourcePutAround(ProceedingJoinPoint proceedingJoinPoint, ResourceMethod resourceMethod)
        throws Throwable
    {
        logger.info("開(kāi)始處理資源自動(dòng)設(shè)置Aop切面邏輯");
        /**
         * 執(zhí)行方法,獲取返回值
         */
        Object result = proceedingJoinPoint.proceed();
        if(StringUtils.isEmpty(result)) {return result;}
        /**
         * 返回值為L(zhǎng)ist集合時(shí)
         */
        if(result instanceof List) {
            List<Object> list = (List<Object>) result;
            resourcePushService.push(list);
        }
        /**
         * 返回值為單值時(shí),返回的實(shí)例類型必須繼承BaseEntity
         */
        else if(result instanceof BaseEntity) {
            resourcePushService.push(result);
        }
        logger.info("資源自動(dòng)設(shè)置Aop切面邏輯處理完成.");
        return result;
    }
}

切面環(huán)繞方法resourcePutAround大致流程為:

  1. 執(zhí)行需要切面的方法,獲取方法結(jié)果
  2. 根據(jù)方法返回的結(jié)果判斷是單個(gè)、多個(gè)對(duì)象進(jìn)行調(diào)用不同的方法
  3. 統(tǒng)一資源方法自動(dòng)根據(jù)@ResourceField注解配置信息以及對(duì)象類型配置@Id字段的值作為目標(biāo)對(duì)象編號(hào)設(shè)置資源到返回對(duì)象內(nèi)。
  4. 返回處理后的對(duì)象實(shí)例

為了方便配置我們?cè)?code>@ResourceField注解內(nèi)添加了CommonResourceFlag枚舉類型的flag屬性,該屬性就是配置了資源類型的標(biāo)識(shí),切面會(huì)根據(jù)該標(biāo)識(shí)去查詢資源的類型編號(hào),再拿著資源類型的編號(hào) & 目標(biāo)編號(hào)去查詢資源列表,CommonResourceFlag枚舉代碼如下所示:

/**
 * 資源標(biāo)識(shí)枚舉
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:下午3:40
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@Getter
public enum CommonResourceFlag
{
    /**
     * 用戶頭像
     */
    USER_HEAD_IMAGE("USER_HEAD_IMAGE"),
    /**
     * 用戶背景圖片
     */
    USER_BACK_IMAGE("USER_BACK_IMAGE");
    private String name;

    CommonResourceFlag(String name) {
        this.name = name;
    }
}

以上我們簡(jiǎn)單介紹了common-resource子模塊的核心內(nèi)容以及基本的運(yùn)行流程原理,下面我們來(lái)創(chuàng)建一個(gè)user-provider子模塊來(lái)使用同一資源查詢用戶的頭像、用戶背景圖片列表。

user-provider

user-provider子模塊目?jī)?nèi)我們預(yù)計(jì)添加一個(gè)查詢用戶詳情的方法,在方法上配置@ResourceMethod注解,這樣可以讓切面切到該方法,然后在查詢用戶詳情方法返回的對(duì)象類型內(nèi)字段上添加@ResourceField注解并添加對(duì)應(yīng)的資源類型標(biāo)識(shí)配置,這樣我們就可以實(shí)現(xiàn)資源的自動(dòng)映射。

由于該模塊需要數(shù)據(jù)庫(kù)的支持,在application.yml配置文件內(nèi)添加對(duì)應(yīng)的數(shù)據(jù)庫(kù)鏈接配置信息,如下所示:

#數(shù)據(jù)源配置
spring:
  jpa:
    properties:
      hibernate:
        show_sql: true
        format_sql: true
  datasource:
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: 123456
      url: jdbc:mysql://127.0.0.1:3306/resources?characterEncoding=utf8

配置文件內(nèi)使用的druidalibaba針對(duì)SpringBoot封裝的jar,提供了yml配置文件相關(guān)支持以及提示。

用戶實(shí)體構(gòu)建

針對(duì)數(shù)據(jù)庫(kù)內(nèi)的用戶基本信息表我們需要?jiǎng)?chuàng)建對(duì)應(yīng)的Entity實(shí)體,代碼如下所示:

/**
 * 用戶基本信息實(shí)體
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:上午11:18
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
@Entity
@Table(name = "hy_user_info")
public class UserInfoEntity
    extends BaseEntity
{
    /**
     * 用戶編號(hào)
     */
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    @Column(name = "UI_ID")
    private String userId;
    /**
     * 用戶名
     */
    @Column(name = "UI_NAME")
    private String userName;
    /**
     * 昵稱
     */
    @Column(name = "UI_NICK_NAME")
    private String nickName;
    /**
     * 年齡
     */
    @Column(name = "UI_AGE")
    private int age;
    /**
     * 所居地
     */
    @Column(name = "UI_ADDRESS")
    private String address;
}

由于我們的用戶頭像以及用戶背景圖片并沒(méi)有在用戶基本信息表內(nèi)所以我們需要單獨(dú)創(chuàng)建一個(gè)用戶詳情實(shí)體并繼承用戶基本信息實(shí)體,如下所示:

/**
 * 用戶詳情dto映射實(shí)體
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:上午11:54
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
public class UserDetailDTO
    extends UserInfoEntity
{
    /**
     * 用戶頭像
     */
    @ResourceField(flag = CommonResourceFlag.USER_HEAD_IMAGE)
    private String headImage;
    /**
     * 背景圖片
     */
    @ResourceField(flag = CommonResourceFlag.USER_BACK_IMAGE,multiple = true)
    private List<String> backImage;
}

在上面實(shí)體內(nèi)我們僅僅是配置了字段所需的資源類型枚舉。

我們一般在開(kāi)發(fā)過(guò)程中,用戶表內(nèi)對(duì)應(yīng)的實(shí)體是不允許根據(jù)業(yè)務(wù)邏輯修改的,如果你需要變動(dòng)需要繼承實(shí)體后添加對(duì)應(yīng)的字段即可。

  • 用戶數(shù)據(jù)接口
/**
 * 用戶基本信息數(shù)據(jù)接口
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:上午11:30
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
public interface UserInfoRepository
    extends JpaRepository<UserInfoEntity,String>
{
    /**
     * 根據(jù)用戶名稱查詢
     * @param userName
     * @return
     */
    UserInfoEntity findUserInfoEntityByUserName(String userName);
}
  • 用戶業(yè)務(wù)邏輯實(shí)現(xiàn)
/**
 * 用戶基本信息業(yè)務(wù)邏輯實(shí)現(xiàn)
 *
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:上午11:53
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class UserInfoService {
    /**
     * 用戶數(shù)據(jù)接口
     */
    @Autowired
    private UserInfoRepository userInfoRepository;

    /**
     * 更新用戶名稱查詢用戶詳情
     * @param userName 用戶名
     * @return
     */
    @ResourceMethod
    public UserDetailDTO selectByUserName(String userName) {
        /**
         * 獲取用戶基本信息
         */
        UserInfoEntity userInfoEntity = userInfoRepository.findUserInfoEntityByUserName(userName);
        /**
         * 通過(guò)mapStruct轉(zhuǎn)換detailDto
         */
        UserDetailDTO detailDTO = UserMapStruct.INSTANCE.fromUserEntity(userInfoEntity);
        return detailDTO;
    }
}

我們?cè)诜椒?code>selectByUserName上配置了@ResourceMethod,讓統(tǒng)一資源可以切面到該方法上,在selectByUserName方法內(nèi)我們只需要去處理根據(jù)用戶名查詢的業(yè)務(wù)邏輯,通過(guò)MapStruct進(jìn)行UserInfoEntityUserDetailDTO轉(zhuǎn)換。在方法返回對(duì)象時(shí)就會(huì)被資源自動(dòng)處理分別將查詢到的資源設(shè)置到UserDetailDTO內(nèi)的headImagebackImage。

  • 用戶控制器
    我們?cè)诳刂破鲀?nèi)添加一個(gè)根據(jù)用戶名查詢用戶詳情的方法,如下所示:
/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:下午3:09
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@RestController
@RequestMapping(value = "/user")
public class UserInfoController
{
    /**
     * 用戶基本信息業(yè)務(wù)邏輯實(shí)現(xiàn)
     */
    @Autowired
    private UserInfoService userInfoService;

    /**
     * 根據(jù)用戶名查詢?cè)斍?     * @param userName 用戶名
     * @return
     */
    @RequestMapping(value = "/{userName}",method = RequestMethod.GET)
    public UserDetailDTO detail(@PathVariable("userName") String userName)
    {
        return userInfoService.selectByUserName(userName);
    }
}

下面我們來(lái)編寫一個(gè)測(cè)試用例,查看是否能夠達(dá)到我們預(yù)計(jì)的效果。

測(cè)試

我們?cè)?code>src/test下創(chuàng)建一個(gè)名為CommonResourceTester測(cè)試類,代碼如下所示:

/**
 * 測(cè)試用例
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/12/31
 * Time:下午5:04
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@SpringBootTest(classes = Chapter44Application.class)
@RunWith(SpringRunner.class)
public class CommonResourceTester
{
    /**
     * 模擬mvc測(cè)試對(duì)象
     */
    private MockMvc mockMvc;

    /**
     * web項(xiàng)目上下文
     */
    @Autowired
    private WebApplicationContext webApplicationContext;

    /**
     * 所有測(cè)試方法執(zhí)行之前執(zhí)行該方法
     */
    @Before
    public void before() {
        //獲取mockmvc對(duì)象實(shí)例
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    /**
     * 測(cè)試查詢用戶詳情
     * @throws Exception
     */
    @Test
    public void selectDetail() throws Exception
    {
        /**
         * 發(fā)起獲取請(qǐng)求
         */
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/hengboy"))
        .andDo(MockMvcResultHandlers.log())
        .andReturn();

        int status = mvcResult.getResponse().getStatus();

        mvcResult.getResponse().setCharacterEncoding("UTF-8");
        String responseString = mvcResult.getResponse().getContentAsString();

        Assert.assertEquals("請(qǐng)求錯(cuò)誤", 200, status);

        System.out.println(responseString);
    }
}

接下來(lái)我們執(zhí)行selectDetail測(cè)試方法,看下控制臺(tái)輸出對(duì)應(yīng)的 JSON內(nèi)容,格式化后如下所示:

{
    "userId": "bc4c8e38-edd6-11e7-969c-3c15c2e4a8a6", 
    "userName": "hengboy", 
    "nickName": "恒宇少年", 
    "age": 23, 
    "address": "山東省濟(jì)南市", 
    "headImage": "https://upload.jianshu.io/users/upload_avatars/4461954/f09ba256-f6db-41ed-a4ac-b2d23737f0ac.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96", 
    "backImage": [
        "https://upload.jianshu.io/collections/images/358868/android.graphics.Bitmap_d88b4de.jpeg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240", 
        "https://upload.jianshu.io/collections/images/522928/kafka_diagram.png?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240"
    ]
}

根據(jù)結(jié)果我們可以看到,我們已經(jīng)自動(dòng)的讀取了配置的資源列表,也通過(guò)反射自動(dòng)設(shè)置到字段內(nèi)。

總結(jié)

本章的代碼比較多,還是建議大家根據(jù)源碼比對(duì)學(xué)習(xí),這種方式也是我們?cè)谄綍r(shí)開(kāi)發(fā)中總結(jié)出來(lái)的,我們僅僅需要配置下@ResourceField以及@ResourceMethod就可以了完成資源的自動(dòng)映射,資源與業(yè)務(wù)邏輯的耦合度得到的很好的降低。

本章源碼已經(jīng)上傳到碼云:
SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源碼地址:[https://gitee.com/hengboy/spring-cloud-chapter]

作者個(gè)人 博客
使用開(kāi)源框架 ApiBoot 助你成為Api接口服務(wù)架構(gòu)師

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

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

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