小公司做网站的好处,代理ip自动提取网站源码,外贸网站建站n,新泰房产网58个人出售Filter 过滤器 对所有请求都可以过滤。 实现Filter接口#xff0c;重写几个方法#xff0c;加上WebFilter注解#xff0c;表示拦截哪些路由#xff0c;如上是所有请求都会拦截。 然后还需要在入口处加上SvlterComponentScan注解#xff0c;因为Filter是javaweb三大组件之…Filter 过滤器 对所有请求都可以过滤。 实现Filter接口重写几个方法加上WebFilter注解表示拦截哪些路由如上是所有请求都会拦截。 然后还需要在入口处加上SvlterComponentScan注解因为Filter是javaweb三大组件之一并不是springboot的内容。 chain.doFilter是放行该请求的意思如果没有将会卡在当前过滤器上。
过滤器链
一个服务可以配置多个过滤器多个过滤器形成过滤器链。有点像koa的洋葱模型。 过滤器1执行 - 过滤器2执行 - 执行主要逻辑 - 过滤器2放行后的逻辑执行 - 过滤器1放行后的逻辑执行 那么怎么区分那个过滤器先执行呢通过过滤器首字母排序来决定。
Interceptor拦截器 拦截器是Spring框架提供的跟filter不一样。 使用 实现HandlerInterceptor接口重写方法其中preHanldel是在controller执行前执行返回值作为放行的条件。 postHandle是在controller执行后执行。 实现拦截器之后还需要配置才能生效。 实现WebMvcConfigurer接口然后使用Configuration这样springboot启动的时候会自动扫描该注解生效该配置。 如上就是将拦截器注册并指定其拦截的接口。
拦截器-拦截路径 可以通过addPathPatterns指定哪些路径需要拦截通过excludePathPatterns指定哪些路径不需要拦截。
执行时机
上面说过拦截器是spring框架提供的而过滤器是tomcat框架提供的如图。 如果都存在的话会先执行过滤器再执行拦截器的逻辑。 过滤器会拦截所有的请求资源而拦截器只会拦截Spring环境中的资源。
案例 实现jwt登陆验证
安装对应依赖 dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt/artifactIdversion0.9.1/version/dependency实现jwtUtils类
package com.example.demo.util;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;
import java.util.Map;public class JwtUtils {private static String singKey test1234; // 密钥private static long expire 432000L; //过期时间public static String generatorToken(MapString, Object data) {String jwt Jwts.builder().addClaims(data).signWith(SignatureAlgorithm.HS256, JwtUtils.singKey).setExpiration(new Date(System.currentTimeMillis() JwtUtils.expire)).compact();return jwt;}public static Claims parseToken(String token) {Claims calims Jwts.parser().setSigningKey(singKey).parseClaimsJws(token).getBody();return calims;}}使用jwt生成token
Slf4j
RestController
public class LoginController {PostMapping(/login)public Result login(RequestBody LoginUser body){MapString, Object data new HashMap();log.info({},{}, body.getName(),body.getPassword());data.put(name, body.getName());data.put(password, body.getPassword());String token JwtUtils.generatorToken(data);return Result.success(token);};
}使用对应的拦截器进行校验
Component(loginInterceptor)
public class LoginInterceptor implements HandlerInterceptor {Overridepublic boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {System.out.println(preHandle..... req.getRequestURL());String url req.getRequestURL().toString();if(url.contains(login)){// 登陆接口不需要校验return true;}String token req.getHeader(token);try {if (token ! null) {Claims data JwtUtils.parseToken(token);req.setAttribute(user, data);return true;} else {throw new Exception(token不存在或者过期);}} catch (Exception e) {Result error Result.error(token is not exists or expire);res.addHeader(Content-Type, application/json);res.getWriter().write(JSONObject.toJSONString(error));return false;}}
}Configuration
public class SpringMvcConfigure implements WebMvcConfigurer {Resource(nameloginInterceptor)LoginInterceptor loginInterceptor;Overridepublic void addInterceptors(InterceptorRegistry registry){registry.addInterceptor(loginInterceptor).addPathPatterns(/**); //该拦截器拦截所有请求}
}登陆接口不需要验证让该拦截器拦截对应请求拿到token进行解析解析到用户数据就塞入req中后续controller就可以拿到该用户信息。
异常处理
程序开发过程中不可避免会碰到异常有时候返回的信息并不是后端统一的信息。 像nest可以配置全局异常过滤器会俘获所有的异常然后统一信息返回。
方案
java也可以定义全局异常处理器。
通过注解RestControllerAdvice指定这是一个controller异常处理器,RestControllerAdvicd中也包含ResponseBody表示所有的方法返回的值会被转为json传给前端(实际上所有的请求传输都是字符串只不过设置了contentType为json浏览器会自动识别contentType处理。)。通过ExceptionHandler(Exception.class)指定拦截什么类型的异常Exception.class就是指拦截所有的异常。
import com.example.demo.pojo.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;RestControllerAdvice
public class GlobalExceptionHandler {ExceptionHandler(Exception.class)public Result ex(Exception ex){ex.printStackTrace();return Result.error(服务器报错: ex.getMessage());}
}事务管理 springboot提供了Transactional注解来开启事务。
回滚异常 Transactional注解可以传入值可以控制出现什么异常的情况下回滚事务。默认只有运行时异常才会处理。
事务传播行为
事务a方法调用事务b方法的时候 比如在a中调用b的方法当a失败后事务回滚会导致b方法执行的逻辑也会回滚事务传播行为默认是有则加入也就是b方法会加入当前a方法的事务中。
将其改为REQUIRES_NEW在a中调用b的时候会先挂起a的事务然后起一个b的事务当a失败后a执行的逻辑会回滚但是b方法执行的逻辑如果没报错会保留。 案例 删除一个部门并将该部门下所有员工删除不管删除失败成功都需要记录日志
如下启用Propagation.REQUIRES_NEW在调用创建日志方法的时候并不会受原本事务的影响 Transactional(rollbackFor Exception.class, propagation Propagation.REQUIRES_NEW)public Result deleteDepts(Integer id) {try {this.dept.deleteDept(id);this.emp.deleteEmpByJobId(id); //删除员工表下该部门的员工return Result.success(0);} catch (Exception e) {return Result.error(e.getMessage());} finally {// 记录日志 也是一个事务方法用REQUIRES_NEW则不会被上述逻辑影响会正常记录日志logger.create(new Date(), 删除)}}AOP 实现 编写AOP类交给IOC容器管理。 通过joinPoint可以拿到原始方法。
AOP核心概念 连接点joinPoint可以被AOP控制的方法比如上图的list,delete,save等方法都属于连接点。通知Advice重复逻辑比如上图的recordTime方法共性逻辑所有连接点都会执行切入点pointCut顾名思义就是在哪里切入这个类匹配连接点的条件。切面描述通知与切入点的对应关系如上图的Around(切入点表达式)recordTIme(通知)方法就是切面。目标对象TargetAO类所应用的对象。
AOP的执行流程
AOP类是如何作用于目标对象的 上述说过AOP是通过动态代理实现的如上DeptServiceImpl类是AOP的目标对象他会根据通知生成一个代理类DeptServiceProxy然后重新list方法执行AOP类的逻辑最后如上通过Autowired注入的deptService就不是DeptServiceImpl而是DeptSerivceProxy这个代理对象所以执行list方法的时候就会执行代理对象list从而执行通知的逻辑
通俗的说AOP类会在不影响目标对象代码的基础上基于目标对象新增一些其他的逻辑通过代理的形式生成一个新的类交给IOC容器.
案例 记录每个controller的耗费时间
可以用拦截器做到也可以用过滤器做到也可以用AOP做到。
Component
Aspect
Slf4j
public class TimeAspect {Around(execution(* com.example.demo.controller.*.*(..)))public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable{long begin System.currentTimeMillis();Object object joinPoint.proceed(); //调用原方法long end System.currentTimeMillis();log.info(joinPoint.getSignature() 执行耗时: {}ms, end- begin);return object; //返回}
} AOP高阶
通知类型
上述我们使用的Around就是通知类型。他的功能最强大可以编写目标方法执行前的逻辑也可以编写目标方法执行后的逻辑可以拿到目标方法 Pointcut注解可以抽离公共的切入点表达式服用切入点表达式
通知的执行顺序
当有多个通知都匹配到同一个切入点时目标方法执行多个通知方法都会执行。 跟过滤器一样其实是根据AOP的类名字母排序有关且before和after的执行顺序就跟洋葱模型一样也是234 - 432这样执行。
除此之外还可以通过Order注解来标记执行顺序。
切入点表达式 通配符号 * 和 … 如 Around(execution(* com.*.demo.controller.*.update*(*)))* com表示 匹配任何返回值 com.*.demo表示二级包是任意的 controller.*表示controller下的类或者接口是任意的 update*表示以update开头的类或者接口 (*)表示匹配一个参数 上述表示 任何的返回值com下任意的二级包里含有的demo三级包下的controller目录下的任意的以类或者接口下以update开头且只能有一个参数的方法 如果换成… 即 Around(execution(* com..controller.*.update*(..)))com…可以匹配任意层级的包 update*(…)表示匹配update开头的方法且可以有任意参数。 上述表示 任何的返回值com下任意的二级包里含有controller的目录下的任意的以类或者接口下以update开头且只能有一个参数的方法 多个切入点表达式可以用 || 等组合。
annotation 匹配标识有特定注解的方法 定义一个注解用来标识
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;Retention(RetentionPolicy.RUNTIME)
Target(ElementType.METHOD)
public interface Mylog {}
将execution切换成annotation
public class TimeAspect {//Around(execution(* com.example.demo.controller.*.*(..)))Around(annotation(com.example.demo.aop.Mylog))public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable{long begin System.currentTimeMillis();Object object joinPoint.proceed(); //调用原方法long end System.currentTimeMillis();log.info(joinPoint.getSignature() 执行耗时: {}ms, end- begin);return object; //返回}
}
连接点
对于Around只能通过ProceddingJoinPoint获取连接点信息如下 对于其他四种通知 只能通过JoinPoint获取连接点信息。
Aspect
Component
Slf4j
public class TimeAspect {Around(execution(* com.example.demo.controller.*.*(..)))public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable{long begin System.currentTimeMillis();String className joinPoint.getTarget().getClass().getName();Signature signature joinPoint.getSignature();String methodName signature.getName();Object[] args joinPoint.getArgs();log.info(打印结果{},{},{},{}, className, signature, methodName, Arrays.toString(args));Object object joinPoint.proceed(); //调用原方法long end System.currentTimeMillis();log.info(joinPoint.getSignature() 执行耗时: {}ms, end- begin);return object; //返回}
}案例 对增删改操作进行日志写入操作
使用AOP完成该功能。 定义注解
Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
public interface OperatorLog {
}
在增删改方法上加上该注解 编写AOP代码
Component
Aspect
Slf4j
public class OperatorLog {// 通过注解获取request对象Autowiredprivate HttpServletRequest request;Around(annotation(com.example.demo.aop.OperatorLog))public Object createLog(ProceedingJoinPoint joinPoint) throws Throwable {long begin System.currentTimeMillis();Claims user (Claims) request.getAttribute(user);String className joinPoint.getTarget().getClass().getName();Signature signature joinPoint.getSignature();String methodName signature.getName();Object[] args joinPoint.getArgs();Object object joinPoint.proceed(); //调用原方法long time begin - System.currentTimeMillis();Log newLog new Log((String) user.get(name),time, methodName, Arrays.toString(args),className);log.info(插入一条日志: {},newLog);Log.insertLogs(newLog);return object; //返回}
}
通过注解获取request对象在拦截器的时候将user注入到req里面去所以这里可以获取得到。然后插入一条日志。