Skip to content

第三章 Spring 基础(二) v1.0 ​

第一部分 登录校验 ​

一、会话技术 ​

用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。

在一次会话中可以包含多次请求和响应。

会话跟踪:维护浏览器状态的方法,服务器识别多次请求是否来自同一浏览器,在同会话的多次请求间共享数据。

常见会话跟踪方案:

  • 客户端会话跟踪技术:Cookie
  • 服务端会话跟踪技术:Session
  • 令牌技术

image.png ​

二、JWT令牌(JSON Web Token) ​

种简洁的、自包含的格式,用于在通信双方以JSON数据格式安全(使用数字签名)的传输信息。

1.组成 ​

JWT使用Base64(一种基于64个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式)表示

  1. 第一部分:Header(头) ,记录令牌类型、签名算法等。例如:{"lg":"HS256","type":"JWT"}
  2. 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1","username":"Tom"}
  3. 第三部分:Signature(数字签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

2.生成与校验JWT令牌 ​

xml
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
java
public String genJwt(){
    Map<String,object> claims = new HashMap<>();
    claims.put("id", 1);
    claims.put("username", "Tom");
    String jwt = Jwts.builder()
        .setClaims(claims) //设定PayLoad
        .signWith(SignatureAlgorithm.HS256, "key" ) //签名算法
        .setExpiration(new Date(System.currentTimeMillis() + 12*3600*1000))  //设置有效期
        .compact();
    return jwt;
}
java
public void parseJwt(String jwt){
    Claims claims = Jwts.parser()
        .setSigningKey("key") //指定签名秘钥
        .parseClaimsJws(jwt) //解析令牌
        .getBody();
    return claims;
}

三、过滤器(Filter) ​

过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。

过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。

一个web应用可以配置多个过滤器,多个过滤器形成过滤器链,优先级是按照过滤器类名(字符串)的自然排序

1.定义Filter ​

实现Filter接口,实现接口中的方法(doFilter必须实现,初始化和销毁方法有默认实现)

java
public class DemoFilter implements Filter {
    //初始化方法, Web服务器启动,创建Filter时调用,只调用一次
    public void init (FilterConfig filterConfig) throws ServletException { 
        Filter.super.init(filterConfig);
    }
    //拦截到请求时,调用该方法,可调用多次:
    public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain){
        //放行操作
        chain.doFilter(request,response);
    }
    //销毁方法,服务器关闭时调用,只调用一次
    public void destroy() { 
        Filter.super.destroy();
    }
}

2.配置Filter ​

配置拦截资源路径(@WebFilter注解),开启Servlet组件支持(@ServletComponentScan)

java
@WebFilter(urlPattern="/*")
public class DemoFilter implements Filter {...}
java
@ServletComponentScan
@SpringBootApplication
public class ProjectApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);  // 运行应用
    }
}

四、拦截器(Interceptor) ​

Spring框架中提供的,用来动态拦截控制器方法的执行

1.定义拦截器 ​

实现HandlerInterceptor接口,重写其所有方法

java
@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;  //返回true放行,返回false不放行
    }

    @Override  //目标资源方法执行之后执行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override  //视图渲染完成之后执行,最后执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
java
@Configuration  //是一个配置类
public class InterceptorConfig implements WebMvcConfigurer {
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
    }
}

2.拦截路径 ​

添加拦截路径:addPathPatterns("路径")

移除拦截路径:excludePathPatterns("路径")

3.实现登录验证 ​

xml
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.83</version>
</dependency>
java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String token = request.getHeader("token");
    if(!StringUtils.hasLength(token)){  //token不存在
        String res= JSONObject.toJSONString(Result.error("令牌不存在"));
        response.getWriter().write(res);
        return false;
    }
    try{
        JwtUtils.parseJwt(token);
    }catch (Exception e){
        String res= JSONObject.toJSONString(Result.error("错误"));
        response.getWriter().write(res);
        return false;
    }
    return true;
}

五、全局异常处理器 ​

java
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public Result exception(Exception e) {
        e.printStackTrace();
        return Result(...);
    }
}

第二部分 事务管理与Spring AOP ​

一、Spring事务管理 ​

1.使用事务管理 ​

方法:使用@Transactional注解,将方法/类/接口交由Spring进行事务管理,出现异常时自动回滚事务

java
@Transactional
@Override
public void delete(Integer id) {
    mapper.delete(id);
	anotherMapper.deleteById(id);
}

2.事务属性——rollbackfor ​

在Spring事务管理中,默认情况下,只有出现RuntimeException才回滚异常

可以通过rollbackfor属性控制当出现何种异常类型时进行回滚事务

java
@Transactional(rollbackFor = Exception.class)  //控制出现所有异常都进行回滚操作
@Override
public void delete(Integer id) {...}

3.事务属性——propagation ​

用于控制事务的传播行为,即当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

二、AOP基础 ​

1.概述 ​

AOP概念:Aspect Oriented Programming ( 面向切面编程、面向方面编程),即面向特定方法编程。

AOP实现:通过String框架的底层的动态代理机制,对特定方法进行编程

2.优势 ​

  1. 代码无侵入
  2. 减少重复代码
  3. 提高开发效率
  4. 维护方便

3.核心概念 ​

  1. 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
  2. 通知:Advice,指AOP中重复的逻辑,即共性功能(提现为一个方法)
  3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行的时候被应用
  4. 切面:描述通知与切入点的对应关系(通知+切入点)
  5. 目标对象:Target,通知所应用的对象

4.快速入门 ​

xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
java
@Component
@Aspect
public class TimeAspect {
    @Around("execution(* asia.hymsk.*.*(..))")  //环绕通知(切入点表达式)
    public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long begin = System. currentTimeMillis();
        object object = proceedingJoinPoint.proceed(); //调用原始方法运行
        long end = System.currentTimeMillis();
        log.info(proceedingJoinPoint.getSignature()+"执行耗时: {}ms", end - begin);
        return object;
    }
}

5.切入点表达式 ​

a.抽取切入点表达式 ​
java
@Component
@Aspect
public class TimeAspect {
    @PointCut("execution(* asia.hymsk.*.*(..))")  //抽取切入点表达式
	private void pt() {}
    
    @Around("pt()") //引用抽取的切入点表达式
    public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {}
}
b.根据方法签名匹配(execution) ​

标准语法格式:excution([访问修饰符] 返回值 [包名.类名.]方法名(方法参数) [throws 声明异常])

通配符:*:单个独立的任意符号;..:多个连续的任意符号(如任意层级的包、任意个参数)

注意:上述语法中包名.类名.不建议省略,范围过大会影响匹配效率

c.根据注解匹配(@annotation) ​

标准语法格式:@annotation(注解全类名)

三、AOP进阶 ​

1.通知类型 ​

  1. @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  2. @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  3. @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  4. @AfterReturning:返回后通知, 此注解标注的通知方法在目标方法后被执行,有异常不会执行
  5. @AfterThrowing:异常后通知, 此注解标注的通知方法发生异常后执行

注意要点:

  • @Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要
  • @Around环绕通知方法的返回值,必须指定为0bject,来接收原始方法的返回值。

2.不同切面类的通知顺序 ​

默认:目标方法前的通知的执行顺序默认按照类名(字符串)的自然排序排列,目标方法后的通知则相反。

自定义:使用@Order控制通知的执行循序,目标方法前的通知数字小的先执行,目标方法后的通知则相反。

3.连接点(JoinPoint) ​

Spring中用连接点(JoinPoint)获得方法执行时的相关信息,如目标类名、方法名、方法参数等

对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint

对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类

java
String joinPoint.getTarget().getClass().getName();  //获取目标类名
Signature joinPoint.getSignature();  //获取目标方法签名
String joinPoint.getSignature().getName();  //获取目标方法名
Object[] args = joinPoint.getArgs();  //获取目标方法运行参数
Object res = joinPoint.proceed();  //执行原始方法并获取返回值(ProceedingJoinPoint方法)