# ISO **Repository Path**: wan_you_to/iso ## Basic Information - **Project Name**: ISO - **Description**: Java学习笔记项目 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-09-14 - **Last Updated**: 2024-11-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 异常处理 ## Throwable:所有错误和异常的超类 Error:程序本身无法处理的一种严重问题,一般是系统级别的问题,如:系统崩溃、虚拟机错误等,无法恢复、无法捕捉。 Exception:异常,是程序设计方面的错误,为非致命的错误,一般可以通过异常捕捉使来处理。 RuntimeException(不检查):运行时异常,是程序缺陷所引起的异常,java虚拟机不会去检查此错误,程序应该从逻辑角度尽可能避免这类异常的发生。 NullPointerException :空指针异常 classnotfoundexception:类不存在异常 illegalargumentexception:方法的参数异常 NoSuchMethodError:方法不存在 NumberFormatException :数字格式异常 ClassCastException :类型强制转换异常。 IllegalAccessException :无访问权限异常 IllegalArgumentException:传递非法参数异常。 ArithmeticException :算术运算异常(用了0作为除数) ArrayIndexOutOfBoundsException :数组下标越界异常 IndexOutOfBoundsException :下标越界异常 ……… 非运行时异常(检查):是必须进行处理的异常,如果不处理,程序就不能编译通过。 IOException :io异常 SQLException :sql语句异常 在Java中异常的种类多种多样,若是使用try…catch…finally去匹配异常类型,会导致大量的代码冗余,且用户端在报异常时拿到的数据不可控 所以需要配置一个全局的异常处理,当程序报错时返回给用户的数据,若是接口中使用了try…catch…finally则不影响原逻辑。 ## 使用 ```java // 全局异常处理器 /* * 此配置将所有的异常全部格式化,以规定的处理抛出异常 * 原本的异常是直接抛出,而使用全局异常则会将异常拦截后处理抛出 * * */ @RestControllerAdvice public class Global { @ExceptionHandler(Exception.class)// Exception.class 所有的异常 public ResultList ex(Exception ex){ ex.printStackTrace(); return ResultList.error("系统错误,请联系管理员"); } } ``` # JWT登录鉴权 ## 作用 对用户登录的信息进行加密返回给用户,用户对接其他需鉴权的接口携带参数进行鉴权 ## 使用 ### 配置 需要在pom.xml文件中加入依赖即可 ```xml io.jsonwebtoken jjwt 0.9.1 io.jsonwebtoken jjwt-api 0.11.2 io.jsonwebtoken jjwt-impl 0.11.2 runtime io.jsonwebtoken jjwt-jackson 0.11.2 runtime ``` ### 加密 ```java import java.util.Map; public String GetJWT(Map claims) { // JWT加密生成token return Jwts.builder() .signWith(SignatureAlgorithm.HS256, "zhangsan")// 签名算法HS256,加密字符串; .setClaims(claims)// 自定义加密内容 .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))// 设置有效期为1h .compact();// 生成jwt返回字符串 } Map claims = new HashMap<>(); claims.put("id","张三"); String jwt = GetJWT(claims); ``` 加密过程主要通过HS256进行加密,并且设置时效为1h ### 解密 ```java public Claims ParseJWT(String str){ return Jwts.parser() .setSigningKey("zhangsan")// 解密字符串 .parseClaimsJws(str)// 解密内容 .getBody();// 返回数据 } ``` ## 区别 cookie和seesion与token 1. cookie在响应体中携带具体数据,每次在用户的请求体中浏览器会自动携带,数据未加密不安全,并且浏览器可限制禁用token 2. seesion是服务端发送id值的形式给用户,用户得到的不是具体值,具体数据在服务端,相对安全,但是不适合当下的服务器集群环境。并且具备cookie的所有缺点 3. token是将数据加密后进行传输,浏览器只能删除token值删除后可以重新获取token,前端也可验证token是否过期,使用HS256加密相对安全 # 拦截器与过滤器 ## 过滤器 ### @WebFilter(urlPatterns = "/*") 在过滤器类上加上@WebFilter注解表示为该此程序开启一个过滤器,对对应的接口生效 配置过滤器需要过滤哪些数据/*表示过滤全部接口,/login表示只会拦截login路径,/emps/*只会拦截emps下的所有路径比如/emps/1 ### 状态 1. init:在程序开始时执行一次, 2. doFilter:在用户每次访问接口时都需要执行,**由于该方法用于控制接口返回,所以需要在合适的位置放行否则接口内容无法访问** 3. destory:在程序关闭时执行一次,多用于记录程序的异常关闭的情况 ```java @WebFilter(urlPatterns = "/*") public class DemoFilter implements Filter { public void init(){};// 初始化方法,只会调用一次 // 拦截请求之后调用,会调用多次 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){ chain.doFilter(request,response); return; } public void destory(){}// 销毁方法,只会调用一次 } ``` ### 使用 常用的过滤器方法是doFilter,该方法会在每次调用接口时都运行。一般用于应用的前置登录鉴权 1. ServletRequest request:请求体 2. ServletResponse response:数据返回体 3. FilterChain chain:放行拦截 ### 搭配JWT登录鉴权使用 ```java public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("请求过滤器启动。。。"); HttpServletRequest req= (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; // 获取请求url String url = req.getRequestURL().toString(); log.info("请求过滤器获取到的url地址为:{}",url); // 判断请求url中是否包含login,如果包含,说明是登录操作,放行 if(url.contains("login")){ log.info("登录操作,放行..."); // 放行 chain.doFilter(request,response); return; } // 获取请求头中的令牌 String token = req.getHeader("token"); // 判断令牌是否存在,如果不存在则返回错误(未登录) if(!StringUtils.hasLength(token)){ log.info("token不存在,未返回登录的信息"); ResultList error = ResultList.error("请求头中未携带token"); // 手动转换对象--json------->阿里巴巴fastjson String notLogin = JSONObject.toJSONString(error); // 使用response.getWriter().write返回数据给前端 response.getWriter().write(notLogin); return; } // 解析token ,如果解析失败则令牌过期 try{ JWTUtils.ParseJWT(token); }catch (Exception e){ log.info("token已过期"); ResultList error = ResultList.error(401,"token已过期,请重新登录"); // 手动转换对象--json------->阿里巴巴fastjson String notLogin = JSONObject.toJSONString(error); // 使用response.getWriter().write返回数据给前端 response.getWriter().write(notLogin); return; } // 放行 chain.doFilter(request,response); } ``` ## 拦截器 ### 配置 拦截器启动需要单独声明一个配置类来启动拦截器 ```java // 配置请求拦截器 @Configuration @EnableAspectJAutoProxy public class WebConfig implements WebMvcConfigurer { @Autowired private LoginCheckInterceptor loginCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry){ registry.addInterceptor(loginCheckInterceptor) .addPathPatterns("/**") // 拦截所以请求 .excludePathPatterns("/interceptor/**"); // 排除以interceptor开头的接口 } } ``` ### 方法 1. preHandle:目标资源方法运行前运行,返回true放行,返回false不放行 2. postHandle:目标资源接口方法运行后运行 3. afterCompletion:视图渲染完毕后运行,最后运行 ### 使用 拦截器在日常使用中preHandle使用最多,在preHandle方法中必须返回一个Boolean值判断程序是否进行下去 ```java @Override // 目标资源方法运行前运行,返回true放行,返回false不放行 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取请求url String url = request.getRequestURI().toString(); log.info("请求拦截器获取到的url地址为:{}",url); // 如果请求以/interceptor开头,直接放行 if (url.contains("interceptor")) { log.info("请求为/interceptor开头,放行..."); return true; } // 判断请求url中是否包含login,如果包含,说明是登录操作,放行 if(url.contains("login")){ log.info("登录操作,放行..."); return true; } // 获取请求头中的令牌 String token = request.getHeader("token"); // 判断令牌是否存在,如果不存在则返回错误(未登录) if(!StringUtils.hasLength(token)){ log.info("token不存在,未返回登录的信息"); ResultList error = ResultList.error("请求头中未携带token"); // 手动转换对象--json------->阿里巴巴fastjson String notLogin = JSONObject.toJSONString(error); // 使用response.getWriter().write返回数据给前端 response.getWriter().write(notLogin); return false; } // 解析token ,如果解析失败则令牌过期 try{ JWTUtils.ParseJWT(token); }catch (Exception e){ log.info("token已过期"); ResultList error = ResultList.error(401,"token已过期,请重新登录"); // 手动转换对象--json------->阿里巴巴fastjson String notLogin = JSONObject.toJSONString(error); // 使用response.getWriter().write返回数据给前端 response.getWriter().write(notLogin); return false; } // 放行 return true; } ``` ## 拦截器与过滤器的对比 拦截器与过滤器的方式基本一样,但是拦截器的配置相对麻烦。需要单独声明一个配置类去使用 在http请求中是先经过filter过滤器再通过拦截器后去访问资源,所以按优先级来看拦截器的优先级更高 ![img.png](img.png) # 事务 ## @Transactional 在类上加上@Transactional注解会在这个类开启一个事务 此事务遵循一个原则(同时提交或同时回滚) xmlUser.creatStudent(stu); int num = 1/0; xmlUser.deleteFrom(12); 在执行上述代码时1/0会报Runtime Exception运行时异常错误,所以导致xmlUser.deleteFrom(12);不执行,加上注解事务会整体回滚 即xmlUser.creatStudent(stu);也执行不成功 ## 事务属性 ### rollbackFor 默认情况下,只有出现Runtime Exception运行时异常才会回滚,rollbackFor属性用于控制出现异常类型,回滚事务 @Transactional(rollbackFor = {IOException.class, SQLException.class}) 常用的异常类有: Exception:所有异常都触发回滚。 RuntimeException:所有运行时异常都触发回滚。(默认值) Throwable:所有可抛出的异常都触发回滚。 ### propagation | 属性值 | 含义 |:---------------|:------------------------------| | **REQUIRED** | 默认值,支持当前事务,如果不存在,则新建一个事务。 (常用) | **REQUIRES_NEW** | 总是新建一个事务,如果存在一个事务,则挂起当前事务。(常用) | NOT_SUPPORTED | 以非事务方式运行,如果存在事务,则挂起当前事务。 | MANDATORY | 如果存在事务,则在该事务中运行;否则抛出异常。 | NEVER | 以非事务方式运行,如果存在事务,则抛出异常。 | NESTED | 如果存在事务,则在嵌套事务中运行;否则新建一个事务。 # AOP(面向切面编程) ## 配置安装依赖 ```xml org.springframework.boot spring-boot-starter-aop ``` ## @Aspect 在类上添加该注解表示为spring项目添加开启一个AOP ## 定义通知类型 | 名称 | 注解名 | 含义 |:----|:----------------|:-------| |前置通知| @Before |在目标方法调用之前调用通知 |后置通知| @After |在目标方法完成之后调用通知 |环绕通知| @Around(常用) |在被通知的方法调用之前和调用之后执行自定义的方法 |返回通知| @AfterReturning |在目标方法成功执行之后调用通知 |异常通知| @AfterThrowing |在目标方法抛出异常之后调用通知 通知注解中需要传入作用于哪个作用域方法的具体参数 ## execution ```java @Around("execution(* com.example.controller.*.*(..))") /* execution(修饰符?匹配目标类路径?具体方法?(参数个数)) * :表示访问修饰符(public,*) com.example.controller: 目标方法在哪个路径下 .* :controller目录下所有的类<若是想只作用于哪个类需要直接.类名> *server<通配server开头的类> server*<通配server结尾的类> (..)方法需要携带的参数,..<任意个数的参数>,(*)方法必须携带一个参数的 */ ``` ### @Pointcut 此注解可以提前声明类名路径即通知类型参数,全局可用,本类中使用直接方法名()其他AOP使用可以具体路径() 使用方法 ```java @Pointcut("execution(* com.example.controller.upload.*(..))") public void put() { } // 声明类中使用 @Around("put()") public void BeforeTime(){} // 其他AOP类使用 @Around("com.example.aop.insertAop.put()") public void BeforeTime(){} ``` ## @annotation 同样这个也是作用于当前通知类型中的参数,不过这个是单类匹配,也就是需要哪个aop控制哪个方法就在那个方法上加注解就行 ### 使用: 1. 声明对应注解 2. 将该注解声明在对应方法上 3. 在通知注解中携带@annotation(注解具体地址) ### 例如: #### A. 声明注解 ```java //声明注解 /* * @Retention(RetentionPolicy.RUNTIME):表示这个注解在运行时仍然有效,可以被反射机制读取和使用。 * @Target(ElementType.METHOD):表示这个注解只能被放置在类的方法上。 * 当前注解起一个标识的作用 * * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyLog { } ``` #### B.作用注解到对应方法 ```java import com.example.aspect.MyLog; @MyLog public void List(){} ``` #### C.使用注解在AOP ```java @Around("@annotation(com.example.aspect.MyLog)") // @annotation(com.example.aspect.MyLog)这个标识符表示只有加上这个注解的类才会被aop控制 public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {} ``` ## @Order() 在AOP控制中可能会有多个AOP控制类,执行顺序为: before:按类名字母排序正序 after:按类名字母排序倒序 AOP控制类多的情况下去改类名会比较麻烦,若是需要在修改执行先后顺序可以使用@Order(数字),括号中数字越小越先执行 ## @Around 这里着重讲解一下Around这个通知,因为AOP中这个通知类使用最多,可操作最多 ```java // 获取目标对象的类名 String className = joinPoint.getTarget().getClass().getName(); // 获取目标方法的方法名 String methodName = joinPoint.getSignature().getName(); // 获取目标方法运行时传入的参数 Object[] args = joinPoint.getArgs(); // 获取目标方法运行后的返回值 Object result = joinPoint.proceed();// 调用原始操作 return result;// 在Around通知类中必须返回值,否则目标方法无法获取到值,相当于在这里把数据拦截了 ``` 上述讲解了一些Around中最常见的方法使用,总得来说AOP更像一种监控,监控管理员的增删改这种危险操作。以作备份操作权限 ## @Bean ### 作用 1. 第三方的bean对象无法使用@Component以及其他衍生注解声明beaan,所以需要管理第三方bean就需要使用@Bean对第三方bean进行管理 2. 若是要对第三方的bean对象进行管理,建议对这些bean进行分类配置,可以通过@Configuration注解进行声明一个配置类 ### 使用方式 1. 配置文件(推荐) ```java @Configuration// 配置类注解 public class WebConfig implements WebMvcConfigurer { @Bean// 声明Bean全局使用 public logInfo loginfo(){ return new logInfo(); } } ``` 2. 启动类配置(不推荐) ```java @SpringBootApplication// springboot启动注解 class StudentsApplicationTests { @Bean// 声明Bean全局使用 public logInfo loginfo(){ return new logInfo(); } } ``` ### 注意方式 1. 通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名 2. 如果第三方bean需要依赖其他bean对象,直接在bean定义方法中设置形参即可,容器会默认自动装配 ```java import javax.xml.parsers.SAXParser; @Bean// 声明Bean全局使用 public logInfo loginfo(SAXParser Saxparser) { // 第三方需要这个bean依赖则可以直接以形参的方式装配 return new logInfo(); } ``` # maven版本锁定 ## 锁定 在pom.xml文件中,挨个去找依赖的版本号是比较麻烦的,并且在工程与工程之间有联系的情况下无法准确的控制依赖版本 可以使用标签进行版本声明 ```xml 3.17.4 com.aliyun.oss aliyun-sdk-oss ${oss.version} ``` 在上述演示中在properties标签中声明了oss.version对oss版本进行控制。