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

本章內(nèi)容比較偏向系統(tǒng)設計方面拭宁,簡單的封裝就可以應用到系統(tǒng)中使用跳昼,從而提高我們的編碼效率以及代碼的可讀性姓迅。統(tǒng)一資源在系統(tǒng)內(nèi)是不可避免的模塊敲霍,資源分類也有很多種,比較常見如:圖片資源丁存、文本資源色冀、視頻資源等,那么資源統(tǒng)一處理的好處是什么呢柱嫌?大家有可能會有疑問,我把資源存放到業(yè)務表內(nèi)豈不更好嗎屯换?這樣查詢起來也方便编丘,并不需要關聯(lián)資源信息表!當然設計不分好壞彤悔,只有更適合嘉抓、更簡單!接下來帶著疑問進入本章的內(nèi)容晕窑。

免費教程專題

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

本章目標

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

構建項目

本章使用到的依賴相對來說比較多疾牲,大致:Web植捎、MapStructSpringDataJpa阳柔、LomBok等焰枢,數(shù)據(jù)庫方面采用MySQL來作為數(shù)據(jù)支持。

SpringBoot 企業(yè)級核心技術學習專題


專題 專題名稱 專題描述
001 Spring Boot 核心技術 講解SpringBoot一些企業(yè)級層面的核心組件
002 Spring Boot 核心技術章節(jié)源碼 Spring Boot 核心技術簡書每一篇文章碼云對應源碼
003 Spring Cloud 核心技術 對Spring Cloud核心技術全面講解
004 Spring Cloud 核心技術章節(jié)源碼 Spring Cloud 核心技術簡書每一篇文章對應源碼
005 QueryDSL 核心技術 全面講解QueryDSL核心技術以及基于SpringBoot整合SpringDataJPA
006 SpringDataJPA 核心技術 全面講解SpringDataJPA核心技術
007 SpringBoot核心技術學習目錄 SpringBoot系統(tǒng)的學習目錄舌剂,敬請關注點贊<贸!!

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

本章用到的數(shù)據(jù)表結構以及初始化的數(shù)據(jù)之前都是放在項目的resources目錄下霍转,為了大家使用方面我在這里直接貼出來荐绝,如下所示:

--
-- 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 '所屬目標編號,關聯(lián)其他信息表主鍵谴忧,如:用戶頭像關聯(lián)用戶編號',
  `CR_TYPE_ID` varchar(36) DEFAULT NULL COMMENT '資源類型編號',
  `CR_URL` varchar(200) DEFAULT 'NULL' COMMENT '資源路徑很泊,如:圖片地址',
  `CR_CREATE_TIME` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '資源添加時間',
  `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 '類型編號',
  `CRT_NAME` varchar(20) DEFAULT NULL COMMENT '類型名稱',
  `CRT_FLAG` varchar(30) DEFAULT NULL COMMENT '資源標識',
  `CRT_CREATE_TIME` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '創(chuàng)建時間',
  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,'山東省濟南市');
/*!40000 ALTER TABLE `hy_user_info` ENABLE KEYS */;
UNLOCK TABLES;

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

搭建項目

本章我們把統(tǒ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>
....//

接下來我們開始創(chuàng)建common-resource子模塊昏兆,將資源處理完全獨立出來枫虏,在創(chuàng)建子模塊時要注意package命名要保證可以被SpringBoot運行時掃描到!E朗隶债!

common-resource

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

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

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

  • 資源實體
/**
 * 資源實體
 * @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
{
    /**
     * 資源編號
     */
    @Column(name = "CR_ID")
    @Id
    @GeneratedValue
    private Integer resourceId;
    /**
     * 資源所屬目標編號
     */
    @Column(name = "CR_TARGET_ID")
    private String targetId;
    /**
     * 類型編號
     */
    @Column(name = "CR_TYPE_ID")
    private String typeId;
    /**
     * 資源路徑
     */
    @Column(name = "CR_URL")
    private String resourceUrl;
    /**
     * 創(chuàng)建時間
     */
    @Column(name = "CR_CREATE_TIME")
    private Timestamp createTime;
    /**
     * 排序
     */
    @Column(name = "CR_ORDER")
    private int order;
}
  • 資源類型實體
/**
 * 資源類型實體
 * @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
{
    /**
     * 類型編號
     */
    @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;
    /**
     * 類型標識
     */
    @Column(name = "CRT_FLAG")
    private String flag;
    /**
     * 類型添加時間
     */
    @Column(name = "CRT_CREATE_TIME")
    private Timestamp createTime;
}

下面我們來創(chuàng)建對應實體的數(shù)據(jù)接口赞警,我們采用SpringDataJPA的方法名查詢規(guī)則來查詢對應的數(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ù)類型編號 & 目標編號查詢出資源實體
     * @param typeId 類型編號
     * @param targetId 目標編號
     * @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ù)類別標識查詢
     * @param flag 資源類型標識
     * @return
     */
    CommonResourceTypeEntity findTopByFlag(String flag);
}

接下來我們開始編寫根據(jù)資源類型獲取指定目標編號的資源列表業(yè)務邏輯方法虏两,創(chuàng)建名為CommonResourceService統(tǒng)一資源業(yè)務邏輯實現(xiàn)類愧旦,如下所示:

/**
 * 公共資源業(yè)務邏輯實現(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ù)資源標識 & 所屬目標編號查詢資源路徑路邊
     *
     * @param resourceFlag 資源標識
     * @param targetId     目標編號
     * @return
     */
    public List<String> selectUrlsByFlag(CommonResourceFlag resourceFlag, String targetId) throws Exception {
        /**
         * 獲取資源類型
         */
        CommonResourceTypeEntity resourceType = selectResourceTypeByFlag(resourceFlag);
        /**
         * 查詢該目標編號 & 類型的資源列表
         */
        List<CommonResourceEntity> resources = resourceRepository.findByTypeIdAndTargetId(resourceType.getId(), targetId);

        return convertUrl(resources);
    }

    /**
     * 轉(zhuǎn)換路徑
     * 通過實體集合轉(zhuǎn)換成路徑集合
     *
     * @param resources 資源實體列表
     * @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ù)資源類型標識查詢資源類型基本信息
     *
     * @param resourceFlag 資源類型標識
     * @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提供了對外的方法selectUrlsByFlag可以查詢指定目標編號 & 指定類型的多個資源地址。

統(tǒng)一資源映射

common-resource子模塊項目內(nèi)添加統(tǒng)一資源的相關映射內(nèi)容定罢,我們預計的目標效果是根據(jù)我們自定義的注解結合AOP來實現(xiàn)指定方法的結果處理映射笤虫,我們需要創(chuàng)建兩個自定義的注解來完成我們的預想效果,注解分別為:ResourceField祖凫、ResourceMethod琼蚯,下面我們來看看ResourceField注解的屬性定義,如下所示:


/**
 * 配置統(tǒng)一資源字段
 * 該注解配置在普通字段上惠况,根據(jù)配置信息自動查詢對應的資源地址
 * Demo:
 *
 * @ResourceField(flag=CommonResourceFlagEnum.SHOP_COVER_IMG)
 * private String shopCoverImage;
 *
 * 其中multiple不需要配置凌停,因為封面只有一張,使用默認值即可
 * flag設置為對應的資源標識售滤,資源類型不存在時不執(zhí)行查詢
 * @ResourceTargetId 如果注解不存在或目標編號不存在或者為null罚拟、""時不執(zhí)行查詢資源
 *
 * @author:于起宇 <br/>
 * ===============================
 * Created with Eclipse.
 * Date:2017/12/31
 * Time:13:11
 * 簡書:http://www.reibang.com/u/092df3f77bca
 * ================================
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface ResourceField {

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

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

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

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

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

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


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

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

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

資源切面類

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

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

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

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

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

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

為了方便配置我們在@ResourceField注解內(nèi)添加了CommonResourceFlag枚舉類型的flag屬性编整,該屬性就是配置了資源類型的標識,切面會根據(jù)該標識去查詢資源的類型編號乳丰,再拿著資源類型的編號 & 目標編號去查詢資源列表掌测,CommonResourceFlag枚舉代碼如下所示:

/**
 * 資源標識枚舉
 * ========================
 * 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;
    }
}

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

user-provider

user-provider子模塊目內(nèi)我們預計添加一個查詢用戶詳情的方法,在方法上配置@ResourceMethod注解什燕,這樣可以讓切面切到該方法粘勒,然后在查詢用戶詳情方法返回的對象類型內(nèi)字段上添加@ResourceField注解并添加對應的資源類型標識配置,這樣我們就可以實現(xiàn)資源的自動映射屎即。

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

#數(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針對SpringBoot封裝的jar技俐,提供了yml配置文件相關支持以及提示乘陪。

用戶實體構建

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

/**
 * 用戶基本信息實體
 * @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
{
    /**
     * 用戶編號
     */
    @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;
}

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

/**
 * 用戶詳情dto映射實體
 * @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;
}

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

我們一般在開發(fā)過程中,用戶表內(nèi)對應的實體是不允許根據(jù)業(yè)務邏輯修改的井赌,如果你需要變動需要繼承實體后添加對應的字段即可谤逼。

  • 用戶數(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è)務邏輯實現(xiàn)
/**
 * 用戶基本信息業(yè)務邏輯實現(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);
        /**
         * 通過mapStruct轉(zhuǎn)換detailDto
         */
        UserDetailDTO detailDTO = UserMapStruct.INSTANCE.fromUserEntity(userInfoEntity);
        return detailDTO;
    }
}

我們在方法selectByUserName上配置了@ResourceMethod贵扰,讓統(tǒng)一資源可以切面到該方法上,在selectByUserName方法內(nèi)我們只需要去處理根據(jù)用戶名查詢的業(yè)務邏輯流部,通過MapStruct進行UserInfoEntityUserDetailDTO轉(zhuǎn)換戚绕。在方法返回對象時就會被資源自動處理分別將查詢到的資源設置到UserDetailDTO內(nèi)的headImagebackImage贵涵。

  • 用戶控制器
    我們在控制器內(nèi)添加一個根據(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è)務邏輯實現(xiàn)
     */
    @Autowired
    private UserInfoService userInfoService;

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

下面我們來編寫一個測試用例,查看是否能夠達到我們預計的效果宾茂。

測試

我們在src/test下創(chuàng)建一個名為CommonResourceTester測試類瓷马,代碼如下所示:

/**
 * 測試用例
 * ========================
 * 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測試對象
     */
    private MockMvc mockMvc;

    /**
     * web項目上下文
     */
    @Autowired
    private WebApplicationContext webApplicationContext;

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

    /**
     * 測試查詢用戶詳情
     * @throws Exception
     */
    @Test
    public void selectDetail() throws Exception
    {
        /**
         * 發(fā)起獲取請求
         */
        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("請求錯誤", 200, status);

        System.out.println(responseString);
    }
}

接下來我們執(zhí)行selectDetail測試方法,看下控制臺輸出對應的 JSON內(nèi)容跨晴,格式化后如下所示:

{
    "userId": "bc4c8e38-edd6-11e7-969c-3c15c2e4a8a6", 
    "userName": "hengboy", 
    "nickName": "恒宇少年", 
    "age": 23, 
    "address": "山東省濟南市", 
    "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ù)結果我們可以看到欧聘,我們已經(jīng)自動的讀取了配置的資源列表,也通過反射自動設置到字段內(nèi)端盆。

總結

本章的代碼比較多怀骤,還是建議大家根據(jù)源碼比對學習,這種方式也是我們在平時開發(fā)中總結出來的焕妙,我們僅僅需要配置下@ResourceField以及@ResourceMethod就可以了完成資源的自動映射蒋伦,資源與業(yè)務邏輯的耦合度得到的很好的降低。

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

作者個人 博客
使用開源框架 ApiBoot 助你成為Api接口服務架構師

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末焚鹊,一起剝皮案震驚了整個濱河市痕届,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌末患,老刑警劉巖研叫,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異璧针,居然都是意外死亡嚷炉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門探橱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來申屹,“玉大人,你說我怎么就攤上這事隧膏《栏蹋” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵私植,是天一觀的道長忌栅。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么索绪? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任湖员,我火速辦了婚禮,結果婚禮上瑞驱,老公的妹妹穿的比我還像新娘娘摔。我一直安慰自己,他們只是感情好唤反,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布凳寺。 她就那樣靜靜地躺著,像睡著了一般彤侍。 火紅的嫁衣襯著肌膚如雪肠缨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天盏阶,我揣著相機與錄音晒奕,去河邊找鬼。 笑死名斟,一個胖子當著我的面吹牛脑慧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播砰盐,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼闷袒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了岩梳?” 一聲冷哼從身側響起囊骤,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蒋腮,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體藕各,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡池摧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了激况。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片作彤。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖乌逐,靈堂內(nèi)的尸體忽然破棺而出竭讳,到底是詐尸還是另有隱情,我是刑警寧澤浙踢,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布绢慢,位于F島的核電站,受9級特大地震影響洛波,放射性物質(zhì)發(fā)生泄漏胰舆。R本人自食惡果不足惜骚露,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缚窿。 院中可真熱鬧棘幸,春花似錦、人聲如沸倦零。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扫茅。三九已至蹋嵌,卻和暖如春驻龟,著一層夾襖步出監(jiān)牢的瞬間柏蘑,已是汗流浹背尉尾。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工梗搅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挠说,地道東北人屋彪。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓囤采,卻偏偏與公主長得像需曾,于是被迫代替她去往敵國和親慧起。 傳聞我的和親對象是個殘疾皇子菇晃,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

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