首页 文章 精选 留言 我的

精选列表

搜索[SpringBoot],共4254篇文章
优秀的个人博客,低调大师

SpringBoot入坑指南之二:配置篇

开篇语 很多人都说,Spring Boot最大的作用就是简化配置,摆脱原来Spring的配置地狱。确实,相比Spring原来的配置,Spring Boot简直就是天堂,所以说Spring Boot就是一个又大又深的坑,跳进去了就再也出不来(你也不愿意出来),这也是这个系列文章为什么叫入坑指南的原因。 不过,任何应用都不可能摆脱配置,像数据库相关配置、业务自定义配置等就肯定需要进行配置的。所以,在本文主要讲一下Spring Boot的一些配置方式,主要参考官方文档和自己项目中的一些使用方式。 Spring Boot的配置文件 默认配置文件 Spring Boot默认的配置文件时application.yml(或application.properties),相对于properties文件,yaml格式层级结构清晰易于阅读和管理,故推荐使用yaml格式进行配置。需注意的是,当相同目录下同时存在application.properties和application.yml文件时,会优先读取properties文件中的配置。 application.properties/application.yml配置文件读取优先级顺序如下: 文件|优先级<br>(值越小优先级越高)|说明 --|--|-- file:./config/application.properties|1|Jar包所处的同级目录的下级config目录 file:./config/application.yml|2|Jar包所处的同级目录的下级config目录 file:./application.properties|3|Jar包所处的同级目录 file:./application.yml|4|Jar包所处的同级目录 classpath:./config/application.properties|5|ClassLoader根目录下级config目录 classpath:./config/application.yml|6|ClassLoader根目录下级config目录 classpath:./application.properties|7|ClassLoader根目录 classpath:./application.yml|8|ClassLoader根目录 上表只是在默认情况下,实际上你可以通过spring.config.location配置修改配置文件读取路径,该配置既可以直接指定读取的配置文件,也可以指定配置文件的目录,如果需要指定目录,配置值必须是斜杠“/”结尾。 比如将配置设置为spring.config.location=classpath:./custom_location/,file:./custom_location,那么读取的优先级顺序会变成:file:./custom_location、classpath:./custom_location。 需注意的是,spring.config.location配置需在环境变量或者命令行参数中进行配置。 其他配置方式 Spring Boot还可以通过环境变量或者命令行参数进行应用配置,这两种配置方式优先级高于配置文件,即: 命令行参数>>环境变量>>application.properties>>application.yml 这两种配置方式在实际应用中有很大的用处,特别是用于多环境配置文件切换,下文会进行介绍。 多环境配置 如何管理多环境配置 软件开发过程中,会涉及到开发、测试、灰度、生产等各部署环境,每个环境所需要的配置肯定会有差异,不同环境的配置文件怎么管理?CI/CD过程如何切换? Spring Boot框架提供了多环境配置文件管理的解决方式,上文说到,Spring Boot默认的配置文件是application.yml,你可以按application-{profiles}.yml创建多个配置文件如: application-dev.yml application-test.yml application-prod.yml 之后在application.yml主配置文件中通过spring.profiles.active配置参数指定启用的profiles即可。 多环境配置切换方式 上面说的是如何管理多环境配置,配置文件配置好了,如何在持续集成的流程中进行配置文件的动态切换呢?我接触的项目中,注意是以下几种方式,给大家介绍一下。 方式一:原始方式(手工切换,非常Low,不推荐) 实现方式 如题,就是构建的时候手动将spring.profiles.active配置给改了,或者保证测试、或生产代码分支下该配置不变。 优点 只有一个:简单粗暴。 缺点 如果忘记改了或者合并分支的时候被覆盖,那就问题大了。 测试、生产环境保存在源码中,安全性较低。 方式二:使用Maven变量 注意:使用maven变量时,项目的父项目需要是“spring-boot-starter-parent”,否则需要额外增加配置,可参考:https://docs.spring.io/spring-boot/docs/2.1.2.RELEASE/reference/htmlsingle/#howto-automatic-expansion 实现方式 (1).在项目pom.xml文件中增加profiles配置,配置参考如下: <!-- 不同环境查找不同配置文件 --> <profiles> <profile> <id>dev</id> <properties> <profiles.active>dev</profiles.active> <maven.test.skip>true</maven.test.skip> </properties> <activation> <!-- 默认环境变量 --> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>test</id> <properties> <profiles.active>test</profiles.active> <maven.test.skip>true</maven.test.skip> </properties> </profile> <profile> <id>prod</id> <properties> <profiles.active>prod</profiles.active> <maven.test.skip>true</maven.test.skip> </properties> </profile> </profiles> (2).修改application.yml中的spring.profiles.active配置,使用maven变量替换。 spring: profiles: active: @profiles.active@ (3)..maven构建时通过参数指定替换变量,如下: mvn clean package -Pprod 优点 构建时指定,只需调整持续集成逻辑即可。 实现简单,符合软件部署逻辑。 缺点 测试、生产环境保存在源码中,安全性较低。 方式三:利用配置文件读取优先级(推荐) 实现方式 Spring Boot一般来说都是通过构建Jar包进行部署(war包部署请绕路),参考上面介绍的配置文件读取路径优先级顺序,在测试/生产服务器对应的部署路径中放置对应环境的application配置文件,部署的时候直接将Jar部署到对应目录,启动时就会使用外部的配置。 优点 测试、生产等环境配置文件无需放到代码中,避免泄漏。 应用构建、部署过程无需处理配置。 测试、生产的配置文件只存在于服务器上面,只有服务器相关运维人员能够查看和修改,安全性较高。 缺点 只适合服务器相对固定的场景,对于使用容器分布式弹性部署的场景不适用。 如果应用增加或修改配置,需要到各个服务器进行修改。 方式四:应用启动时指定(推荐) 实现方式 在Spring Boot应用启动时,通过命令行参数指定启用的profiles,由于命令行参数优先级高于配置文件,故生效的是命令行中指定的profiles。 参考启动命令如下: # 启用test profiles java -jar target/spring-boot-examples-properties-1.0-SNAPSHOT.jar --spring.profiles.active=test 优点 无需关注打包过程,只需要在服务启动命令中设置后启动参数即可。 配置文件可以保存在源码中,也可以保存在服务器中(参考上文方式三)。 缺点 暂无 本文示例代码 码云:https://gitee.com/centy/spring-boot-examples/tree/master/spring-boot-examples-properties 尾巴 通过Spring Boot可以快速构建一个微服务,那么微服务的服务治理就不得不提Spring Cloud全家桶,Spring Cloud提供各种配置中心解决方案(Spring Cloud Config、Consul、Zookeeper等)。 通过配置中心管理各个微服务的配置,开发、测试环境与生产的配置中心分离,就可以无需关注单个服务的不同环境配置的切换了,是不是很爽? 但是还有一个问题,每个微服务需要指定配置中心的地址,这个地址还是需要切换不是吗?有没有方法可以不切换这个配置呢?实际不用考虑的那么复杂,有很简单的方式,如果你是通过Docker容器部署应用的,通过设置容器环境变量指定配置中心的访问地址就可以了。

优秀的个人博客,低调大师

ABAP和Java SpringBoot的单元测试

ABAP 在ABAP类里,本地类(Local Class)里用关键字FOR TESTING声明过的方法, 在单元测试启动后会自动被调用到。 Spring Boot 在Spring及Spring Boot “Convention over configuration”的设定思路里,放在路径src/test/java下面以Tests.java结尾的Java类会被当成单元测试类处理。 对上述项目执行命令行mvn clean install后,报错误消息: java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) wit

优秀的个人博客,低调大师

springboot统一表单数据校验

统一表单数据校验 在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦: 验证代码繁琐,重复劳动 方法内代码显得冗长 每次要看哪些参数验证是否完整,需要去翻阅验证逻辑代码 hibernate validator(官方文档)提供了一套比较完善、便捷的验证实现方式。 spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。 于是编写了一套校验工具类,并配置了切面做统一的校验,无需在每次手动调用校验方法。 校验工具类 ValidatorUtil import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.groups.Default; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * @author lism * @date 2018年8月29日10:25:34 * bean 校验工具类 */ public class ValidatorUtil { private static Validator validator = Validation.buildDefaultValidatorFactory() .getValidator(); /** * 校验bean * @param bean * @param <T> * @return */ public static <T> List<ValidateBean> validate(T bean) { Set<ConstraintViolation<T>> constraintViolations = validator.validate(bean, Default.class); return errors2ValidateBeanList(constraintViolations); } /** * 校验属性 * @param bean * @param property * @param <T> * @return */ public static <T> List<ValidateBean> validateProperty(T bean, String property) { Set<ConstraintViolation<T>> constraintViolations = validator.validateProperty(bean, property, Default.class); return errors2ValidateBeanList(constraintViolations); } /** * 校验属性值 * @param bean * @param property * @param propertyValue * @param <T> * @return */ public static <T> List<ValidateBean> validateValue(T bean, String property, Object propertyValue) { Set<? extends ConstraintViolation<?>> constraintViolations = validator.validateValue(bean.getClass(), property, propertyValue, Default.class); return errors2ValidateBeanList(constraintViolations); } private static <T> List<ValidateBean> errors2ValidateBeanList(Set<? extends ConstraintViolation<?>> errors) { List<ValidateBean> validateBeans = new ArrayList<>(); if (errors != null && errors.size() > 0) { for (ConstraintViolation<?> cv : errors) { //这里循环获取错误信息,可以自定义格式 String property = cv.getPropertyPath().toString(); String message = cv.getMessage(); validateBeans.add(new ValidateBean(property, message)); } } return validateBeans; } } 校验结果辅助类 ValidateBean import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class ValidateBean { private String property; private String message; @Override public String toString() { final StringBuilder sb = new StringBuilder("{"); sb.append("property='").append(property).append('\''); sb.append(", message='").append(message).append('\''); sb.append('}'); return sb.toString(); } } 统一数据校验和异常处理切面 import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Arrays; import java.util.List; import java.util.Optional; /** * 统一数据校验和异常处理 * 控制层无需再进行数据校验和异常捕获 * @author lism */ @Aspect @Component @Slf4j @Order(1) public class ValidateBeanAspect { /** * 定义一个切入点 */ @Pointcut("execution(* org.lism..controller..*.*(..))") private void anyMethod() { } @Around("anyMethod()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); if (args.length > 0) { Optional<List<ValidateBean>> optional = Arrays.stream(args).filter(arg -> { return !(arg == null ||arg instanceof HttpServletRequest || arg instanceof HttpServletResponse); }).map(arg -> { return ValidatorUtil.validate(arg); }).filter(validateBeans -> { return validateBeans.size() > 0; }).findFirst(); if (optional.isPresent()) { return new ResponseBean(RTCodeEnum.CODE_FAIL.getCode(), optional.get().toString()); } } try { Object proceed = pjp.proceed(args); if (proceed instanceof ResponseBean) { return proceed; } else { return new ResponseBean(proceed, RTCodeEnum.CODE_200); } } catch (BaseException e) { RequestContextUtil.writeToResponse(new ResponseBean<>(e.getCode(), e.getMessage()).toString()); log.error(e.getMessage()); // return new ResponseBean<>(e.getCode(), e.getMessage()); return null; } catch (Exception e) { log.error(e.getMessage()); RequestContextUtil.writeToResponse(new ResponseBean<>(RTCodeEnum.CODE_FAIL.getCode(), e.getMessage())); // return new ResponseBean<>(RTCodeEnum.CODE_FAIL.getCode(), e.getMessage()); return null; } } @Before("anyMethod()") public void doBefore(JoinPoint pjp) throws Throwable { } @AfterReturning("anyMethod()") public void doAfterReturning(JoinPoint pjp) throws Throwable { } } RequestContextUtil工具类 import com.alibaba.fastjson.JSON; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * RequestContext工具类 * @Author lism * @Date 2018/8/29 17:04 */ public class RequestContextUtil { public static ServletRequestAttributes getRequestAttributes() { return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); } /** * 获取Request * @return */ public static HttpServletRequest getRequest() { //TODO:单元测试的时候,还是会得到requestAttributes,不等于null ServletRequestAttributes requestAttributes = getRequestAttributes(); if (requestAttributes != null) { return requestAttributes.getRequest(); } else { return null; } } /** * 获取Response * @return */ public static HttpServletResponse getResponse() { //TODO:单元测试的时候,还是会得到requestAttributes,不等于null ServletRequestAttributes requestAttributes = getRequestAttributes(); if (requestAttributes != null) { return requestAttributes.getResponse(); } else { return null; } } /** * 获取SessionId * @return */ public static String getSessionId() { //TODO:单元测试的时候,还是会得到requestAttributes,不等于null ServletRequestAttributes requestAttributes = getRequestAttributes(); if (requestAttributes != null) { return requestAttributes.getSessionId(); } else { return null; } } /** * 往前端写数据 * @param object */ public static void writeToResponse(Object object) { PrintWriter writer = null; try { HttpServletResponse response = RequestContextUtil.getResponse(); response.setCharacterEncoding("utf-8"); response.setHeader("Content-type", "text/html;charset=utf-8"); writer = response.getWriter(); writer.write(JSON.toJSONString(object)); writer.flush(); } catch (IOException e) { e.printStackTrace(); }finally { writer.close(); } } } 返回code 枚举类 import com.alibaba.fastjson.JSONObject; /** */ public enum RTCodeEnum { CODE_OK(0, "OK"), // CODE_DONE(1, "Done"), // CODE_FAIL(-1, "Failed"), CODE_PARAM_ERROR(300, "Input Param Error"), CODE_TOKEN_ERROR(301, "Token Validation Error"), CODE_CAPTCHA_ERROR(302, "验证码错误,请重试"), CODE_DATA_VALIDATE_FAILED(303, "数据校验未通过"), CODE_STATE_EXIST(305, "请勿重复请求"), // Data Issue: 4** CODE_400(400, "服务404"), CODE_DATA_ERROR_PAGETIME_EXPIRE(401, "页面超时不可用,请刷新重试"), // System Service Issue: 5** CODE_SERVICE_NOT_AVAILABLE(500, "系统服务不可用,请联系管理员"), CODE_200(200,"成功"), CODE_401(401,"未登录,需要登录"), CODE_405(405, "权限不足"), CODE_406(406,"客户端请求接口参数不正确或缺少参数"), CODE_501(501,"服务器接口错误"), CODE_999(999,"保留码"); private int code; private String desc; RTCodeEnum(int code, String desc) { this.code = code; this.desc = desc; } public JSONObject toJSON() { JSONObject jsonObject = new JSONObject(); jsonObject.put("code", code); jsonObject.put("desc", desc); return jsonObject; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } } ResponseBean工具类 import com.alibaba.fastjson.annotation.JSONField; import com.google.common.base.MoreObjects; import java.util.Date; /** * Created by lism 2018/8/23. */ public class ResponseBean<T> { private int code = 200; private String msg; private T data; @JSONField(format="yyyy-MM-dd HH:mm:ss") private Date date = new Date(); public static ResponseBean me(Object data){ return new ResponseBean(data); } public ResponseBean(T data) { this.data = data; } public ResponseBean(T data, RTCodeEnum rtCodeEnum) { this.data = data; this.msg = rtCodeEnum.getDesc(); this.code = rtCodeEnum.getCode(); } public ResponseBean(RTCodeEnum rtCodeEnum) { this.msg = rtCodeEnum.getDesc(); this.code = rtCodeEnum.getCode(); } public ResponseBean(T data, int code, String msg) { this.data = data; this.code = code; this.msg = msg; } public ResponseBean(int code, String msg) { this.code = code; this.msg = msg; } public ResponseBean() { } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public T getData() { return data; } public void setData(T data) { this.data = data; } public Date getDate() { return date; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("code", code) .add("msg", msg) .add("data", data) .toString(); } } 1.校验实体类型参数 编写测试实体 import lombok.Data; import org.hibernate.validator.constraints.Length; import javax.persistence.Entity; import javax.persistence.Table; import javax.validation.constraints.NotEmpty; @Entity @Table(name = "t_subject") @Data public class Subject extends BaseEntityModel { @NotEmpty(message = "专题名不能为空") @Length(min = 1, max = 20, message = "专题名1-20个字符之间") private String name; private String keyword; private String[] docType; private String startTime; private String endTime; } 测试控制器 @RestController @RequestMapping(value = "/subject") public class SubjectController { @RequestMapping(value = "/", method = RequestMethod.POST, produces = "application/json", consumes = "application/json") @ResponseBody @Override public ResponseBean create(@RequestBody Subject model) { return super.create(model); } 输入一个空的名子进行测试,可以看到返回结果 v2.png 2.校验RequestParam 类型请求参数 ValidatorUtil方式无法校验RequestParam 请求方法,在BaseController 加上@Validated注解 @RestController @Validated public class BaseController { } 在子控制器写测试方法 @RestController @RequestMapping(value = "/subject") public class SubjectController extends BaseController { /** * test4 * @return */ @RequestMapping(value = "/test3/", method = RequestMethod.GET, produces = "application/json") @ResponseBody public void test3(HttpServletRequest request, HttpServletResponse response, @Length(min = 1, max = 20, message = "专题名1-20个字符之间") @RequestParam String name) { log.info("test4"); } 通过测试看到:验证不通过时,抛出了ConstraintViolationException异常,所以我们在ValidateBeanAspect中使用统一的捕获异常是可以捕获到异常,并调用RequestContextUtil.writeToResponse方法将异常写到前台。 测试结果: v1.png 参考 https://www.cnblogs.com/mr-yang-localhost/p/7812038.htmlhttps://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册