自定义权限校验注解、枚举和注解和新特性(反射+注解)

枚举和注解

一:如何自定义枚举类(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 四个元注解

什么是元注解:可以理解为,修饰其他注解的注解

  1. Retention
  2. Target
  3. 下面俩个在自定义注解的时候不常用
  4. Documented
  5. 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_PARAMETERTYPE_USE两个枚举值,从而可以使用@Target(ElementType_TYPE_USE)修饰注解定义,这种注解被称为类型注解,可以用在任何使用到类型的地方

  • TYPE_PARAMETER:表示该注解能写在类型参数的声明语句中。
    1. 类型参数声明如: <T> <T extends Person>

  • TYPE_USE:表示注解可以再任何用到类型的地方使用,比如允许在如下位置使用:
    1. 创建对象(用 new 关键字创建)
    2. 类型转换
    3. 使用 implements 实现接口
    4. 使用 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) {

    }
}

解释:

  1. 可以让编译器执行更严格的代码检查
  2. 需要指出的是,上面程序虽然使用了大量 @NotNull 注解,但是这些注解暂时不会起任何作用
  3. 因为没有为这些注解提供处理工具,java8本身并没有提供,要想这些注解发挥作用,需要开发者自己实现,或者使用第三方提供的工具

小编感觉一篇比较好的java8中对类型注解解释比较好的博文==>入口


完结

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

suqinyi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值