之所以想写这一系列,是因为之前工作过程中有几次项目是从零开始搭建的,而且项目涉及的内容还不少。在这过程中,遇到了很多棘手的非业务问题,在不断实践过程中慢慢积累出一些基本的实践经验,认为这些与业务无关的基本的实践经验其实可以复刻到其它项目上,在行业内可能称为脚手架,因此决定将此java基础脚手架的搭建总结下来,分享给大家使用。
注意:由于框架不同版本改造会有些使用的不同,因此本次系列中主要使用基本框架是 spring-boo-2.3.12.RELEASE和spring-cloud.-Hoxton.SR12,所有代码都在commonFramework项目上:https://github.com/forever1986/commonFramework/tree/master
1 日志
日志打印是项目脚手架基本功能,其目的包括统一日志管理,方便日志打印。市面上的日志框架有点多,那么我们如何去理解和选择呢?
1.1 门面日志和实现日志
首先,我们先理解门面日志和实现日志。日志门面是日志实现的抽象层,日志实现是具体日志功能的实现。之所以有这2个东西,主要是为了避免日志框架改动,导致原先代码无法使用,因此使用门面日志定义日志统一的接口,然后其它日志实现根据统一接口进行实现,这样项目中通过使用门面日志与日志实现解耦效果。
常见的门面日志包括slf4j和JCL,JCL已经停止更新,因此现在基本上使用slf4j。
常见的日志实现包括logback、log4j、log4j2 等。
1.2 日志级别
不同的门面日志或者实现日志有着不同的日志级别。但是我们只需要了解以下几种即可:
- TRACE:最低级别的日志记录,用于追踪程序的详细执行路径和调试信息。
- DEBUG:用于记录调试信息,例如变量的值、方法的执行情况等。
- INFO:用于记录程序的正常运行信息,例如应用程序启动、关键操作完成等。
- WARN:用于记录警告信息,表示程序可能存在潜在的问题或异常情况。
- ERROR:最高级别的日志记录,用于记录错误信息和异常情况。
1.3 实践
如果项目中只是引入门面日志,是不会打印日志的,因此需要配合一个日志实现。现在还有一个更为简便封装的框架lombok,其本身为了让实体类通过注解的方式提供有参构造、无参构造、get、set、tostring等方法,比如在类前面使用@Data则表示该类中的所有变量有get和set方法。而lombok另外一个功能就是提供实现slf4j的注解,通过@Slf4j注解到某个类,则等同于注入logger类到该类下面:
private final static Logger log= LoggerFactory.getLogger(XXXX.class);
在代码中可以使用log.xxx即可打印日志。在实践中,我们一般通过AOP切面功能自动打印日志,也通过实现注解方式注入类和方法的一些标识。下面通过引入spring-boot-starter-web依赖(默认有logback依赖)和lombok模块(引入slf4j依赖)来实现该功能
请参照common-log和manage-biz子模块
1)在commonFramework项目下面建立common子模块,在common子模块下面建立common-log(该子模块以spring.factories方式引入项目)。common-log子模块的功能是引入lombok依赖,同时实现AOP切面日志自动打印以及实现注解方式对一些类和方法进行标识
2)common-log子模块的pom文件引入如下依赖:
<!-- AOP切面 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 日志门面 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 获取Http Servlet相关配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3)设置spring.factories和configure类
在resources下面的META-INF下面建立spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.common.log.config.LogConfiguration
配置LogConfiguration 日志配置
package com.demo.common.log.config;
import com.demo.common.log.aspect.LogAspect;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LogConfiguration {
@Bean
@ConditionalOnMissingBean
public LogAspect logAspect() {
return new LogAspect();
}
}
4)设置AOP的切点LogAspect.java
package com.demo.common.log.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
@Aspect
@Slf4j
public class LogAspect {
@Pointcut("execution(public * com.demo..*.controller..*.*(..)) || @annotation(com.demo.common.log.aspect.SysLog)") //controller包下的所有方法都打日剧
public void logPointCut(){
}
@Before("logPointCut()")
public void doBefore(JoinPoint joinPoint){
log.info("方法执行前...");
SysLog sysLog = getAnnotationLog(joinPoint);
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(sra != null){
HttpServletRequest request = sra.getRequest();
log.info("ip:" + request.getRemoteHost());
log.info("url:" + request.getRequestURI());
log.info("method:"+request.getMethod());
log.info("class.method:" + joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName());
log.info("args:" + joinPoint.getArgs());
log.info(" 模块编码:"+sysLog.module().getCode());
log.info(" 模块名称:"+sysLog.module().getName());
log.info(" 方法描述:"+sysLog.description());
}
log.info("----------AOP日志end---------------");
}
private SysLog getAnnotationLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
return method != null ? method.getAnnotation(SysLog.class) : null;
}
}
5)先建注解,为了通过注解方式设置一些类和方法的标识
package com.demo.common.log.aspect;
import java.lang.annotation.*;
import com.demo.common.log.enums.ModuleTypeEnum;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
/**
* 操作模块
*/
ModuleTypeEnum module();
/**
* 操作描述
*/
String description() default "";
}
6)在子模块manage-biz中引用common-log子模块,这样就可以默认controller层会自动打印日志,如果要让controller增加更为精确表示,可以使用@SysLog注解增加模块和操作描述
2 国际化
在项目后端,经常需要返回一些异常信息或者提示等等,而如果项目是针对不同地区(比如中国、美国等),那么需要做到不同环境(在中国或者美国)后端也可以实现中英文切换,也就是在应用程序中实现多语言支持和区域设置的功能,因此需要引入国际化功能。
2.1 基本概念
- 语言标签(Language Tag) 语言标签是用来标识某个语言或语言和国家/地区的组合的字符串。语言标签通常由语言代码(ISO 639-1或ISO 639-3)和国家/地区代码(ISO 3166-1 Alpha-2)组成,使用"-"号分隔。例如,"en-US"表示英语(English)和美国(United States)的组合。
- 资源束(Resource Bundle) 资源束是包含本地化文本和对象的集合。每个资源束都包含了一个或多个语言的本地化资源,可以根据语言标签来选择不同的资源束。资源束通常以.properties文件的形式存在,包含了一系列键值对(key-value)的配置项。
- 区域设置(Locale) 区域设置是指某个特定的国家或地区的语言和文化习惯的组合。在Java中,区域设置由Locale类表示,可以通过构造函数或静态方法来创建。例如,Locale.US表示美国的区域设置,Locale.CHINA表示中国的区域设置。
简单来说,就是通过Locale获取到语言标签,再通过语言标签去不同资源束中获得对应的文字(中文、英文等)
2.2 代码实现
请参考manage-biz子模块
1)在resources目录下新建i18n文件夹
2)在i18n目录下,先建messages.properties、messages_en_US.properties、messages_zh_CN.properties三个文件
3)3个文件分别写入key-value,其中key为一致,value则使用中英文不同描述
4)在yaml文件中配置国际化路径
spring:
# 配置国际化
messages:
basename: i18n/messages
5)新建获取国际化的公共类
package com.demo.manage.biz.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
@Component
public class MessagesUtil {
@Autowired
MessageSource messageSource;
public String getMessage(String key){
return messageSource.getMessage(key, null, LocaleContextHolder.getLocale());
}
}
6)这样就可以在项目中通过key方式获取国际化功能