反射机制&自定义注解

反射机制&自定义注解

  • 反射
    • 基础概括
    • 反射Api
      • Class 类
      • Constructor 构造函数
        • 执行无参构造函数
        • 执行有参构造函数
      • Field 字段
        • 公有属性赋值
        • 私有属性赋值
      • Method 方法
        • 调用公有无参方法
        • 调用私有无参方法
        • 调用有参方法
  • 注解
    • 注解详情
    • 获取注解信息
    • 应用-日志记录

反射

基础概括

定义

  • 在编码中我们对于已知需要用到的对象,我们通过new Object()方法去创建;当我们面对在未知某一天/某一个时刻,或者说在程序启动以后再去用到的对象,该如何创建呢?
  • Java反射机制是指在程序运行动时动态地加载类,创建对象,调用方法,设置和获取成员变量的值的能力;
  • 在Java中,反射机制是通过 java.lang.reflect包中的类和接口来实现;
  • 举例:Spring IOC创建bean实例,定时任务执行任务,数据库驱动选择,注解&Aop等。

反射机制主要包括

  • Class类:类的实体
  • Constructor类:类的构造方法
  • Method类:类的方法
  • Field类:类的字段属性

优缺点

  • 可以在运行时动态地获取类的信息、调用类的方法、访问类的属性,可以避免硬编码,使得程序更加灵活、可扩展性更高,同时也方便了框架的开发;
  • 在运行时通过反射获取相关信息,这比直接调用方法的效率要低,所以会带来一定的性能开销;也会破坏封装性,因为它可以访问类的私有属性和方法,可能还会导致安全问题;代码可读性较差,容易出现错误,需要开发者具备较高的技术水平。

反射Api

package com.demo.fs;

public class DemoEntity {

    public Integer id;
    private String name;

    public DemoEntity() {}

    public DemoEntity(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() { return id;}
    public void setId(Integer id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    private void sayHello(){ System.out.println("hello"); }
    public void sayHi() {System.out.println("hi");}
    private void say(String param) {System.out.println(param);}
    
    @Override
    public String toString() { return "id:" + id + ",name:" + name; }
}

Class 类

  1. new Object().getClass()
  2. .class
  3. Class.forName("类的全路径")
Class<DemoEntity> aClass = DemoEntity.class;
Class<? extends DemoEntity> cClass = new DemoEntity().getClass();
Class<?> bClass = Class.forName("com.demo.fs.DemoEntity");

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

Constructor 构造函数

执行无参构造函数

  • newInstance()
Class<DemoEntity> clazz = DemoEntity.class;
DemoEntity demoEntity = clazz.newInstance();
demoEntity.setId(1); 
demoEntity.setName("张三");
System.out.println(demoEntity);

执行有参构造函数

  • getConstructor()
Class<DemoEntity> clazz = DemoEntity.class;
Constructor<DemoEntity> constructor = clazz.getConstructor(Integer.class, String.class);
DemoEntity demoEntity = constructor.newInstance(2, "李四");
System.out.println(demoEntity);


getConstructor() 参数为构造函数的参数对应的数据类型,如若不匹配则会抛出 java.lang.NoSuchMethodException 异常
反射机制&自定义注解_第1张图片

Field 字段

  • getFields(): 获取当前class类全部公共字段
  • getField(String name):获取指定的公共字段
  • getDeclaredField():获取类型声明的所有的字段,包括私有、受保护、公共和默认访问权限的字段
  • getDeclaredField(String name):可以获取类中声明的指定字段,包括私有、受保护、公共和默认访问权限的字段

公有属性赋值

上述实体类中id为public权限,name为private权限

Field filed = Class.getField("属性名");
filed.set("实体类",Object属性值);
Class<DemoEntity> clazz = DemoEntity.class;
Field idField = clazz.getField("id");
DemoEntity demoEntity = clazz.newInstance(); 

idField.set(demoEntity, 3);
System.out.println(demoEntity);

私有属性赋值

获取私有属性字段需要使用getDeclaredField()方法

Class<DemoEntity> clazz = DemoEntity.class;
Field idField = clazz.getField("id");
Field nameField = clazz.getDeclaredField("name");
DemoEntity demoEntity = clazz.newInstance();

idField.set(demoEntity, 4);     
nameField.set(demoEntity,"小孙");
System.out.println(demoEntity);


上述代码执行中会抛出异常:java.lang.IllegalAccessException: Class com.demo.fs.AppRun can not access a member of class com.demo.fs.DemoEntity with modifiers "private" 是因为name是私有属性
获取私有属性的字段需要暴力破除,通过设置nameField.setAccessible(true)来设置允许访问私有属性
反射机制&自定义注解_第2张图片

Method 方法

  • getMethods(): 获取所有公有方法,包括父类(及Object)
  • getMethod(String name): 获取指定公有方法,包括父类(及Object)
  • getDeclaredMethods(): 获取类中声明的所有方法,包括私有、受保护、公共和默认访问权限的方法
  • getDeclaredMethod(String name): 获取类中指定方法,包括私有、受保护、公共和默认访问权限的方法
  • invoke():执行方法

调用公有无参方法

Class<DemoEntity> clazz = DemoEntity.class;
DemoEntity demoEntity = clazz.newInstance();
Method sayHi = clazz.getMethod("sayHi");
sayHi.invoke(demoEntity);

调用私有无参方法

此处调用也是一样,需要暴力破除 setAccessible(true);

Class<DemoEntity> clazz = DemoEntity.class;
DemoEntity demoEntity = clazz.newInstance();
Method sayHello = clazz.getDeclaredMethod("sayHello");
sayHello.setAccessible(true);
sayHello.invoke(demoEntity);

否则一样会出现此异常报错:


调用有参方法

在获取方法时,需指定方法对应的参数类型,当指定的参数类型缺少或数据类型不匹配则会抛出异常,当有参方法为私有时,也需要在获取方法对象后,调用setAccessible(true);

Class<DemoEntity> clazz = DemoEntity.class;
DemoEntity demoEntity = clazz.newInstance();
Method say = clazz.getDeclaredMethod("say", String.class);
say.setAccessible(true);
say.invoke(demoEntity, "hello world");


注解

Java注解(Annotation)是一种元数据(metadata),它可以用于在代码中添加注释和标记,使得程序可以通过反射机制获取到注解中的信息,从而达到动态修改程序行为的udit,提供了一种更加简洁,优雅,安全的编程方式。
注解可以适用于类,方法,字段,参数等多种程序元素上,比如常用注解@Override,@Service,@Bean

如何定义一个注解?

@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Log {
    // 注解属性
}

注解详情

**@Target **

@Target用于指定被注解的适用范围,即可以标记在那些元素上面

  • ElementType.TYPE:表示该注解可以用于类、接口和枚举上。
  • ElementType.FIELD:表示该注解可以用于成员变量上。
  • ElementType.METHOD:表示该注解可以用于方法上。
  • ElementType.PARAMETER:表示该注解可以用于参数上。
  • ElementType.CONSTRUCTOR:表示该注解可以用于构造函数上。
  • ElementType.LOCAL_VARIABLE:表示该注解可以用于局部变量上。
  • ElementType.ANNOTATION_TYPE:表示该注解可以用于注解上。
  • ElementType.PACKAGE:表示该注解可以用于包上。

**@Retention **

@Retention用于指定注解的生命周期,通常情况下,我自定义注解需要使用RetentionPolicy.RUNTIME,以便于运行时可以通过反射机制获取注解信息

  • RetentionPolicy.SOURCE:表示该注解只保留在源代码中,编译器会忽略该注解
  • RetentionPolicy.CLASS:表示注解会被编译器保留在字节码文件中,但不会被虚拟机加载
  • RetentionPolicy.RUNTIME:表示该注解会保留在字节码文件中,并且在运行时可以被虚拟机加载和获取

**@Inherited **

@Inherited表示一个注解类型可以被继承,如果一个类使用了被@Inherited的注解,则它的子类也会自动继承该注解

**@Documented **

@Documented 是一个标记注解(meta-annotation),它用于指示被它所注解的注解将被 javadoc 工具记录。当使用 javadoc 命令生成 API 文档时,这些被记录的注解信息将被包含在生成的文档中。这样,在阅读 API 文档时,开发者可以方便地了解这些注解的含义和用法。

例如,如果一个注解被标记了 @Documented,那么在使用 javadoc 命令生成 API 文档时,该注解的说明信息将会被包含在生成的文档中。如果没有使用 @Documented 标记,则该注解的说明信息将不会被包含在生成的文档中。

一般来说,如果你开发的注解可以被用于文档化 API,则应该使用 @Documented 注解。

获取注解信息

@Log(module = "demo",action = "test")
public class DemoEntity {

    public static void main(String[] args) {
        Class<DemoEntity> clazz = DemoEntity.class;
        Log annotation = clazz.getAnnotation(Log.class);
        if (annotation != null) {
            System.out.println("module:" + annotation.module() + ",action:" + annotation.action());
        }
    }


通过Class类方法获取注解

  • getAnnotation(Class annotationClass):返回指定类型的注解
Class<DemoEntity> clazz = DemoEntity.class;
Log annotation = clazz.getAnnotation(Log.class);
  • isAnnotationPresent(Class annotationClass):判断目标对象是否有指定类型的注解,返回是一个布尔值
if (DemoEntity.class.isAnnotationPresent(Log.class)) {
    Log annotation = DemoEntity.class.getAnnotation(Log.class);
    // 处理注解
}
  • getAnnotatedInterfaces():返回目标对象上所有注解类型为@Inherited的注解的接口类型,这个方法会遍历目标类的所有父类,并且如果注解的类型为@Inherited,也会返回这些父类的接口类型。
  • getAnnotations():该方法返回一个包含此元素上所有注解的数组,包括继承的注解
    Annotation[] annotations = DemoEntity.class.getAnnotations();
    for (Annotation annotation : annotations) {
        if (annotation instanceof Log) {
            // 处理注解
        }
    }
  • getDeclaredAnnotations():返回一个包含此元素上所有声明的注解的数组,不包括继承的注解和类上的注解。
    Annotation[] annotations = DemoEntity.class.getDeclaredAnnotations();
    for (Annotation annotation : annotations) {
        if (annotation instanceof Log) {
            // 处理注解
        }
    }

应用-日志记录

日志记录

注解&AOP实现日志信息记录

pom.xml

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.5.8version>
parent>
 <dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-aopartifactId>
    dependency>
dependencies>

自定义注解:

@Documented
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Log {
    /**
     * 当前模块
     * @return 模块
     */
    String module() default "" ;

    /**
     * 操作类型
     * @return add新增,modify编辑,delete删除
     */
    String action() default "" ;
}

aop切面

@Aspect
public class LogAop {

    @Pointcut("execution(* com.*.controller.*.*(..))")
    public void LogPointcut() {}

    @Around("LogPointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Log annotation = methodSignature.getMethod().getAnnotation(Log.class);
        // 日志记录
        if (annotation != null) {
            String module = annotation.module();
            String action = annotation.action();
            // TODO: 进行数据库存储

        }
        return point.proceed();
    }


}

你可能感兴趣的:(一,Java基础,java,spring,servlet)