本章內(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ù)想效果,注解分別為:ResourceField、ResourceMethod,下面我們來(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大致流程為:
- 執(zhí)行需要切面的方法,獲取方法結(jié)果
- 根據(jù)方法返回的結(jié)果判斷是單個(gè)、多個(gè)對(duì)象進(jìn)行調(diào)用不同的方法
- 統(tǒng)一資源方法自動(dòng)根據(jù)
@ResourceField注解配置信息以及對(duì)象類型配置@Id字段的值作為目標(biāo)對(duì)象編號(hào)設(shè)置資源到返回對(duì)象內(nèi)。 - 返回處理后的對(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)使用的druid是alibaba針對(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)行UserInfoEntity與UserDetailDTO轉(zhuǎn)換。在方法返回對(duì)象時(shí)就會(huì)被資源自動(dòng)處理分別將查詢到的資源設(shè)置到UserDetailDTO內(nèi)的headImage、backImage。
- 用戶控制器
我們?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]