俗话说:“无规矩不成方圆”,在我们软件开发过程中也是这样的要求,特别是对大型项目必须制定诸多的开发规范,有的是通过文件制度的形式要求,有的是通过工程框架要求,我个人认为要好的贯彻执行只能靠工程框架约束,否则每个工程师对文件制度都有不同的认识,长此以往这些规范制度就形同虚设了,而且通过工程框架约束还能够通过一些检查工具自动校验,最大限度的降低了人为的偏差。
今天就不多说编码的规范了,这个已经是业界公认的标准了,今天要介绍的是对公司大型项目如何通过工程框架来实现统一的规范约束。
实现统一规范的方案
-
统一结构的工程脚手架
这个是通过工程架构约束的基础能力,就如同盖大楼一样,主体结构是大楼的基本体现,要想快速有效的落实基础架构,工程脚手架是必须的第一步。这里推荐的脚手架结构是以项目为目标,功能为模块的两层结构,把每个模块通用的部分放到项目层面,每个模块本身只包含项目特有的结构。
- 以jar包封装一些统一的规范能力
项目中要用的统一能力可以通过集中封装的方式集成到一些独立分发的jar包中提供给项目的其他工程使用,这样就把一些统一的规范集中管控,从而缩小了泛化的可能性,当然同时也抑制了工程的自由度,这个要根据项目的实际情况做一个平衡。Springboot提供了更好的方式来实现这个目标,就是starter,这个也是本文重点介绍的方式。 - 以微服务方式提供黑箱服务
将项目中通用的能力以微服务方式提供,对其他模块的使用更加的隔离,微服务的优势和方法不在本文详述,如果感兴趣可以自行脑补。
自定义starter
Springboot通过starter方式提供了封装更为精准的独立服务能力,让使用方开箱即用。目前很多开源系统都提供了starter方式,这样大大降低了我们使用这些优秀开源系统的门槛,也极大提高了开发效率,那么我们项目自身是否也可以用这样的方式来实现一些通用功能呢?答案是肯定的,而且过程也非常的简单,让原先以jar包分发的方式更加的优雅和简单。
starter工程和其他的Springboot工程结构没有什么区别,下面就把一些特殊的要求罗列一下。
- 命名规范
1.1 官方命名空间(Springboot旗下项目)
前缀:spring-boot-starter-
模式:spring-boot-starter-{模块名}
举例:spring-boot-starter-web、spring-boot-starter-jdbc
1.2 自定义命名空间(非Springboot项目)
后缀:-spring-boot-starter
模式:{模块}-spring-boot-starter
举例:mybatis-spring-boot-starter - 必须引入的依赖包
starter模式和以前的jar包模式的区别是配置装配能力,也就是starter包可以自动从配置文件获取项目本身的配置参数,从而更灵活的适应项目个性,那么就必须依赖这个包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
- 工程入口程序
这个程序是取代普通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("中台服务-基础服务装配开始...");
log.info("运行域:{}",basicProperties.getDomain());
BasicService basicService=new BasicService();
log.info("中台服务-基础服务装配完成。");
return basicService;
}
}
这里指明获取配置的类,需要实例化的bean等,这里装配的bean在使用端就可以通过@Autowired的方式直接使用了,这些注解的详细说明自行搜索,资料很多。
配置文件application.yml如下:
myself:
middle:
domain: bj.myself.middle
- 配置spring.factories,指明starter的主入口
在resources目录下建META-INF目录,创建spinrg.factories文件,文件内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.myself.platform.middle.BasicServiceAutoConfiguration
AOP方式统一服务日志
原先实现统一日志都是放到每个工程中以AOP方式实现,现在有了starter方式,就可以将公司的日志规范集中到这里来统一管理。
- 编写Aspect类
该类的实现和原先的方式没有任何变化,直接放代码如下:
@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 {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
log.info("开始服务:{}", request.getRequestURL().toString());
log.info("客户端IP :{}" , request.getRemoteAddr());
log.info("参数值 :{}",Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
log.info("返回值 : {}" , ret);
}
}
- starter装载类添加AOP Bean方法
AOP如果直接放置到Springboot工程中就可以直接生效,但是通过starter方式提供还得在装载类中Bean化,这点要特别注意,否则不会生效。
@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("中台服务-统一web AOP装配开始...");
log.info("运行域:{}",basicProperties.getDomain());
WebLogAspect webLogAspect=new WebLogAspect();
log.info("中台服务-统一web AOP装配完成。");
return webLogAspect;
}
}
发布
经过以上步骤这个包含了AOP及统一功能的starter就开发完成了,执行mvn install就可以部署到本地maven仓库给其他项目使用了,如果要在全公司使用就通过mvn deploy部署到公司私有仓库。
实际执行效果
2019-12-12 17:29:13.736 INFO [user-base,,,] 1 --- [ main] c.s.p.m.BasicServiceAutoConfiguration : 中台服务-统一web AOP装配开始...
2019-12-12 17:29:13.736 INFO [user-base,,,] 1 --- [ main] c.s.p.m.BasicServiceAutoConfiguration : 运行域:http://bj.myself.com
2019-12-12 17:29:13.736 INFO [user-base,,,] 1 --- [ main] c.s.p.m.BasicServiceAutoConfiguration : 中台服务-统一web AOP装配完成。
看到AOP已经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 : 开始服务: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 : 请求参数 :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 : 参数值 :[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的统一日志已经输出。