俗話說:“無規(guī)矩不成方圓”蛉鹿,在我們軟件開發(fā)過程中也是這樣的要求回论,特別是對大型項(xiàng)目必須制定諸多的開發(fā)規(guī)范,有的是通過文件制度的形式要求谱净,有的是通過工程框架要求窑邦,我個(gè)人認(rèn)為要好的貫徹執(zhí)行只能靠工程框架約束,否則每個(gè)工程師對文件制度都有不同的認(rèn)識(shí)壕探,長此以往這些規(guī)范制度就形同虛設(shè)了冈钦,而且通過工程框架約束還能夠通過一些檢查工具自動(dòng)校驗(yàn),最大限度的降低了人為的偏差李请。
今天就不多說編碼的規(guī)范了瞧筛,這個(gè)已經(jīng)是業(yè)界公認(rèn)的標(biāo)準(zhǔn)了,今天要介紹的是對公司大型項(xiàng)目如何通過工程框架來實(shí)現(xiàn)統(tǒng)一的規(guī)范約束导盅。
實(shí)現(xiàn)統(tǒng)一規(guī)范的方案
-
統(tǒng)一結(jié)構(gòu)的工程腳手架
這個(gè)是通過工程架構(gòu)約束的基礎(chǔ)能力驾窟,就如同蓋大樓一樣,主體結(jié)構(gòu)是大樓的基本體現(xiàn)认轨,要想快速有效的落實(shí)基礎(chǔ)架構(gòu)绅络,工程腳手架是必須的第一步。這里推薦的腳手架結(jié)構(gòu)是以項(xiàng)目為目標(biāo)嘁字,功能為模塊的兩層結(jié)構(gòu)恩急,把每個(gè)模塊通用的部分放到項(xiàng)目層面,每個(gè)模塊本身只包含項(xiàng)目特有的結(jié)構(gòu)纪蜒。
- 以jar包封裝一些統(tǒng)一的規(guī)范能力
項(xiàng)目中要用的統(tǒng)一能力可以通過集中封裝的方式集成到一些獨(dú)立分發(fā)的jar包中提供給項(xiàng)目的其他工程使用衷恭,這樣就把一些統(tǒng)一的規(guī)范集中管控,從而縮小了泛化的可能性纯续,當(dāng)然同時(shí)也抑制了工程的自由度随珠,這個(gè)要根據(jù)項(xiàng)目的實(shí)際情況做一個(gè)平衡。Springboot提供了更好的方式來實(shí)現(xiàn)這個(gè)目標(biāo)猬错,就是starter窗看,這個(gè)也是本文重點(diǎn)介紹的方式。 - 以微服務(wù)方式提供黑箱服務(wù)
將項(xiàng)目中通用的能力以微服務(wù)方式提供倦炒,對其他模塊的使用更加的隔離显沈,微服務(wù)的優(yōu)勢和方法不在本文詳述,如果感興趣可以自行腦補(bǔ)逢唤。
自定義starter
Springboot通過starter方式提供了封裝更為精準(zhǔn)的獨(dú)立服務(wù)能力拉讯,讓使用方開箱即用。目前很多開源系統(tǒng)都提供了starter方式鳖藕,這樣大大降低了我們使用這些優(yōu)秀開源系統(tǒng)的門檻魔慷,也極大提高了開發(fā)效率,那么我們項(xiàng)目自身是否也可以用這樣的方式來實(shí)現(xiàn)一些通用功能呢著恩?答案是肯定的院尔,而且過程也非常的簡單纹烹,讓原先以jar包分發(fā)的方式更加的優(yōu)雅和簡單。
starter工程和其他的Springboot工程結(jié)構(gòu)沒有什么區(qū)別召边,下面就把一些特殊的要求羅列一下铺呵。
- 命名規(guī)范
1.1 官方命名空間(Springboot旗下項(xiàng)目)
前綴:spring-boot-starter-
模式:spring-boot-starter-{模塊名}
舉例:spring-boot-starter-web、spring-boot-starter-jdbc
1.2 自定義命名空間(非Springboot項(xiàng)目)
后綴:-spring-boot-starter
模式:{模塊}-spring-boot-starter
舉例:mybatis-spring-boot-starter - 必須引入的依賴包
starter模式和以前的jar包模式的區(qū)別是配置裝配能力隧熙,也就是starter包可以自動(dòng)從配置文件獲取項(xiàng)目本身的配置參數(shù)片挂,從而更靈活的適應(yīng)項(xiàng)目個(gè)性,那么就必須依賴這個(gè)包贞盯。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
- 工程入口程序
這個(gè)程序是取代普通Springboot工程的Application的音念,是配置獲取、bean裝配的入口躏敢。
@Slf4j
@Configuration
@EnableConfigurationProperties(BasicProperties.class)
@ConditionalOnClass(BasicService.class)
@ConditionalOnProperty(prefix = "myself.middle", value = "enabled", matchIfMissing = true)
public class BasicServiceAutoConfiguration {
@Autowired
private BasicProperties basicProperties;
@Bean
@ConditionalOnMissingBean(BasicService.class)
public BasicService basicService(){
log.info("中臺(tái)服務(wù)-基礎(chǔ)服務(wù)裝配開始...");
log.info("運(yùn)行域:{}",basicProperties.getDomain());
BasicService basicService=new BasicService();
log.info("中臺(tái)服務(wù)-基礎(chǔ)服務(wù)裝配完成闷愤。");
return basicService;
}
}
這里指明獲取配置的類,需要實(shí)例化的bean等件余,這里裝配的bean在使用端就可以通過@Autowired的方式直接使用了讥脐,這些注解的詳細(xì)說明自行搜索,資料很多啼器。
配置文件application.yml如下:
myself:
middle:
domain: bj.myself.middle
- 配置spring.factories旬渠,指明starter的主入口
在resources目錄下建META-INF目錄,創(chuàng)建spinrg.factories文件端壳,文件內(nèi)容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.myself.platform.middle.BasicServiceAutoConfiguration
AOP方式統(tǒng)一服務(wù)日志
原先實(shí)現(xiàn)統(tǒng)一日志都是放到每個(gè)工程中以AOP方式實(shí)現(xiàn)告丢,現(xiàn)在有了starter方式,就可以將公司的日志規(guī)范集中到這里來統(tǒng)一管理损谦。
- 編寫Aspect類
該類的實(shí)現(xiàn)和原先的方式?jīng)]有任何變化岖免,直接放代碼如下:
@Aspect
@Component
@Slf4j
public class WebLogAspect {
@Pointcut("execution(public * com.myself.platform.middle..web.*.*(..))")
public void webLog(){}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到請求,記錄請求內(nèi)容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 記錄下請求內(nèi)容
log.info("開始服務(wù):{}", request.getRequestURL().toString());
log.info("客戶端IP :{}" , request.getRemoteAddr());
log.info("參數(shù)值 :{}",Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 處理完請求照捡,返回內(nèi)容
log.info("返回值 : {}" , ret);
}
}
- starter裝載類添加AOP Bean方法
AOP如果直接放置到Springboot工程中就可以直接生效颅湘,但是通過starter方式提供還得在裝載類中Bean化,這點(diǎn)要特別注意麻敌,否則不會(huì)生效栅炒。
@Slf4j
@Configuration
@EnableConfigurationProperties(BasicProperties.class)
@ConditionalOnClass(BasicService.class)
@ConditionalOnProperty(prefix = "myself.middle", value = "enabled", matchIfMissing = true)
public class BasicServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean(WebLogAspect.class)
public WebLogAspect webLogAspect(){
log.info("中臺(tái)服務(wù)-統(tǒng)一web AOP裝配開始...");
log.info("運(yùn)行域:{}",basicProperties.getDomain());
WebLogAspect webLogAspect=new WebLogAspect();
log.info("中臺(tái)服務(wù)-統(tǒng)一web AOP裝配完成掂摔。");
return webLogAspect;
}
}
發(fā)布
經(jīng)過以上步驟這個(gè)包含了AOP及統(tǒng)一功能的starter就開發(fā)完成了术羔,執(zhí)行mvn install就可以部署到本地maven倉庫給其他項(xiàng)目使用了,如果要在全公司使用就通過mvn deploy部署到公司私有倉庫乙漓。
實(shí)際執(zhí)行效果
2019-12-12 17:29:13.736 INFO [user-base,,,] 1 --- [ main] c.s.p.m.BasicServiceAutoConfiguration : 中臺(tái)服務(wù)-統(tǒng)一web AOP裝配開始...
2019-12-12 17:29:13.736 INFO [user-base,,,] 1 --- [ main] c.s.p.m.BasicServiceAutoConfiguration : 運(yùn)行域:http://bj.myself.com
2019-12-12 17:29:13.736 INFO [user-base,,,] 1 --- [ main] c.s.p.m.BasicServiceAutoConfiguration : 中臺(tái)服務(wù)-統(tǒng)一web AOP裝配完成级历。
看到AOP已經(jīng)Bean化了。
2019-12-13 09:58:15.007 INFO [user-base,1a66e1fde3654b29,dbaacc6e1b0dd547,true] 1 --- [nio-1010-exec-3] c.s.p.middle.service.WebLogAspect : 開始服務(wù):http://172.16.15.229:1010/readUserinfo
2019-12-13 09:58:15.008 INFO [user-base,1a66e1fde3654b29,dbaacc6e1b0dd547,true] 1 --- [nio-1010-exec-3] c.s.p.middle.service.WebLogAspect : 客戶端IP :172.16.15.229
2019-12-13 09:58:15.008 INFO [user-base,1a66e1fde3654b29,dbaacc6e1b0dd547,true] 1 --- [nio-1010-exec-3] c.s.p.middle.service.WebLogAspect : 請求參數(shù) :org.apache.catalina.util.ParameterMap@22597edd
2019-12-13 09:58:15.008 INFO [user-base,1a66e1fde3654b29,dbaacc6e1b0dd547,true] 1 --- [nio-1010-exec-3] c.s.p.middle.service.WebLogAspect : 參數(shù)值 :[61d4b219-e4e5-4c29-9688-b6759e3e6427]
2019-12-13 09:58:15.072 INFO [user-base,1a66e1fde3654b29,dbaacc6e1b0dd547,true] 1 --- [nio-1010-exec-3] c.s.p.middle.service.WebLogAspect : 返回值 : ResponseData(code=0, message=成功, data={"avatarUrl":"fgjfgjghj","userName":"57547457","userId":"61d4b219-e4e5-4c29-9688-b6759e3e6427","email":"fghjfgjgh"})
可以看到AOP的統(tǒng)一日志已經(jīng)輸出叭披。