SpringBoot开发规范
目录结构、静态资源访问
# 目录结构
* src/main/java:存放代码
* src/main/resources
* static: 存放静态文件,比如 css、js、image, (访问方式 [http://localhost:8080/js/main.js](http://localhost:8080/js/main.js))
* templates:存放静态页面jsp,html,tpl
* config:存放配置文件,application.properties
# 静态资源访问(同一个文件的加载顺序)
默认配置 spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
* META/resources >
* resources >
* static >
* public
启动、部署
# Jar方式打包启动(pom文件新增maven插件)
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
如果没Maven插件有加,则执行jar包 ,报错如下:
java -jar spring-boot-demo-0.0.1-SNAPSHOT.jar
no main manifest attribute, in spring-boot-demo-0.0.1-SNAPSHOT.jar
# 打包
构建:mvn install (target目录下有对应的jar包就是打包后的项目)
构建跳过测试类 mvn install -Dmaven.test.skip=true
# 启动
进到对应的target目录启动 java -jar xxxxx.jar 即可
想后台运行,就用守护进程 nohup java -jar xxx.jar &
打包后的Jar里面的目录结构
example.jar
|
+-META-INF //指定main函数入口
| +-MANIFEST.MF
| +-maven
| +-pom.properties
| +-pom.xml
+-org //启动类目录
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-BOOT-INF
+-classes //项目的字节码文件目录、资源文件目录(resources)
| +-mycompany
| +-project
| +-YourClasses.class
+-lib //依赖的库文件
+-dependency1.jar
+-dependency2.jar
常用HTTP请求注解
@GetMapping = @RequestMapping(method = RequestMethod.GET)
@PostMapping = @RequestMapping(method = RequestMethod.POST)
@PutMapping = @RequestMapping(method = RequestMethod.PUT)
@DeleteMapping = @RequestMapping(method = RequestMethod.DELETE)
@RequestBody 接收 raw (JSON) 复杂对象
@PostMapping 接收 form(param)
定制JSON字段
# JavaBean序列化为Json
* 性能:Jackson > FastJson > Gson > Json-lib 同个结构
* Jackson、FastJson、Gson类库各有优点,各有自己的专长
* 空间换时间,时间换空间
# jackson处理相关自动(一下注解作用于domain包下的bean的属性上)
* 指定字段不返回:@JsonIgnore
* 指定日期格式:@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
* 空字段不返回:@JsonInclude(Include.NON_NULL)
* 指定别名:@JsonProperty 例如: @JsonProperty("cover_img") coverImg (后端驼峰,返回给前端变为下划线)
# 自定义JSON格式
* 过滤用户敏感信息
* 视频创建时间返回自定义格式
# 序列化和反序列化操作
//序列化操作
ObjectMapper objectMapper = new ObjectMapper();
String jsonStr = objectMapper.writeValueAsString(list);
System.out.println(jsonStr);
//反序列化操作
List<Video> temp = objectMapper.readValue(jsonStr,List.class);
热部署
应用正在运行的时候升级功能, 不需要重新启动应用,对于Java应用程序来说, 热部署就是在运行时更新Java类文件。
# 优点
不需要重新手工启动应用,提高本地开发效率
# 常见实现热部署的方式
Jrebel
Spring Loaded
spring-boot-devtools
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork><!--必须添加这个配置-->
</configuration>
</plugin>
</plugins>
</build>
IDEA开启自动编译
Preferences --> Build --> Compiler --> Build project automatically
使用快捷键打开,选择Registry
注意默认快捷键
window快捷键 Shift+Ctrl+Alt+/
mac快捷键 Shift+Command+Alt+/
选择compiler.automake.allow.when.app.running
重启idea就行!!!
配置文件
# Springboot里面常用xx.yml
YAML(Yet Another Markup Language)
写 YAML 要比写 XML 快得多(无需关注标签或引号) 使用空格 Space 缩进表示分层,不同层次之间的缩进可以使用不同的空格数目
注意:key后面的冒号,后面一定要跟一个空格,树状结构
# Springboot里面常用 xx.properties(推荐)
Key=Value格式
语法简单,不容易出错
注解配置文件映射属性、实体类
# 方式一
1. Controller上面配置 @PropertySource({"classpath:resource.properties"})
2. 增加属性 @Value("${test.name}") private String name;
@RestController
@RequestMapping("api/v1/test")
@PropertySource({"classpath:pay.properties"})
public class TestController {
@Value("${wxpay.appid}")
private String payAppid;
@Value("${wxpay.secret}")
private String paySecret;
@Autowired
private WXConfig wxConfig;
@GetMapping("get_config")
public JsonData testConfig(){
Map<String,String> map = new HashMap<>();
// map.put("appid",payAppid);
// map.put("secret",paySecret);
//
// return JsonData.buildSuccess(map);
map.put("appid",wxConfig.getPayAppid());
map.put("secret",wxConfig.getPaySecret());
map.put("mechid",wxConfig.getPayMechId());
return JsonData.buildSuccess(map);
}
}
# 方式二:实体类配置文件
1. 添加 @Configuration 注解;
2. 使用 @PropertySource 注解指定配置文件位置;
3. 使用 @Value注解,设置相关属性;
4. 必须 通过注入IOC对象Resource 进来 , 才能在类中使用获取的配置文件值。
@Configuration
@PropertySource(value="classpath:pay.properties")
public class WXConfig implements Serializable {
@Value("${wxpay.appid}")
private String payAppid;
@Value("${wxpay.secret}")
private String paySecret;
@Value("${wxpay.mechid}")
private String payMechId;
public String getPayAppid() {
return payAppid;
}
public void setPayAppid(String payAppid) {
this.payAppid = payAppid;
}
public String getPaySecret() {
return paySecret;
}
public void setPaySecret(String paySecret) {
this.paySecret = paySecret;
}
public String getPayMechId() {
return payMechId;
}
public void setPayMechId(String payMechId) {
this.payMechId = payMechId;
}
}
单元测试
# 软件开发流程
需求分析->设计->开发->测试->上线
# 测试里面的种类
* 单元测试
完成最小的软件设计单元的验证工作,目标是确保模块被正确的编码
* 黑盒测试
不考虑内部结构,主要测试功能十分满足需求
* 白盒测试
针对代码级别,测试开发工程师一般具备白盒测试能力,针对程序内部的逻辑结构进行代码级别的测试
* 回归测试
对原先提出的缺陷进行二次验证,开发人员修复后进行二次的验证
* 集成测试
测试模块和模块之间的整合,且测试主要的业务功能
* 系统测试
针对整个产品系统进行的测试,验证系统是否满足产品业务需求
引入相关依赖:单元测试
<!--springboot程序测试依赖,如果是自动创建项目默认添加-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
配置相关注解
@RunWith(SpringRunner.class) //底层用junit SpringJUnit4ClassRunner
@SpringBootTest(classes={XXXApplication.class})//启动整个springboot工程
public class SpringBootTests { }
常用单元测试的注解
* @before 在@Test方法执行之前执行
* @Test
* @After 在@Test方法执行之后执行
断言
判断程序结果是否符合预期 TestCase.assertXXX()
import junit.framework.TestCase;
import net.xdclass.demoproject.controller.UserController;
import net.xdclass.demoproject.domain.User;
import net.xdclass.demoproject.utils.JsonData;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class) //底层用junit SpringJUnit4ClassRunner
@SpringBootTest(classes={DemoProjectApplication.class})//启动整个springboot工程
public class UserTest {
@Autowired
private UserController userController;
@Test
public void loginTest(){
User user = new User();
user.setUsername("jack");
user.setPwd("1234");
JsonData jsonData = userController.login(user);
System.out.println(jsonData.toString());
TestCase.assertEquals(0,jsonData.getCode());
}
}
MockMVC 调用 Controller 层API接口完成单元测试
# 如何测试Controller对外提供的接口
* 增加类注解 @AutoConfigureMockMvc
* 注入一个MockMvc类(此类相当于一个HTTP客户端,可以用于发起HTTP请求)
# 相关API
* perform执行一个RequestBuilder请求
* andExpect:添加ResultMatcher->MockMvcResultMatchers验证规则
* andReturn:最后返回相应的MvcResult->Response
@Test
public void testVideoListApi()throws Exception{
//发起HTTP请求到相应的API接口,并且添加验证规则针对状态码,如果成功才返回结果
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/pub/video/list"))
.andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
//从JSON数据中解析对应的状态码
int status = mvcResult.getResponse().getStatus();
//输出状态码
System.out.println(status);
//会乱码
//String result = mvcResult.getResponse().getContentAsString();
// 使用下面这个,增加 编码 说明,就不会乱码打印
String result = mvcResult.getResponse().getContentAsString(Charset.forName("utf-8"));
System.out.println(result);
}
全局异常处理
全局异常配置
# 为什么要配置全局异常?
不配全局服务端报错场景 1/0、空指针等
# 配置好处
统一的错误页面或者错误码
对用户更友好
# Springboot2.X怎么在项目中配置全局异常
* 类添加注解
@ControllerAdvice,如果需要返回json数据,则方法需要加@ResponseBody
@RestControllerAdvice, 默认返回json数据,方法不需要加@ResponseBody
* 方法添加处理器
捕获全局异常,处理所有不可知的异常
@ExceptionHandler(value=Exception.class)
import net.xdclass.demoproject.utils.JsonData;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* 标记这个是一个异常处理类
*/
@RestControllerAdvice
public class CustomExtHandler {
//捕获全局异常,处理所有不可知的异常:Exception是父类,能捕获所有子类异常
@ExceptionHandler(value = Exception.class)
JsonData handlerException(Exception e, HttpServletRequest request){
//使用JsonData工具类封装异常时的响应数据
return JsonData.buildError("服务端出问题了", -2);
}
}
自定义异常和错误页面跳转(技术比较老,单体项目)
# 返回自定义异常界面,需要引入thymeleaf依赖(非必须,如果是简单的html界面则不用)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
# 步骤如下
* 由于springboot家在静态资源文件的是有顺序的,可以添加templates目录用于存放自定义异常页面,修改application.properties,在value最后追加路径 classpath:/templates/
spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/templates/
* resource目录下新建templates,并新建error.html
<!DOCTYPE html>
<html xmlns:th="http://www/thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
欢迎光临!!
<p th:text="${msg}"> </p>
</body>
</html>
* 自定义异常处理类
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
/**
* 标记这个是一个异常处理类
*/
//@RestControllerAdvice //默认返回JSON格式
@ControllerAdvice
public class CustomExtHandler {
@ExceptionHandler(value = Exception.class)
Object handlerException(Exception e, HttpServletRequest request){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error.html");
//System.out.println(e.getMessage());
//将捕获异常的getMessage的值作为错误页面跳转的传递参数msg的值
modelAndView.addObject("msg",e.getMessage());
return modelAndView;
}
}
过滤器
# 场景
权限控制,用户登录状态控制,也可以交给拦截器处理等
# springboot2.x 里面的过滤器
ApplicationContextHeaderFilter
OrderdCharacterEncodingFilter
OrderedFormContentFilter
OrderedRequestContextFilter
# 过滤器的优先级
低位值意味着更高的优先级
Ordered.HIGHEST_PRECEDENCE
Ordered.LOWEST_PRECEDENCE
# 注册 Filter 配置两种方式
bean FilterRegistrantionBean
Servlet3.0 webFilter 注解的方式
# Servlet3.0的注解进行配置步骤
* 启动类里面增加 @ServletComponentScan,进行扫描
* 新建一个Filter类,implements Filter,并实现对应的接口
* @WebFilter 标记一个类为filter,被spring进行扫描
* urlPatterns:拦截规则,支持正则
* 控制chain.doFilter的方法的调用,来实现是否通过放行
* 不放行,web应用resp.sendRedirect("/index.html") 或者 返回json字符串
过滤器:用户登录状态控制,权限控制。
import com.fasterxml.jackson.databind.ObjectMapper;
import net.xdclass.demoproject.domain.User;
import net.xdclass.demoproject.service.impl.UserServiceImpl;
import net.xdclass.demoproject.utils.JsonData;
import org.springframework.util.StringUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebFilter(urlPatterns = "/api/v1/pri/*", filterName = "loginFilter")
public class LoginFilter implements Filter {
//ObjectMapper 是jackson中帮忙序列化的类
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 容器加载的时候
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init LoginFilter======");
}
/**
* 核心业务逻辑
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter LoginFilter======");
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
String token = req.getHeader("token");
if(StringUtils.isEmpty(token)){
token = req.getParameter("token");
}
if(!StringUtils.isEmpty(token)){
//判断token是否合法
User user = UserServiceImpl.sessionMap.get(token);
if(user!=null){
//token存在,并且合法,放行
filterChain.doFilter(servletRequest,servletResponse);
}else {//有token,但token失效
JsonData jsonData = JsonData.buildError("登录失败,token无效",-2);
String jsonStr = objectMapper.writeValueAsString(jsonData);
renderJson(resp,jsonStr);
}
}else {//token为空
JsonData jsonData = JsonData.buildError("未登录",-3);
String jsonStr = objectMapper.writeValueAsString(jsonData);
renderJson(resp,jsonStr);
}
}
/* 回写给前端JSON数据 */
private void renderJson(HttpServletResponse response,String json){
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
try(PrintWriter writer = response.getWriter()){
writer.print(json);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 容器销毁的时候
*/
@Override
public void destroy() {
System.out.println("destroy LoginFilter======");
}
}
Servlet3.0的注解原生Servlet
javaweb的使用doPost和doGet方法使用,Servlet3.0替代更轻量级
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 使用servlet3.0 开发原生的接口 (不需要在xml文件中配置路径了,使用注解直接代替)
*/
@WebServlet(name = "userServlet", urlPatterns = "/api/v1/test/customs" )
class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.write("this is my custom servlet");
writer.flush();
writer.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
监听器
监听器:应用启动监听器,会话监听器,请求监听器。
# 作用
* ServletContextListener 应用启动监听
* HttpSessionLisener 会话监听
* ServletRequestListener 请求监听
# 常用的监听器
* ServletContextListener
* HttpSessionListener
* ServletRequestListener
# 作用
ServletContextListener 应用启动监听:用于资源的初始化加载,读取配置文件、建立连接、垃圾清理、初始化
HttpSessionLisener 会话监听:分布式环境下很少用,几乎不用
ServletRequestListener 请求监听:用于日志打印
ServletContextListener
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* 应用上下文监听器
*/
@WebListener
class ApplicationListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("contextInitialized====");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed====");
}
}
HttpSessionLisener
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@WebListener
class CustomSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("sessionCreated====");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("sessionDestroyed====");
}
}
ServletRequestListener
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@WebListener
class CustomSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("sessionCreated====");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("sessionDestroyed====");
}
}
拦截器
拦截器和过滤器用途基本类似。
适用场景:权限控制、用户登录状态控制等。
# 配置拦截器
* SpringBoot2.X 新版本配置拦截器 implements WebMvcConfigurer
* 自定义拦截器 HandlerInterceptor
* preHandle:调用Controller某个方法之前
* postHandle:Controller之后调用,视图渲染之前,如果控制器Controller出现了异常,则不会执行此方法
* afterCompletion:不管有没有异常,这个afterCompletion都会被调用,用于资源清理
* 按照注册顺序进行拦截:流入方向,先注册,先被拦截,流出方向,后注册,先执行。
LoginIntercepter preHandle =====
TwoIntercepter preHandle =====
TwoIntercepter postHandle =====
LoginIntercepter postHandle =====
TwoIntercepter afterCompletion =====
LoginIntercepter afterCompletion =====
# 拦截器不生效常见问题
- 是否有加@Configuration
- 拦截路径是否有问题 ** 和 *
- 拦截器最后路径一定要 /** 如果是目录的话则是 /*/
# 和Filter过滤器的区别
* Filter和Interceptor二者都是AOP编程思想的体现,功能基本都可以实现
* 拦截器功能更强大些,Filter能做的事情它都能做
* Filter在只在Servlet前后起作用,而Interceptor够深入到方法前后、异常抛出前后等
* filter依赖于Servlet容器即web应用中,而Interceptor不依赖于Servlet容器所以可以运行在多种环境。
* 在接口调用的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。
# Filter和Interceptor的执行顺序
过滤前->拦截前->action执行->拦截后->过滤后
# 补充知识
* 如何配置不拦截某些路径?
registry.addInterceptor(new LoginIntercepter()).addPathPatterns("/api/v1/pri/**")
* 配置不拦截某些路径,比如静态资源
.excludePathPatterns("/**/*.html","/**/*.js");
拦截器配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 拦截器配置类
*/
@Configuration
class CustomWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册拦截器
registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/api/v1/pri/**");
registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api/v1/pri/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
// 补充知识点
// @Override
// public void addInterceptors(InterceptorRegistry registry) {
//
// registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/api/v1/pri/**","/api/v1/pri/user/**")
// .excludePathPatterns("/**/*.html","/**/*.js"); //配置不拦截某些路径;
//
// registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api/v1/pri/**")
//
//
// WebMvcConfigurer.super.addInterceptors(registry);
//
//
// }
@Bean
public LoginIntercepter getLoginInterceptor(){
return new LoginIntercepter();
}
}
拦截器类
import com.fasterxml.jackson.databind.ObjectMapper;
import net.xdclass.demoproject.domain.User;
import net.xdclass.demoproject.service.impl.UserServiceImpl;
import net.xdclass.demoproject.utils.JsonData;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
class LoginIntercepter implements HandlerInterceptor {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("LoginIntercepter preHandle =====");
String token = request.getHeader("token");
if(StringUtils.isEmpty(token)){
token = request.getParameter("token");
}
if(!StringUtils.isEmpty(token)){
//判断token是否合法
User user = UserServiceImpl.sessionMap.get(token);
if(user!=null){
return true;
}else {
JsonData jsonData = JsonData.buildError("登录失败,token无效",-2);
String jsonStr = objectMapper.writeValueAsString(jsonData);
renderJson(response,jsonStr);
return false;
}
}else {
JsonData jsonData = JsonData.buildError("未登录",-3);
String jsonStr = objectMapper.writeValueAsString(jsonData);
renderJson(response,jsonStr);
return false;
}
//return HandlerInterceptor.super.preHandle(request,response,handler);
}
private void renderJson(HttpServletResponse response,String json){
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
try(PrintWriter writer = response.getWriter()){
writer.print(json);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("LoginIntercepter postHandle =====");
HandlerInterceptor.super.postHandle(request,response,handler,modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("LoginIntercepter afterCompletion =====");
HandlerInterceptor.super.afterCompletion(request,response,handler,ex);
}
}
TwoIntercepter
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
class TwoIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("TwoIntercepter preHandle =====");
return HandlerInterceptor.super.preHandle(request,response,handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("TwoIntercepter postHandle =====");
HandlerInterceptor.super.postHandle(request,response,handler,modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("TwoIntercepter afterCompletion =====");
HandlerInterceptor.super.afterCompletion(request,response,handler,ex);
}
}
常见模版引擎
JSP(后端渲染,消耗性能)
Java Server Pages 动态网页技术,由应用服务器中的JSP引擎来编译和执行,再将生成的整个页面返回给客户端
可以写java代码
持表达式语言(el、jstl)
内建函数
JSP->Servlet(占用JVM内存)permSize
javaweb官方推荐
springboot官方不推荐 ,因为JSP极消耗性能
Freemarker
FreeMarker Template Language(FTL) 文件一般保存为 xxx.ftl
严格依赖MVC模式,不依赖Servlet容器(不占用JVM内存)
内建函数
Thymeleaf (主推)
轻量级的模板引擎(复杂逻辑业务的不推荐,解析DOM或者XML会占用多的内存)
可以直接在浏览器中打开且正确显示模板页面
直接是html结尾,直接编辑xdlcass.net/user/userinfo.html
社会工程学伪装
定时任务
# 使用场景
* 某个时间定时处理某个任务
* 发邮件、短信等
* 消息提醒
* 订单通知
* 统计报表系统
* …
# 常见定时任务
* Java自带的java.util.Timer类配置比较麻烦,时间延后问题
* Quartz框架: 配置更简单,xml或者注解适合分布式或者大型调度作业(复杂、功能强大)
* SpringBoot框架自带(便捷)
# SpringBoot使用注解方式开启定时任务
* 启动类里面 @EnableScheduling开启定时任务,自动扫描
* 定时任务业务类 加注解 @Component被容器扫描
* 定时执行的方法加上注解 @Scheduled(fixedRate=2000) 定期执行一次
/**
* 启动类里面 @EnableScheduling开启定时任务,自动扫描
* 定时统计订单,金额
*/
@Component
class VideoOrderTask {
//每2秒执行一次
@Scheduled(fixedRate = 2000)
public void sum(){
//正常的是从数据库中查询
System.out.println(LocalDateTime.now() + " 当前交易额="+ Math.random());
}
}
常用定时任务表达式配置和在线生成器
- cron 定时任务表达式 @Scheduled(cron="*/1 * * * * *") 表示每秒;crontab表达式生成工具
- fixedRate: 定时多久执行一次(上一次开始执行时间点后xx秒再次执行;)
- fixedDelay: 上一次执行结束时间点后xx秒再次执行 (任务sleep也算在内,适合用于任务执行之后再等待若干时间再执行。)
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Random;
/**
* 定时统计订单,金额
*/
@Component
class VideoOrderTask {
//每2秒执行一次
@Scheduled(fixedDelay = 4000)
//@Scheduled(fixedRate = 4000)
//@Scheduled(cron = "*/1 * * * * *")
public void sum(){
//正常的是从数据库中查询
System.out.println(LocalDateTime.now() + " 当前交易额="+ Math.random());
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
异步任务
异步任务相当于多开一个线程去执行,主程序会被立刻返回执行后续的动作,可以减少用户等待时间,提高用户体验。
# 适用场景
什么是异步任务和使用场景:适用于处理log、发送邮件、短信……等
* 下单接口->查库存 1000
* 余额校验 1500
* 风控用户1000
# 创建异步任务的步骤
* 启动类里面使用@EnableAsync注解开启功能,自动扫描
* 定义异步任务类并使用@Component标记组件被容器扫描,异步方法加上@Async
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
@Async
public class AsyncTask {
public void task1(){
try {
Thread.sleep(4000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" task 1 ");
}
public void task2(){
try {
Thread.sleep(4000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" task 2 ");
}
public void task3(){
try {
Thread.sleep(4000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" task 3 ");
}
}