枚举和注解
一:如何自定义枚举类(JDK 1.5后推出的enum修饰枚举类)二:注解的说明,JDK8中注解的新特性,和小案例(自定义注解+拦截器-权限校验)
文章目录
一、枚举
枚举类的实现方式2种:
- JDK1.5之前需要自定义枚举类(没啥人用了吧)
- JDK 1.5 新增的 enum 关键字用于定义枚举类
- 说明:枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰
- 枚举类也可以实现接口,和普通的Java类一样
1.1 使用enum 定义枚举类:
(JDK1.5之前需要自定义枚举类那种方式,就不总结了)
说明:
- 使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再继承其他类
- 枚举类的构造器只能使用 private 权限修饰符
- 枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。
- 列出的实例系统会自动添加 public static final 修饰
必须在枚举类的第一行声明枚举类对象
而使用enum 定义枚举类就不用使用 private final 修饰了
原因:
1.2 案例:
/**
* @author suqinyi
* @Date 2021/4/11
*/
public class WareConstants {
/**
*第一个枚举
*/
public enum PurchaseStatusEnum {
//等同于 public static final WareConstants CREATED= new WareConstants (0, "新建");
//第一步:创建枚举对象
CREATED(0, "新建"),
ASSIGNED(1, "已分配"),
RECEIVE(2, "已领取"),
FINISH(3, "已完成"),
HASHERROE(4, "有异常")
;
//2.声明属性
private int code;
private String mes;
//3.构造函数
PurchaseStatusEnum(int code, String mes) {
this.code = code;
this.mes = mes;
}
//get 方法
public int getCode() {
return code;
}
public String getMes() {
return mes;
}
}
//第二个枚举
public enum PurchaseDetailStatusEnum {
CREATED(0, "新建"),
ASSIGNED(1, "已分配"),
BUYING(2, "正在采购"),
FINISH(3, "已完成"),
HASHERROE(4, "采购失败")
;
private int code;
private String mes;
PurchaseDetailStatusEnum(int code, String mes) {
this.code = code;
this.mes = mes;
}
public int getCode() {
return code;
}
public String getMes() {
return mes;
}
}
}
使用
方式:WareConstants.PurchaseStatusEnum.对象.getxxx
例如:WareConstants.PurchaseStatusEnum.CREATED.getCode()
1.3 Enum类的主要方法(用不太到)
3个常用:
- values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
- valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
- toString():返回当前枚举类对象常量的名称
二、注解(Annotation)
说明
:框架 = 注解 + 反射 + 设计模式。
2.1 四个元注解
什么是元注解:可以理解为,修饰其他注解的注解
- Retention
- Target
下面俩个在自定义注解的时候不常用
- Documented
- Inherited
第一个 @Retention:注解的保留位置
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
第二个 @Target:注解的作用目标
@Target(ElementType.TYPE) //接口、类、枚举
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包
第三个 @Document:
说明该注解将被包含在javadoc中
第四个@Inherited:
说明子类可以继承父类中的该注解
2.2: 自定义注解+拦截器(权限校验)
2.3: 参考下@autowired注解是怎么定义的,在模仿
2.4:自定义的注解
2.4.1 定一个名称叫 UserAuthenticate 注解
/**
* @Target:作用目标
* METHOD:方法上使用。
* ElementType.TYPE 作用范围 :接口、类、枚举
*/
/**
* @Retention
* RetentionPolicy.RUNTIME: 注解会在class字节码文件中存在,在运行时可以通过反射获取到
*/
/**
* @author suqinyi
* @Date 2021/5/1
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAuthenticate {
boolean permission() default true;
}
2.4.2 创建拦截器
创建拦截器类实现 HandlerInterceptor 接口,实现3个方法如图(快捷建ctrl + o):
登录的时候,使用jwt生成token,存入浏览器
全部请求的都经过这个拦截器
package com.filter;
import com.annotation.UserAuthenticate;
import com.entity.ResultParam;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.util.sys.JWTUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
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.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* @author suqinyi
* @Date 2021/5/1
*/
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
/**
* Origin:
* 有referrer功能,针对跨域操作,标准浏览器只要是跨域就会携带此请求头字段,如
* 果后台允许此字段的地址,则正常请求,如果不允许,浏览器就会abort,不产生事件,就好像没有请求过,network也看不到
*/
/**
* ORIGIN
* 在跨域发送请求,会产生俩个请求,一个请求里面的就是 '大头兵 ORIGIN' 先去探路
* 第二个请求才是真正的请求
*/
//获取请求地址
String url = request.getHeader("Origin");
if (request.getHeader(HttpHeaders.ORIGIN) != null && !StringUtils.isEmpty(url) ) {
//跨域
String val = response.getHeader("Access-Control-Allow-Origin");
//如果没有Access-Control-Allow-Origin,就对请求地址添加跨域
if (StringUtils.isEmpty(val)) {
response.addHeader("Access-Control-Allow-Origin", url);
}
/**
* Access-Control-Allow-Credentials:它的值是一个布尔值,表示是否允许发送Cookie。
* 默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。
* 这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
*/
response.addHeader("Access-Control-Allow-Credentials", "true");
/**
* Access-Control-Allow-Methods:该字段必需,
* 它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。
* 注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求
*/
response.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT, HEAD");
/**
* 如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。
* 它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
* 补充一个Token的请求头
*/
response.addHeader("Access-Control-Allow-Headers", "Content-Type, Token");
/**
* 返回结果可以用于缓存的最长时间,单位是秒。
* 在Firefox中,上限是24小时 (即86400秒)
* 而在Chromium 中则是10分钟(即600秒)。Chromium 同时规定了一个默认值 5 秒。
* 如果值为 -1,则表示禁用缓存,每一次请求都需要提供预检请求,即用OPTIONS请求进行检测(即preflight请求-options)。
*/
response.addHeader("Access-Control-Max-Age", "3600");
}
//equalsIgnoreCase 忽略大小写
//request.getMethod():获取请求方式- GET POST ...
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return false;
}
//handler :获取项目下的controller的全路径
if(! (handler instanceof HandlerMethod)) {
return true;
}
//将Object指定成HandlerMethod
HandlerMethod handlerMethod = (HandlerMethod) handler;
//获取 请求方法的全路径
Method method = handlerMethod.getMethod();
//获取方式 ==> 根据反射,判断其方法是否带有UserAuthenticate注解
UserAuthenticate userAuthenticate = method.getAnnotation(UserAuthenticate.class);
//ResultParam 自定义的返回数据对象封装
ResultParam resultParam = new ResultParam();
//Objects.nonNull(obj) 有就返回true,没有就是false
if (Objects.nonNull(userAuthenticate)) {
//重请求头获取Token
String token = request.getHeader("Token");
//是否存在token
if (StringUtils.isEmpty(token)) {
System.out.println("没有token,请重新登录");
response.setStatus(HttpServletResponse.SC_OK);//200
resultParam.setCode(4);
resultParam.setMessage("没有token,请重新登录");
//401代表权限不足
response.setStatus(401);
//===========将某个对象转换成json格式并发送到客户端==================
try {
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = response.getWriter();
ObjectMapper mapper = new ObjectMapper();
String result = mapper.writeValueAsString(resultParam);
writer.print(result);
writer.close();
response.flushBuffer();
} catch (IOException e) {
e.printStackTrace();
}
//=============================================================
return false;
}
//JWTUtil这个就是自己封装的一个工具
//解析token里面的数据-进行校验-->将token传进去,进行解析拿出存的用户名
String username = JWTUtil.getUsername(token);
if (StringUtils.isEmpty(username)) {
System.out.println("无效token,请重新登录 username is null");
response.setStatus(HttpServletResponse.SC_OK);
resultParam.setCode(4);
resultParam.setMessage("无效token,请重新登录");
response.setStatus(401);
//===========将某个对象转换成json格式并发送到客户端==================
try {
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = response.getWriter();
ObjectMapper mapper = new ObjectMapper();
String result = mapper.writeValueAsString(resultParam);
writer.print(result);
writer.close();
response.flushBuffer();
} catch (IOException e) {
e.printStackTrace();
}
//=============================================================
return false;
}
//JWTUtil这个就是自己封装的一个工具
//使用用户名和jwt的密钥校验 token是否正常
//校验:-->(使用jwt的密钥和用户名再次加密生成token,和浏览器的token进行比对是否一致,一致就是正确的,没有被篡改过)
boolean verify = JWTUtil.verifyToken(token, username);
if (!verify) {
System.out.println("token失效,请重新登录");
response.setStatus(HttpServletResponse.SC_OK);
resultParam.setCode(4);
resultParam.setMessage("token失效,请重新登录");
response.setStatus(401);
//===========将某个对象转换成json格式并发送到客户端==================
try {
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = response.getWriter();
ObjectMapper mapper = new ObjectMapper();
String result = mapper.writeValueAsString(resultParam);
writer.print(result);
writer.close();
response.flushBuffer();
} catch (IOException e) {
e.printStackTrace();
}
//=============================================================
return false;
}
}
//放行
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
}
}
2.4.3 使用
在你想要进行权限校验的controller上面加注解
例如:
//这个请求就需要校验token,没有token或者不正确就无法访问,没有加注解就不用校验
@UserAuthenticate//自定的权限校验注解
@RequestMapping("test")
public HashMap test(){
HashMap<String, String> map = new HashMap<>();
map.put("title","需要权限校验");
return map;
}
补充说明:
那在前端怎么在请求头里面加token呢?
例如:可以使用对axios进行二次封装,全局定义,这样就比较便捷
如果不知道axios是啥,那就自行百度了
权限校验总结:
01:小编这个mvc拦截器单纯只校验了一个token(存在浏览器中的)
02: 拓展:我们其实可以在浏览器存一份jwt的token,使用redis在存一份其他数据。
03: 在进行双重校验,这样其实安全性会更加安全
2.5 JDK8中注解的新特性
Java 8对注解处理提供了两点改进:
- 可重复的注解
- 可用于类型的注解。
- 额外==>反射也得到了加强,在Java8中能够得到方法参数的名称。这会简化标注在方法参数上的注解
2.5.1 重复注解
说明:即允许在同一申明类型(类,属性,或方法)前多次使用同一个类型注解。
1.8之前用法:
在java8 以前,同一个程序元素前最多只能有一个相同类型的注解
如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”
public @interface Authority {
String role();
}
public @interface Authorities { //@Authorities注解作为可以存储多个@Authority注解的容器
Authority[] value();
}
public class RepeatAnnotationUseOldVersion {
@Authorities({@Authority(role="Admin"), @Authority(role="Manager")})
public void doSomeThing(){
}
}
使用1.8新增特性,重复注解:
@Repeatable(Authorities.class)
public @interface Authority {
String role();
}
public @interface Authorities {
Authority[] value();
}
public class RepeatAnnotationUseNewVersion {
@Authority(role="Admin")
@Authority(role="Manager")
public void doSomeThing(){ }
}
解释:
- 不同的地方是,创建重复注解 Authority 时,加上@Repeatable,指向存储注解Authorities,在使用时候,直接可以重复使用 Authority 注解。
- 从上面例子看出,java 8里面做法更适合常规的思维,可读性强一点。但是,仍然需要定义容器注解。
- 两种方法获得的效果相同。重复注解只是一种简化写法,
- 这种简化写法是一种假象:多个重复注解其实会被作为“容器”注解的 value 成员的数组元素处理
2.5.2 类型注解
java8 以前
在 java8 以前,注解只能用在各种程序元素(定义类、定义接口、定义方法、定义成员变量…)上。从 java8开始,类型注解可以用在任何使用到类型的地方。
Java8 新增
Java8 为 ElementType 枚举增加了
TYPE_PARAMETER
、TYPE_USE
两个枚举值,从而可以使用@Target(ElementType_TYPE_USE)
修饰注解定义,这种注解被称为类型注解,可以用在任何使用到类型的地方
TYPE_PARAMETER
:表示该注解能写在类型参数的声明语句中。- 类型参数声明如:
<T> <T extends Person>
- 类型参数声明如:
TYPE_USE
:表示注解可以再任何用到类型的地方使用,比如允许在如下位置使用:- 创建对象(用 new 关键字创建)
- 类型转换
- 使用 implements 实现接口
- 使用 throws 声明抛出异常
示例:
@Target(ElementType.TYPE_USE)
@interface NotNull{
}
// 定义类时使用
// 在implements时使用
@NotNull
public class TypeAnnotationTest implements Serializable {
// 在方法形参中使用
// 在throws时使用
public static void main(@NotNull String [] args) throws @NotNull FileNotFoundException {
Object obj = "fkjava.org";
// 使用强制类型转换时使用
String str = (@NotNull String) obj;
// 创建对象时使用
Object win = new (@NotNull) JFrame("疯狂软件");
}
// 泛型中使用
public void foo(List<@NotNull String> info) {
}
}
解释:
- 可以让编译器执行更严格的代码检查
- 需要指出的是,上面程序虽然使用了大量 @NotNull 注解,但是这些注解暂时不会起任何作用
- 因为没有为这些注解提供处理工具,java8本身并没有提供,要想这些注解发挥作用,需要开发者自己实现,或者使用第三方提供的工具
小编感觉一篇比较好的java8中对类型注解解释比较好的博文==>入口
完结