本文已参与「新人创作礼」活动,一起开启掘金创作之路。
日期时间格式化处理
两个注解
- 注解
@JsonFormat主要是后端到前端的时间格式的转换, @JsonFormat 注解需要添加 jackson 相关的依赖包. 如果是 SpringBoot 项目就不需要自己手动添加依赖,因为在 spring-boot-start-web 下已经包含了 jackson 相关依赖. - 注解
@DateTimeFormat主要是前端到后端的时间格式的转换,@DateTimeFormat 属于 Spring 的注解.
后端 -> 前端
当后端接口给前端返回数据时, 如果实体类中有 Date 类型的字段.
如果不做处理, 那么返回给前端的是 2020-06-28T08:43:56.719+0000 这种类型的时间, 不符合要求.
可以在 application.yml 中统一对日期的格式进行配置.
#时间格式配置
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
上面配置是统一的处理, 如果某个实体类有自己的特定格式, 可以通过在实体类的 Date 字段上使用 @JsonFormat 注解单独处理.
@JsonFormat(pattern="yyyy-MM-dd ",timezone="GMT+8")
private Date lastLoginTime; //最后登录时间
优先级 : @JsonFormat 注解 > 统一配置 > 未处理
前端 -> 后端
在 controller 层自动封装映射对象时,在对应的接收前台数据的对象的 Date 属性上加 @DateTimeFormat 注解即可.
@DateTimeFormat(pattern = "yyyy-MM-dd") // 表示前端需要传入 yyyy-MM-dd 格式的日期
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
private Date symstarttime;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
private Date symendtime;
需要注意的是: @DateTimeFormat 注解的 pattern 属性指定的日期时间格式, 是前端传入的日期时间格式相对应.
\
获取项目全部URL
Spring boot 项目在做URL权限控制的时候需要获得全部的URL,一个一个去controller中找费时费力。而且有的权限点的命名和URL有一定的对应关系。如果能用程序获得全部URL,将会省去很多事。下面就介绍一种获取URL的方法。在项目中添加如下 Controller,请求 /getAllUrl,即可看到项目所有的URL。当然也可以根据项目将URL写入数据库或写入配置文件。
方式1:
@Controller
@RequestMapping("/*")
public class UrlController {
@Autowired
WebApplicationContext applicationContext;
@GetMapping("/getAllUrl")
@ResponseBody
public List<String> getAllUrl(){
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
//获取url与类和方法的对应信息
Map<RequestMappingInfo,HandlerMethod> map = mapping.getHandlerMethods();
List<String> urlList = new ArrayList<>();
for (RequestMappingInfo info : map.keySet()){
//获取url的Set集合,一个方法可能对应多个url
Set<String> patterns = info.getPatternsCondition().getPatterns();
for (String url : patterns){
urlList.add(url);
}
}
return urlList;
}
}
方式2:
@Autowired
WebApplicationContext applicationContext;
@RequestMapping(value = "v1/getAllUrl", method = RequestMethod.POST)
public Object getAllUrl() {
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
// 获取url与类和方法的对应信息
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
for (Entry<RequestMappingInfo, HandlerMethod> m : map.entrySet()) {
Map<String, String> map1 = new HashMap<String, String>();
RequestMappingInfo info = m.getKey();
HandlerMethod method = m.getValue();
PatternsRequestCondition p = info.getPatternsCondition();
for (String url : p.getPatterns()) {
map1.put("url", url);
}
map1.put("className", method.getMethod().getDeclaringClass().getName()); // 类名
map1.put("method", method.getMethod().getName()); // 方法名
RequestMethodsRequestCondition methodsCondition = info.getMethodsCondition();
for (RequestMethod requestMethod : methodsCondition.getMethods()) {
map1.put("type", requestMethod.toString());
}
list.add(map1);
}
JSONArray jsonArray = JSONArray.fromObject(list);
return jsonArray;
}
对 BigDecimal 的处理
BigDecimal 类型的字段如果值为类似 0.000001 这种精度很高的小数, 直接返回给前端, 则会转为科学记数法去展示. 想在只需要其展示小数点后两位即可.
自定义 Jackson 处理类
1.自定义处理类 :
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class BigDecimalJsonSerializer extends JsonSerializer<BigDecimal>{
@Override
public void serialize(BigDecimal arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
if (arg0 != null) {
BigDecimal newValue = arg0.setScala(2, BigDecimal.Round_DOWN);
arg1.writeString(arg0.toPlainString());
}
}
}
2.在实体类上使用 :
@JsonSerialize(using = BigDecimalJsonSerializer.class)
private BigDecimal amount;
使用 @NumberFormat 注解
还没使用过, 不过好像是可以格式化的.
接口启用压缩
(1) 优化场景
在优化接口时间的过程中,发现很多接口的 Content Download 时间较长,除了网络问题,就是接口请求的数据太大了,有的达到了几百 kb。
控制返回参数的收效甚微,这时开启 gzip 就非常有用了,可以压缩接口请求的数据,一般的 json 文本压缩比率很大,开启之后接口时间大幅下降!
(2) 启用步骤
Spring Boot 项目配置比较简单,如下配置 Gzip 就开启了!
server:
#如果请求需要通过网关转发和返回时,则以下配置需要配置在网关上,而不是目标微服务上
compression:
enabled: true
#响应内容长度超过设置大小时进行压缩,默认值为2048(2KB,不带单位时默认为字节)
min-response-size: 5kb
#对指定的响应类型进行压缩,值是数组,用逗号隔开
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml
(3) 验证 Gzip 是否开启成功
Google 浏览器打开 F12,切换到 NetWork 下,右键表头选择 Response Headers 下的 Content-Encoding,如果开启了 Gzip,对应接口中的 Content-Encoding 中会有显示。
注意: 在同时开启 https 和 http 的工程中,Gzip 配置只对主端口生效!
开启前:数据 Size 为 511KB,右侧时间蓝色部分(Content Download)较长。开启后:数据 Size 为 21.3KB,Content-Encoding 中显示 gzip,右侧时间蓝色部分消失。对比效果非常明显,接口时间由 1.03s 降到 164ms,完美!
配置接口的统一请求前缀
如下配置:
server:
servlet:
context-path: /api/v1.0
那么之前 http://localhost:8080/user/1 就需要变为 http://localhost:8080/api/v1.0/user/1 才能访问.
自定义错误页面
当请求发生了 404 或者 500 错误时, Spring Boot 默认会在 error 目录下查找 4xx, 5xx 的文件作为错误视图。当找不到时会用 error 作为默认的错误页面视图名, 如果还是找不到, 则来到 SpringBoot 默认的错误页面.
自定义错误页面其实很简单, 提供 4xx 和 5xx 页面即可. 如果开发者不需要向用户展示详细的错误信息, 那么可以把错误信息定义成静态页面, 直接在resources/static目录下创建error目录, 然后在 error 目录中创建错误展示页面.
错误展示页面的命名规则有两种:一种是4xx.html, 5xx.html;另一种是直接使用响应码命名文件, 例如 404.html, 405.html, 500.html. 第二种命名方式划分得更细, 当出错时, 不同的错误会展示不同的错误页面.
路径映射
有时候我们想要发出一个请求, 只是跳转到指定的页面, 并不获取数据. 如果使用 SpringMVC, 就必须要写一个 Controller 类, 再写一个方法进行页面跳转, 比较麻烦. 使用 SpringBoot 之后, 只要实现WebMvcConfigurer接口中的addViewControllers方法即可.
@Configuration
public class WebConfig implements WebMvcConfigurer{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/admin").setViewName("forward:/admin.html");
}
}
重写 WebMvcConfigurer 的 addViewControllers 方法, 并不会覆盖 WebMvcAutoConfiguration 中的 addViewControllers(在此方法中, Spring Boot 将 “/” 映射至 index.html), 即自己的配置和 SpringBoot 的自动配置同时有效. 同样在该方法中还可以修改默认的首页.
数据预加载
想要在项目启动时就去加载一些数据或做一些操作时. SpringBoot 中只需要编写一个 Java 类, 实现CommandLineRunner接口, 重写 run() 方法就可以, 无需其他配置.
SpringBoot 在启动后, 会自动遍历 CommandLineRunner 接口的实例, 并运行它们的 run() 方法. 如果接口 CommandLineRunner 的实现类有多个, 可以在实现类上, 使用@Order注解来设置所有 CommandLineRunner 实现类的先后运行顺序. value 值越小, 越先执行.
@Component
@Order(value = 100)
public class MyStartupRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("MyStartupRunner.run()");
System.out.println("服务启动执行,预先加载数据");
}
}
静态资源映射
默认静态资源映射
Spring Boot 默认提供了静态资源处理, 映射优先级顺序为:META-INF/resources > resources > static > public
可以在上面 4 个路径下都放一张同名但内容不同的图片, 访问一下即可验证. 访问也很简单, 直接加资源名即可 : localhost:8080/ + 资源名. 比如: localhost:8080/123.jpg
在 Eclipse 工程里面路径是这样:
在 IDEA 中, 工程目录如下:
自定义静态资源映射
在实际开发中, 可能需要自定义静态资源访问路径, 那么可以继承 WebMvcConfigurerAdapter 来实现.
//配置静态资源映射
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//将所有/sanjingye/** 访问都映射到 classpath:/sanjingye/ 目录下
registry.addResourceHandler("/sanjingye/**")
.addResourceLocations("classpath:/sanjingye/");
}
}
重启项目, 访问:http://lcoalhost:8080/sanjingye/123.jpg 能正常访问 sanjingye 目录下的 123.jpg 图片资源.
也可以指定外部的目录 :
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//将所有/sanjingye/** 访问都映射到 D:/sanjingye/ 目录下
registry.addResourceHandler("/sanjingye/**")
.addResourceLocations("file:D:/sanjingye/");
}
接管 Web 配置
通过在一个配置类(有 @Configuration 注解的类)上添加 @EnableWebMvc 注解,就可以实现完全自定义的 MVC 配置.
不过, 既要保留 Spring Boot 提供的配置, 又需要增加自己的额外的配置时, 可以定义一个配置类并继承 WebMvcConfigurerAdapter, 来实现.
注意 : 在 SpringBoot 1.5 中是使用这个类, 但在 SpringBoot2.0 中, 该类已被废弃, 使用 WebMvcConfigurationSupport 类, 这个类是 WebMvcConfigurerAdapter 的扩展和替代, 它们的使用方法都是一样的.
以下是 WebMvcConfigurerAdapter 比较常用的重写方法 :
/** 解决跨域问题 **/
public void addCorsMappings(CorsRegistry registry) ;
/** 添加拦截器 **/
void addInterceptors(InterceptorRegistry registry);
/** 这里配置视图解析器 **/
void configureViewResolvers(ViewResolverRegistry registry);
/** 配置内容裁决的一些选项 **/
void configureContentNegotiation(ContentNegotiationConfigurer configurer);
/** 视图跳转控制器 **/
void addViewControllers(ViewControllerRegistry registry);
/** 静态资源处理 **/
void addResourceHandlers(ResourceHandlerRegistry registry);
/** 默认静态资源处理器 **/
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);
使用 Servlet / Filter / Listener
SpringBoot 默认是以 jar 包的方式启动嵌入式的 Servlet 容器来启动 web 应用, 所以没有 web.xml 文件. 但有时候确实需要 Servlet , Filter, Listener 来实现一些功能.
在 SpringBoot 中使用 Servlet 有两种方法(Filter 和 Listener 也是同样的方式)
方式一 : 通过代码注册 Servlet
通过 ServletRegistrationBean, FilterRegistrationBean 和 ServletListenerRegistrationBean分别可以获取 Servlet , Filter, Listener.
① 配置类中配置 bean.
@Configure
public class ServletConfig {
//使用代码注册Servlet(不需要@ServletComponentScan注解)
@Bean
public ServletRegistrationBean firstServlet() {
//参数1:servlet对象, 参数2:映射路径.
ServletRegistrationBean firstServlet = new ServletRegistrationBean(new FirstServlet(), "/firstServlet");
//可以进行相关属性的设置.
firstServlet.setLoadOnStartup(1);
return firstServlet;
}
}
② 自定义的 Servlet
public class FirstServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
}
}
方式二 : 使用注解注册 Servlet
在启动类上使用@ServletComponentScan 注解扫描需要注册的 Servlet.
需要注册的 Servlet, Filter, Listener 可以直接通过 @WebServlet, @WebFilter, @WebListener 注解实现自动注册, 无需其他代码.
① SpringBoot 启动类
//使用注解注册Servlet
@ServletComponentScan(basePackages="it.com.servlet")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
② 自定义 Servlet
package it.com.servlet;
@WebServlet(urlPatterns="/firstServlet")
public class FirstServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
}
}
如上是使用 Servlet 的两种方式, 使用 Filter 和 Listener 也是同样的方式.
代码演示 : 使用 Filter
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet")); //过滤路径
Map<String, String> initParameters = new HashMap();
//excludes用于配置不需要参数过滤的请求url
initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*");
registrationBean.setInitParameters(initParameters); //设置init值
return registrationBean;
}
代码演示 : 使用 Listener
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());
return registrationBean;
}