Java反射自定义注解底层设计原理

一、反射机制

1.1、什么是反射

通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。

(1)Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。

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

使用反射机制可以动态获取当前class的信息 比如方法的信息、注解信息、方法的参数、属性等;

1.2、反射机制的优缺点

第三方框架—创建对象 不是直接new 反射机制创建

目的:提供开发者能够更好封装框架实现扩展功能。

在一个类中 定义了一个私有属性/方法 反射机制破解私有属性

优点:

  • 在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

缺点:

  • 反射会消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射;
  • 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

1.3、反射的用途

反编译:.class–>.java
1.通过反射机制访问java对象的属性,方法,构造方法等
2.JDBC加载驱动连接 class.forname

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

3.Spring容器框架IOC实例化对象

<bean id="mayikt" class="com.mayikt.UserEntity" />

4.自定义注解生效(反射+Aop)
5.第三方核心的框架 mybatis orm

1.4、反射技术的使用

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

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

Method类 代表类的方法

Constructor类 代表类的构造方法

  • getField、getMethod和getCostructor方法可以获得指定名字的域、方法和构造器。
  • getFields、getMethods和getCostructors方法可以获得类提供的public域、方法和构造器数组,其中包括超类的共有成员。
  • getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。

1.5、反射常用的API

(1)Object–>getClass
(2)任何数据类型(包括基本的数据类型)都有一个“静态”的class属性
(3)通过class类的静态方法:forName(String className)(最常用)

Class<?> aClass = Class.forName("com.java521.domain.User");

User

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User {
    
    

    private String username;

    private Integer Integer;
}

Test01

public class Test01 {
    
    
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
    
    
        //1.第一种获取class方式
        User user = new User();
        Class userClass1 = user.getClass();
        // 默认执行无参构造函数
        User user2 = (User) userClass1.newInstance();

        // 2.第二种方式 直接获取class
        Class userClass2 = User.class;
        User user3 = (User) userClass2.newInstance();
        System.out.println("user3 = " + user3);

        // 3.第三种方式 类的完整路径地址
        Class userClass3 = Class.forName("com.java521.domain.User");
        User user4 = (User) userClass3.newInstance();
        System.out.println("user4 = " + user4);
    }
}

1.6、运行期间,一个类,只有一个Class对象产生

public class Test02 {
    
    
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
    
    
        //1.第一种获取class方式
        User user = new User();
        Class userClass1 = user.getClass();

        // 2.第二种方式 直接获取class
        Class userClass2 = User.class;

        // 3.第三种方式 类的完整路径地址
        Class userClass3 = Class.forName("com.java521.domain.User");

        System.out.println(userClass3);
        System.out.println(userClass1 == userClass2);
        System.out.println(userClass1 == userClass3);
    }
}

1.7、反射执行构造函数

1.7.1、执行无参数构造函数

Class userClass = Class.forName("com.java521.domain.User");
User user = (User) userClass.newInstance();
System.out.println(user);

1.7.2、执行有参数构造函数

Class userClass = Class.forName("com.java521.domain.User");
Constructor constructor = userClass.getConstructor(String.class, Integer.class);
User user = (User) constructor.newInstance("java521", 25);
System.out.println(user);

1.8、反射执行给属性赋值

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User {
    
    

    private String username;

    private Integer Integer;
    
    //公有
    public Integer sex;
}

1.8.1、反射执行给公有属性赋值

//给公有属性赋值
Class userClass = Class.forName("com.java521.domain.User");
Field sex = userClass.getField("sex");
User user = (User) userClass.newInstance();
sex.set(user, 1);
System.out.println(user);

1.8.2、反射执行给私有属性赋值

//给私有属性赋值
Class userClass = Class.forName("com.java521.domain.User");
User user = (User) userClass.newInstance();
Field username = userClass.getDeclaredField("username");
// 设置允许访问私有属性
username.setAccessible(true);
username.set(user, "xiaoming");
System.out.println(user);

注意

Exception in thread "main" java.lang.IllegalAccessException: Class com.java521.controller.Test06 can not access a member of class com.java521.domain.User with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Field.set(Field.java:761)
	at com.java521.controller.Test06.main(Test06.java:22)

// 解决办法: 
// 设置允许访问私有属性
username.setAccessible(true);

1.9、反射执行调用方法

User

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User {
    
    

    private String username;

    private Integer Integer;

    public Integer sex;


    public void publicFucntion() {
    
    
        System.out.println("我是公有方法");
    }

    private void privateFucntion() {
    
    
        System.out.println("我是私有方法");
    }
    
    private Integer sum(Integer val1, Integer val2) {
    
    
        return val1 + val2;
    }
}

1.9.1、反射调用公有方法

//反射调用公有方法
User user = (User) userClass.newInstance();
Method publicFucntion = userClass.getDeclaredMethod("publicFucntion");
publicFucntion.invoke(user);

1.9.2、反射调用私有方法

//反射调用私有方法
Class userClass = Class.forName("com.java521.domain.User");
User user = (User) userClass.newInstance();
Method privateFucntion = userClass.getDeclaredMethod("privateFucntion");
privateFucntion.setAccessible(true);
privateFucntion.invoke(user);

1.9.3、反射调用方法传递参数

//反射调用方法传递参数
Class userClass = Class.forName("com.java521.domain.User");
User user = (User) userClass.newInstance();
Method sum = userClass.getDeclaredMethod("sum", Integer.class, Integer.class);
sum.setAccessible(true);
Integer result = (Integer) sum.invoke(user, 3, 4);
System.out.println(result);

1.9.4、通过反射越过泛型检查

泛型用在编译期,编译过后泛型擦除(消失掉),所以是可以通过反射越过泛型检查的

public class Test10 {
    
    
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    
    
        List<String> strings = new ArrayList<String>();
        strings.add("java");

        Class<? extends List> aClass = strings.getClass();
        Method add = aClass.getDeclaredMethod("add", Object.class);
        Object invoke = add.invoke(strings, 1);
        System.out.println(strings);
    }
}

二、注解

2.1、注解概念

2.1.1、什么是注解

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

SpringBoot 全部都是采用注解化

2.1.2、常用注解

@Override 只能标注在子类覆盖父类的方法上面,有提示的作用

@Deprecated 标注在过时的方法或类上面,有提示的作用

@SuppressWarnings(“unchecked”) 标注在编译器认为有问题的类、方法等上面,用来取消编译器的警告提示,警告类型有serial、unchecked、unused、all

2.1.3、元注解

元注解用来在声明新注解时指定新注解的一些特性

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

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

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

21.4、常用注解

@Target(ElementType.METHOD) // 指定新注解可以标注在方法上

@Retention(RetentionPolicy.RUNTIME) // 指定新注解保留到程序运行时期

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

public @interface MayiktName {
    
    
    public String name();
}

自定义注解 运行 :反射+aop

2.1.5、注解的Target

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

2.2、获取注解信息

Java521

@Target({
    
    ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Java521 {
    
    
}

User

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@Java521
public class User {
    
    

    private String username;

    private Integer Integer;

    public Integer sex;


    public void publicFucntion() {
    
    
        System.out.println("我是公有方法");
    }

    private void privateFucntion() {
    
    
        System.out.println("我是私有方法");
    }

    private Integer sum(Integer val1, Integer val2) {
    
    
        return val1 + val2;
    }
    private void add(Integer val) {
    
    
        System.out.println("val = " + val);
    }

    @Java521
    public void loveJava() {
    
    
        System.out.println("my love java");
    }
}

Test01

public class Test01 {
    
    
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
    
    
        // 1.获取当前类上的注解
        Class<?> userClass = Class.forName("com.java521.domain.User");
        Annotation annotation1 = userClass.getDeclaredAnnotation(Java521.class);
        System.out.println("annotation1 = " + annotation1);

        // 2.获取方法上的注解
        User user = (User) userClass.newInstance();
        Method loveJava = userClass.getDeclaredMethod("loveJava");
        // 执行方法
        loveJava.invoke(user);
        Annotation annotation2 = loveJava.getDeclaredAnnotation(Java521.class);
        System.out.println("annotation2 = " + annotation2);

        // 3.获取属性上的注解 没有注解 为null
        Field sex = userClass.getDeclaredField("sex");
        Annotation annotation3 = sex.getDeclaredAnnotation(Java521.class);
        System.out.println("annotation3 = " + annotation3);

        // 4.获得构造方法注解 没有注解 为null
        //先获得构造方法对象
        Constructor<?> constructor = userClass.getConstructor(new Class[]{
    
    });
        Annotation annotation4 = constructor.getAnnotation(Java521.class);
        System.out.println("annotation4 = " + annotation4);
    }
}

2.3、注解如何生效

实际项目中 注解想生效通过反射+aop机制

2.4、自定义限流注解实现案例

maven

<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>

<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>

使用自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JavaCurrentLimit {
    
    
    /**
     * name 限流的名称
     *
     * @return
     */
    String name() default "";

    /**
     * 每秒能够允许访问的次数 20 底层
     * 令牌桶
     *
     * @return
     */
    double token() default 20;
}

整合Aop实现接口限流

@Aspect
@Component
public class CurrentLimitAop {
    
    
    /**
     * 每秒生成1.0个令牌 每s产生10 token
     */
//    private RateLimiter rateLimiter = RateLimiter.create(1);
    private ConcurrentHashMap<String, RateLimiter> rateLimiters = new ConcurrentHashMap();

    @Around(value = "@annotation(com.java521.example.JavaCurrentLimit)")
    public Object around(ProceedingJoinPoint joinPoint) {
    
    
        try {
    
    
            //获取拦截的方法名
            Signature sig = joinPoint.getSignature();
            //获取拦截的方法名
            MethodSignature methodSignature = (MethodSignature) sig;
            // 判断方法上是否有加上该注解,如果有加上注解则限流
            JavaCurrentLimit javaCurrentLimit =
                    methodSignature.getMethod().getDeclaredAnnotation(JavaCurrentLimit.class);
            if (javaCurrentLimit == null) {
    
    
                // 执行目标方法
                return joinPoint.proceed();
            }
            // 获取注解上的name
            String name = javaCurrentLimit.name();
            // 获取注解上的token
            double token = javaCurrentLimit.token();
            RateLimiter rateLimiter = rateLimiters.get(name);
            if (rateLimiter == null) {
    
    
                rateLimiter = RateLimiter.create(token);
                rateLimiters.put(name, rateLimiter);
            }
            // 开始限流
            boolean result = rateLimiter.tryAcquire();
            if (!result) {
    
    
                return "当前访问人数过多,请稍后重试!";
            }
            return joinPoint.proceed();
        } catch (Throwable throwable) {
    
    
            return "系统出现了错误!";
        }
    }
}

使用效果

@RestController
public class MemberController {
    
    
    /**
     * 每秒生成1.0个令牌
     */
    @GetMapping("/get")
    @JavaCurrentLimit(name = "get", token = 1)
    public String get() {
    
    
//        boolean result = rateLimiter.tryAcquire();
//        if (!result) {
    
    
//            return "当前访问人数过多,请稍后重试!";
//        }
        return "my is get";
    }

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

    @GetMapping("/my")
    public String my() {
    
    
        return "my is add";
    }
}

http://127.0.0.1:8080/get
http://127.0.0.1:8080/my

猜你喜欢

转载自blog.csdn.net/qq_37242720/article/details/118521508