目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

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就行!!!

配置文件

Reference

# 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 ");
    }
}

作者:Soulboy