反射与自定义注解 笔记

文章目录

目录


1.什么是反射、反射优缺点

1.1 反射的定义

官网定义反射使Java代码能够发现有关已加载类的字段、方法和构造函数的信息,并在安全限制内使用反射的字段、方法和构造函数对其底层对应项进行操作

Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁

1.2 反射的优缺点

第三方框架创建对象:不是直接new创建而是通过反射机制创建

优点:提供开发者能够更好封装框架实现扩展功能

缺点:反射会增大系统开销 ;反射破环规则,会访问到私有变量,还会逃避泛型的检查


2. 反射的用途/反射应用场景

2.1 反射相关的几个类与方法

Class类 :代表类的实体,在运行的Java应用程序中表示类和接口

Field类 :代表类的成员变量(成员变量也称为类的属性)

Method类 :代表类的方法

Constructor类 :代表类的构造方法

getField、getMethod和getCostructor方法可以获得指定名字的域、方法和构造器。

getFields、getMethods和getCostructors方法可以获得类提供的public域、方法和构造器数组,其中包括超类的共有成员。

getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员

2.2 通过反射机制访问java对象的属性,方法,构造方法等

三种获取.class的方式:

  1. Class<Student> c1 = Student.class;
  2. Class<? extends Student> c2 = new Student().getClass();
  3. Class<?> c = Class.forName("day0424.Student");

C1 == C2 ==C,因为内存种只保留有一份.class信息,所以获取到的类对象都是一样的

------------------------------------学生类---------------------------------
class Student{
    // 属性
    private int no;
    private String name;

    // 构造
    public Student() {
        System.out.println("无参构造");
    }

    public Student(int no, String name) {
        this.no = no;
        this.name = name;
        System.out.println("带参构造:" + no + ":" + name);
    }

    // 方法
    public void function() {
        System.out.println("这是一个无参的方法");
    }


    public String method(String str,int num) {
        return "带参方法:" + str + "," + num;
    }
}

----------------------------------------测试类---------------------------------

public class TestStudent {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {


        //  对字节码文件操作。
        // 1. 获得字节码文件对应的对象        // (1)
//      Class<Student> c1 = Student.class;

        // (2)通过对象.getClass
//      Class<? extends Student> c2 = new Student().getClass();


        // (3)通过Class.forName("类的完整路径")
        Class<?> c = Class.forName("day0424.Student");


------------------------------属性-------------------------------------------

        // 获得所有的属性
        Field [] fds = c.getFields();// 获得所有公共的属性
        fds = c.getDeclaredFields();// 包括私有的

        for(Field fd : fds) {
            System.out.println(fd.getName());// 获得属性
            System.out.println(fd.getType());// 获得类型
            System.out.println(Modifier.toString(fd.getModifiers()));// 获得访问权限
        }




        // 操作属性:赋值,获得属性值
        Field fd = c.getDeclaredField("name");// 获得了name属性
        fd.setAccessible(true);// 可以访问私有成员
        Object obj = c.newInstance();// 创建一个类的对象

        fd.set(obj, "郭靖");// 给属性赋值

        System.out.println(fd.get(obj));// 获得属性值


 ------------------------方法------------------------------------------------

        // 获得所有方法

        Method [] mds = c.getDeclaredMethods();

        for(Method md : mds) {
            System.out.println(md.getName());
            System.out.println(md.getReturnType());// 获得返回值类型
            System.out.println(Arrays.toString(md.getParameterTypes()));// 获得参数列表型                       
        }


        // 调用方法
        // 无参无返回值
        Method md = c.getDeclaredMethod("function");

        md.invoke(obj);

        // 带参带返回值
        md = c.getDeclaredMethod("method", String.class,int.class);
        System.out.println(md.invoke(obj, "hello",999));


---------------------构造----------------------------------------------------
        //获得构造
        Constructor [] crs = c.getDeclaredConstructors();
        for(Constructor cr : crs) {
            System.out.println(Arrays.toString(cr.getParameterTypes()));
        }


        // 调用有参构造
        Constructor cr = c.getDeclaredConstructor(int.class,String.class);
        Student s1 = (Student) cr.newInstance(111,"杨康");
        
        //调用无参构造
        cr = c.getConstructor();
        Student s2 = (Student)cr.newInstance();

    }
}

2.3 JDBC加载驱动连接 class.forname

Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动


Connection conn = null;
try {
//1.数据库连接的4个基本要素
//①JDBC URL用于标识一个被注册的驱动程序,驱动程序管理器通过这个URL选择正确的驱动程序,从而建立到数据库的连接;
/*jdbc:表示URL中的协议
mysql:子协议,用于标识mysql数据库驱动程序
localhost:本机的IP地址
3306:端口号
test:表示访问test数据库*/
String url = "jdbc:mysql://localhost:3306/test";
//user:登录数据库的用户名
String user = "root";
//password:用户名对应的密码,这些都是自己之前设定的
String password = "123456";
//mySql的驱动:com.mysql.jdbc.Driver
String driverName = "com.mysql.jdbc.Driver";
             
//2.实例化Driver
Class clazz = Class.forName(driverName);
Driver driver = (Driver) clazz.newInstance();
             
//3.通过DriverManager来注册驱动
DriverManager.registerDriver(driver);

//4.通过DriverManager的getConnection方法,获取Connection类的对象
conn = DriverManager.getConnection(url, user, password);
}

2.4 Spring容器框架IOC实例化对象

 <bean id="student" class="com.hqq.UserEntity"/>

3.反射如何越过泛型检查

场景:有个泛型为<String>的ArrayList,若想添加一个Integer的元素,直接调用add()是不行的!

解决办法:利用反射,获取add()方法,越过泛型检查,add()->加入Object对象

常见报错:加入Integer的list,调用forEach的时候依然会检查每个元素的类型,遇到Integer的时候会报错

public class Test05 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //泛型为String 的集合
        ArrayList<String> list = new ArrayList();
        list.add("String");

        //添加失败
//        list.add(123);

           
        //利用泛型:获得字节码文件对应的对象;获取add()方法;调用add()方法
        Class<? extends ArrayList> aClass = list.getClass();
        Method add = aClass.getDeclaredMethod("add", Object.class);
        add.invoke(list,123);

        //["String",123]
        System.out.println(list);

        //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
        //list.forEach(t -> System.out.println(t));
    }
}

 4.什么是注解/注解生效的原理

4.1 注解概念

注解用来给类声明附加额外信息,可以标注在类、字段、方法等上面,编译器、JVM以及开发人员等都可以通过反射拿到注解信息,进而做一些相关处理

SpringBoot 全部都是采用注解化

4.2 常用注解

  • @Override     只能标注在子类覆盖父类的方法上面,有提示的作用
  • @Deprecated    标注在过时的方法或类上面,有提示的作用
  • @SuppressWarnings("unchecked")   标注在编译器认为有问题的类、方法等上面,用来取消编译器的警告提示,警告类型有serial、unchecked、unused、all

4.3 元注解

元注解:在自定义注解的上方添加的一种注解,专为自定义注解服务,提供一些特性,功能

@Target 指定新注解标注的位置,比如类、字段、方法等,取值有ElementType.Method等

  • TYPE:类、接口(包括注解类型)和枚举的声明
  • FIELD:字段声明(包括枚举常量)
  • METHOD:方法声明
  • PARAMETER:参数声明
  • CONSTRUCTOR:构造函数声明
  • LOCAL_VARIABLE:本地变量声明
  • ANNOTATION_TYPE:注解类型声明
  • PACKAGE:包声明
  • TYPE_PARAMETER:类型参数声明,JavaSE8引进,可以应用于类的泛型声明之处
  • TYPE_USE:JavaSE8引进,此类型包括类型声明和类型参数声明
  • 举例:

场景:自定义一个注解,在此上方调用使用@Target注解,指定在方法,类,属性上可以使用

  • -----------------------------自定义注解---------------
    @Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
    public @interface MyAnnotation {
    }
    
    ------------------------注解的使用------------------
    @MyAnnotation
    public class Entity {
        @MyAnnotation
        private int age;
        
        @MyAnnotation
        public void get(){
            System.out.println("这是一个方法");
        }
        
        //@MyAnnotation     报错,没有指定构造器可以使用
        public Entity(){}
    }

@Retention 指定新注解的信息保留到什么时候,取值有RetentionPolicy.RUNTIME等

注:若不添加此注解,则在反射获取注解信息的时候会获取不到

1.不添加@Retention的情况:

-------------------不加@Retention的自定义注解-------------
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
public @interface MyAnnotation {  //自定义注解
}

-------------------加上自定义注解的类-------------------
@MyAnnotation
public class Entity {
}


--------------------测试-----------------------
public class Test06 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //获取字节码文件对象
        Class<?> aClass = Class.forName("com.hqq.Entity.Entity");
        
        //获取类的注解信息
        MyAnnotation annotation = aClass.getDeclaredAnnotation(MyAnnotation.class);
        System.out.println(annotation);
        
    }
}

不添加的结果:

 2.添加@Retention的情况:

@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {    //自定义注解
}

添加的结果:

@Inherited  指定新注解标注在父类上时可被子类继承


5.自定义注解实现API接口限流框架

自定义限流注解

demo场景:在SpringBoot项目中对我们接口实现 限流 比如 每秒只能访问1次 或者每秒访问两次

Maven配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>mayikt-rf-springboot</artifactId>
    <version>1.0-SNAPSHOT</version>


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
    </parent>
    <dependencies>
        <!--  springboot 整合web组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>18.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

    </dependencies>
</project>
  • 不用限流注解 - Controller
/**
 * @Author 苦恼的java小宝
 * @Date 2022/6/15 20:55
 * @ClassName: MemberService
 * @Version 1.0
 */

@RestController
public class MemberService {
    //每秒生成两个令牌
    private RateLimiter rateLimiter = RateLimiter.create(2.0);

    @GetMapping("/get")
    public String get(){
        boolean res = rateLimiter.tryAcquire();
        if(!res){
            return "当前人数访问过多";
        }
        return "success";
    }
}
  • 使用自定义限流注解(反射+Aop)

自定义注解定义两个参数,name表示要限流的方法名,token表示访问量

注:此时还没起作用,需要结合反射 + AOP做逻辑处理

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitAnnotation {
    //限流方法名称
    String name() default "";
    //默认token数
    double token() default 5;
}

Controller上添加上注解

@RestController
public class MemberService {

    @GetMapping("/get")
    @LimitAnnotation(name = "get", token = 1)
    public String get() {
        return "my is get";
    }

    @GetMapping("/add")
    @LimitAnnotation(name = "add", token = 10)
    public String add() {
        return "my is add";
    }

}

自定义切片类(Aop+反射) :

@Aspect
@Component
public class CurrentLimitAop {
    //因为每个方法每个方法的访问量限制各不同,所以用Map存多个RateLimiter
    private ConcurrentHashMap<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();

    //前置通知
    @Before(value = "@annotation(com.mayikt.service.annotation.LimitAnnotation)")
    public void before() {
        System.out.println("----------------前置通知----------------");
    }

    //后置通知
    @AfterReturning(value = "@annotation(com.mayikt.service.annotation.LimitAnnotation)")
    public void after() {
        System.out.println("----------------后置通知----------------");
    }

    //最核心的环绕通知  -- 能够拦截业务方法的运行
    @Around(value = "@annotation(com.mayikt.service.annotation.LimitAnnotation)")
    public Object around(ProceedingJoinPoint joinPoint) {
        try {
            System.out.println("环绕通知开始执行");

            //获取拦截的签名
            Signature sig = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) sig;

            //获取方法上添加的限流注解
            LimitAnnotation annotation = methodSignature.getMethod().getDeclaredAnnotation(LimitAnnotation.class);

            //若没有注解直接执行业务方法
            if (annotation == null){
                //执行 添加注解的业务方法
                joinPoint.proceed();
                //joinPoint.proceed()   也可以选择注释执行,这样不会执行业务方法,直接饭后下面的"环绕通知结束"
            }
             
    --------------------------------下面是反射-----------------------------
            //获取注解上的name
            String name = annotation.name();
            //获取注解上的token
            double token = annotation.token();

            //从Map中获取业务方法对应的限流rateLimiter
            RateLimiter rateLimiter = rateLimiters.get(name);
            
            //如果没有就创建并添加进Map    
            if (rateLimiter == null){
                rateLimiter = RateLimiter.create(token);
                rateLimiters.put(name,rateLimiter);
            }

            //开始限流
            boolean res = rateLimiter.tryAcquire();
            if(!res){
                return "访问人数过多";
            }
            Object obj = joinPoint.proceed();

            //执行结束后,执行后置通知
            System.out.println("环绕通知结束执行");

            //此时obj为业务方法返回的“my is get”
            return obj;

        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return "success";
    }
}

 通知的执行顺序:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值