第三章 Spring 基础(二) v1.0
第一部分 登录校验
一、会话技术
用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。
在一次会话中可以包含多次请求和响应。
会话跟踪:维护浏览器状态的方法,服务器识别多次请求是否来自同一浏览器,在同会话的多次请求间共享数据。
常见会话跟踪方案:
- 客户端会话跟踪技术:Cookie
- 服务端会话跟踪技术:Session
- 令牌技术
二、JWT令牌(JSON Web Token)
种简洁的、自包含的格式,用于在通信双方以JSON数据格式安全(使用数字签名)的传输信息。
1.组成
JWT使用Base64(一种基于64个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式)表示
- 第一部分:Header(头) ,记录令牌类型、签名算法等。例如:
{"lg":"HS256","type":"JWT"} - 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:
{"id":"1","username":"Tom"} - 第三部分:Signature(数字签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。
2.生成与校验JWT令牌
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>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;
}public void parseJwt(String jwt){
Claims claims = Jwts.parser()
.setSigningKey("key") //指定签名秘钥
.parseClaimsJws(jwt) //解析令牌
.getBody();
return claims;
}三、过滤器(Filter)
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
一个web应用可以配置多个过滤器,多个过滤器形成过滤器链,优先级是按照过滤器类名(字符串)的自然排序
1.定义Filter
实现Filter接口,实现接口中的方法(doFilter必须实现,初始化和销毁方法有默认实现)
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)
@WebFilter(urlPattern="/*")
public class DemoFilter implements Filter {...}@ServletComponentScan
@SpringBootApplication
public class ProjectApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args); // 运行应用
}
}四、拦截器(Interceptor)
Spring框架中提供的,用来动态拦截控制器方法的执行
1.定义拦截器
实现HandlerInterceptor接口,重写其所有方法
@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);
}
}@Configuration //是一个配置类
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
}
}2.拦截路径
添加拦截路径:addPathPatterns("路径")
移除拦截路径:excludePathPatterns("路径")
3.实现登录验证
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>@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;
}五、全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result exception(Exception e) {
e.printStackTrace();
return Result(...);
}
}第二部分 事务管理与Spring AOP
一、Spring事务管理
1.使用事务管理
方法:使用@Transactional注解,将方法/类/接口交由Spring进行事务管理,出现异常时自动回滚事务
@Transactional
@Override
public void delete(Integer id) {
mapper.delete(id);
anotherMapper.deleteById(id);
}2.事务属性——rollbackfor
在Spring事务管理中,默认情况下,只有出现RuntimeException才回滚异常
可以通过rollbackfor属性控制当出现何种异常类型时进行回滚事务
@Transactional(rollbackFor = Exception.class) //控制出现所有异常都进行回滚操作
@Override
public void delete(Integer id) {...}3.事务属性——propagation
用于控制事务的传播行为,即当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
二、AOP基础
1.概述
AOP概念:Aspect Oriented Programming ( 面向切面编程、面向方面编程),即面向特定方法编程。
AOP实现:通过String框架的底层的动态代理机制,对特定方法进行编程
2.优势
- 代码无侵入
- 减少重复代码
- 提高开发效率
- 维护方便
3.核心概念
- 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
- 通知:Advice,指AOP中重复的逻辑,即共性功能(提现为一个方法)
- 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行的时候被应用
- 切面:描述通知与切入点的对应关系(通知+切入点)
- 目标对象:Target,通知所应用的对象
4.快速入门
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>@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.抽取切入点表达式
@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.通知类型
@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行@Before:前置通知,此注解标注的通知方法在目标方法前被执行@After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行@AfterReturning:返回后通知, 此注解标注的通知方法在目标方法后被执行,有异常不会执行@AfterThrowing:异常后通知, 此注解标注的通知方法发生异常后执行
注意要点:
@Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要@Around环绕通知方法的返回值,必须指定为0bject,来接收原始方法的返回值。
2.不同切面类的通知顺序
默认:目标方法前的通知的执行顺序默认按照类名(字符串)的自然排序排列,目标方法后的通知则相反。
自定义:使用@Order控制通知的执行循序,目标方法前的通知数字小的先执行,目标方法后的通知则相反。
3.连接点(JoinPoint)
Spring中用连接点(JoinPoint)获得方法执行时的相关信息,如目标类名、方法名、方法参数等
对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint
对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类
String joinPoint.getTarget().getClass().getName(); //获取目标类名
Signature joinPoint.getSignature(); //获取目标方法签名
String joinPoint.getSignature().getName(); //获取目标方法名
Object[] args = joinPoint.getArgs(); //获取目标方法运行参数
Object res = joinPoint.proceed(); //执行原始方法并获取返回值(ProceedingJoinPoint方法)