本章內(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
植捎、MapStruct
、SpringDataJpa
阳柔、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
大致流程為:
- 執(zhí)行需要切面的方法嵌屎,獲取方法結果
- 根據(jù)方法返回的結果判斷是單個挠将、多個對象進行調(diào)用不同的方法
- 統(tǒng)一資源方法自動根據(jù)
@ResourceField
注解配置信息以及對象類型配置@Id
字段的值作為目標對象編號設置資源到返回對象內(nèi)。 - 返回處理后的對象實例
為了方便配置我們在@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)使用的druid
是alibaba
針對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
進行UserInfoEntity
與UserDetailDTO
轉(zhuǎn)換戚绕。在方法返回對象時就會被資源自動處理分別將查詢到的資源設置到UserDetailDTO
內(nèi)的headImage
、backImage
贵涵。
- 用戶控制器
我們在控制器內(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]