反射、注解、元注解、动态代理

反射

反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等

学习反射就是学习如何获取类的信息并操作它们

  1. 加载类,获取类的字节码:Class对象
  2. 获取类的构造器:Constructor对象
  3. 获取类的成员变量:Field对象
  4. 获取类的成员方法:Method对象

1.获取类的三种方式

  1. Class c1 = 类名.class

  2. 调用Class提供方法:public static Class forName(String package)

  3. Object供的方法:public Class getClass(); Class c3 = 对象.getClass()

public class Test1Class {
    public static void main(String[] args) throws Exception {
        //1.使用class c1 = 类名.class方式加载类
        Class c1 = Student.class;
        String name = c1.getName();//获取全类名
        System.out.println(name);// com.lmh.reflect.Student
        String simpleName = c1.getSimpleName();//获取简类名,不包含包名
        System.out.println(simpleName);// Student

        //2.使用public static Class forName(String package)加载类
        //入参为类的全类名
        Class c2 = Class.forName("com.lmh.reflect.Student");

        //3.使用对象.getClass()方法加载类
        Student student = new Student();
        Class c3 = student.getClass();
        System.out.println(c1 == c2);//true
        System.out.println(c1 == c3);//true
        //因为Student类的字节码文件只有一份,所以该类在内存中也只有一个,故c1、c2、c3都是同一个对象
    }
}

2.获取类的构造器Constructor对象

       Class对象提供的从类中获取构造器的方法
方法 说明
Constructor[ ] getConstructors( ) 获取所有public修饰的构造器
Constructor[ ] getDeclaredConstructors( ) 获取所有构造器,无论什么关键字修饰
Constructor getConstructor(Class … parameterTypes) 入参为可变参数类型,根据参数类型获取public修饰的某个构造器
Constructor getDeclaredConstructor(Class … parameterTypes) 入参为可变参数类型,根据参数类型获取某个构造器,无论什么关键字修饰
public class Test2Constructor {
    @Test
    public void testGetConstructors() throws Exception {
        //1. 加载类
        Class c = Student.class;
        //2. 获取所有public修饰的构造方法
        Constructor[] cs1 = c.getConstructors();
        //3. 获取所有构造方法(推荐使用)
        Constructor[] cs2 = c.getDeclaredConstructors();
        //4. 遍历构造方法
        print(cs1);
        print(cs2);
        //5. 获取public修饰的指定的构造方法
        Constructor c1 = c.getConstructor(String.class, int.class, double.class);
        //6. 获取指定的构造方法
        Constructor c2 = c.getDeclaredConstructor(String.class);
        System.out.println(c1.getName() + "----->"
                + c1.getParameterCount());
        System.out.println("--------------------");
        System.out.println(c2.getName() + "----->"
                + c2.getParameterCount());
    }
    
    public void print(Constructor[] cs){
        for (Constructor constructor : cs) {
            //获取构造方法的名字和参数个数
            System.out.println(constructor.getName() + "----->"
                    + constructor.getParameterCount());
        }
        System.out.println("--------------------");
    }
}
Constructor对象的方法

获取类构造器的作用:初始化对象并返回

方法 说明
T newInstance( Object … initargs ) 调用此构造器对象表示的构造器,并传入参数,完成对象的初始化并返回
public void setAccessible( boolean flag ) 设置为true,表示禁止检查访问控制(暴力反射),private修饰的构造器也可以直接使用
public class Test2Constructor {
    @Test
    public void testConstructorMethod() throws Exception {
        Class c = Student.class;
        Constructor constructor = c.getDeclaredConstructor(String.class);
        /*
        无论是否为public修饰,均可以用以下方法创建对象,是通用模板
         */
        //当构造方法是private修饰时,需要先设置为ture,才能调用,否则会报错
        constructor.setAccessible(true);
        //调用构造方法
        Student s = (Student) constructor.newInstance("张三");
        System.out.println(s.getName());//张三
    }
}


3.获取类的成员变量

获取成员变量的方法
方法 说明
public Field[ ] getFields( ) 获取所有public修饰的成员变量
public Field[ ] getDeclaredFields( ) 获取所有成员变量,无论什么关键字修饰
public Field getField( String name ) 获取public修饰的指定成员变量,入参为变量名
public Field getDeclaredField( String name ) 获取任意关键字修饰的指定成员变量,入参为变量名
public class Test3Field {
    private Class c;
    private Constructor constructor;
    Student student;
    @Before
    public void init() throws Exception {
        c = Student.class;
        constructor = c.getDeclaredConstructor(String.class, int.class, double.class);
        constructor.setAccessible(true);
        student = (Student) constructor.newInstance("lmh", 18, 100);
    }
    @Test
    public void testGetFields() throws Exception {
        // 获取所有的public修饰的成员变量
        Field[] fs1 = c.getFields();
        print(fs1);
        // 获取所有的成员变量
        Field[] fs2 = c.getDeclaredFields();
        print(fs2);
        // 获取指定的public修饰的成员变量
        Field address = c.getField("address");
        System.out.println(address.getName() + "---->"
                + address.getType());
        System.out.println("===================================");
        // 获取指定的成员变量
        Field age = c.getDeclaredField("age");
        System.out.println(age.getName() + "---->"
                + age.getType());
    }
    public void print(Field[] fs) {
        for (Field f : fs) {
            System.out.println(f.getName() + "---->"
                    + f.getType());
        }
        System.out.println("===================================");
    }
}
Field对象的方法

获取成员变量的作用:对成员变量进行赋值与取值

方法    说明
void set( object obj, object value ) 赋值,第一个参数为实体类对象,第二个参数为要赋的值
public void setAccessible( boolean flag ) 设置为true,表示禁止检查访问控制(暴力反射),private修饰的成员变量也可以直接操作
object get( object obj ) 取值,入参为实体类对象
public class Test3Field {
    private Class c;
    private Constructor constructor;
    Student student;
    @Before
    public void init() throws Exception {
        c = Student.class;
        constructor = c.getDeclaredConstructor(String.class, int.class, double.class);
        constructor.setAccessible(true);
        student = (Student) constructor.newInstance("lmh", 18, 100);
    }

    @Test
    public void testFieldMethod() throws Exception{
        Field Fname = c.getDeclaredField("name");
        //设置访问权限
        Fname.setAccessible(true);
        //获取成员变量的值
        String name = (String) Fname.get(student);
        System.out.println(name);//lmh
        //设置成员变量的值
        Fname.set(student, "dadada");
        System.out.println(student.getName());//dadada
    }
}

4.获取类的成员方法

Class对象提供的从类中获取成员方法的方法
方法 说明
Method[ ] getMethods( ) 获取所有public修饰的成员方法
Method[ ] getDeclaredMethods( ) 获取所有成员方法,无论什么关键字修饰
Method getMethod(String name, class … parameterTypes) 入参为可变参数类型,根据方法名和参数类型获取public修饰的某个成员方法
Method getDeclaredMethod(String name, class … parameterTypes) 入参为可变参数类型,根据方法名和参数类型获取某个成员方法,无论什么关键字修饰
public class Test4Method {
    private Class c;
    private Constructor constructor;
    Student student;
    @Before
    public void init() throws Exception {
        c = Student.class;
        constructor = c.getDeclaredConstructor(String.class, int.class, double.class);
        constructor.setAccessible(true);
        student = (Student) constructor.newInstance("lmh", 18, 100);
    }

    @Test
    public void testGetMethod() throws Exception {
        // 获取所有的public修饰的方法
        Method[] methods1 = c.getMethods();
        print(methods1);
        // 获取所有的方法
        Method[] methods2 = c.getDeclaredMethods();
        print(methods2);
        // 获取public修饰的指定的方法
        Method m1 = c.getMethod("show");
        System.out.println(m1.getName() + "---->"
                + m1.getReturnType() + "---->"
                + m1.getParameterCount());
        System.out.println("===================================");
        // 获取指定的方法
        Method m2 = c.getDeclaredMethod("privateMethod", String.class);
        System.out.println(m2.getName() + "---->"
                + m2.getReturnType() + "---->"
                + m2.getParameterCount());
    }
    public void print(Method[] ms) {
        for (Method m : ms) {
            System.out.println(m.getName() + "---->"
                    + m.getReturnType() + "---->"
                    + m.getParameterCount());
        }
        System.out.println("===================================");
    }
}

Method对象的方法

获取成员变量的作用:运行方法

方法 说明
public object invoke( object obj, object … args ) 运行方法,第一个参数为实体类对象,第二个参数为可变参数,为传入的成员方法的参数
public void setAccessible( boolean flag ) 设置为true,表示禁止检查访问控制(暴力反射),private修饰的成员方法也可以直接访问
public class Test4Method {
    private Class c;
    private Constructor constructor;
    Student student;
    @Before
    public void init() throws Exception {
        c = Student.class;
        constructor = c.getDeclaredConstructor(String.class, int.class, double.class);
        constructor.setAccessible(true);
        student = (Student) constructor.newInstance("lmh", 18, 100);
    }
    
    @Test
    public void testMethod() throws Exception{
        Method m = c.getDeclaredMethod("privateMethod", String.class);
        m.setAccessible(true);
        m.invoke(student, "ddd");
        //我是一个重载的私有方法,名字是:ddd
    }
}

5.反射的作用

基本作用:可以解析一个类的全部成分并进行操作

可以通过设置访问权限破坏对象的封装性

最主要用途:适合做Java的框架,基本上主流的框架都会基于反射设计出一些通用的功能

注解

注解概述

  • 就是Java代码里的特殊标记,比如:@Override、@Test等,作用是让其他程序根据注解信息来决定怎么执行该程序
  • 注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上等位置

格式

反射、注解、元注解、动态代理_第1张图片

//定义自定义注解
public @interface MyTest1 {
    String name();
    int age() default 18;//默认值为18
    String[] hobby();
}

/*
使用自定义注解的简单实例
 */
@MyTest1(name = "lmh", age = 20, hobby = {"basketball", "football"})
public class AnnotationTest1 {
    /*
    因为age有默认值,所以可以不用赋值,默认为18
     */
    @MyTest1(name = "ddd", hobby = {"basl", "fl"})
    public void test() {
        System.out.println("test");
    }
}
特殊属性名:value

当自定义注解只有value这一个属性或者有其他属性但是都有默认值时,在使用注解时无需写value,直接传值即可

public @interface MyTest2 {
    String value();
    int age() default 18;//默认值为18
}
//只有value或者只有一个属性时,可以省略属性名
//如果其他属性都有默认值,只有value没有默认值,那么只需要给value赋值,value不必写出来

@MyTest2("lmh")//默认给value赋值
public class AnnotationTest2 {
    
}

反射、注解、元注解、动态代理_第2张图片

元注解

用于修饰注解的注解

反射、注解、元注解、动态代理_第3张图片

//用于声明注解可以使用的位置,下面代码表示该注解只能用于类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
//用于表示注解的保留周期,下面代码表示该注解会一直保留到运行阶段,可以通过反射机制读取到
@Retention(RetentionPolicy.RUNTIME)//开发中一般都是RUNTIME
public @interface MyTest3 {
}
@MyTest3()
public class AnnotationTest2 {
    @MyTest3()//会报错,因为声明的范围是TYPE和METHOD,而FIELD不在范围内
    private String s;
}

注解的解析

判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来

  • 如果要解析类上面的注解,就要先通过反射拿到该类对应的Class对象,再通过Class对象解析注解
  • 如果要解析方法上面的注解,就要先通过反射拿到该方法对应的Method对象,再通过Method对象解析注解
  • Class、Method、Field、Constructor都实现了AnnotatedElement接口,所以都拥有解析注解的能力
AnnotatedElement接口提供的方法 说明
public Annotation[ ] getDeclaredAnnotations( ) 获取当前对象上面的注解对象的集合
public T getDeclaredAnnotation(Class annotationClass) 获取指定的注解对象,入参为注解的类对象
public boolean isAnnotationPresent( Class annotationclass ) 判断当前对象上是否存在某个注解,入参为注解的类对象
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
    String value();
    double aaa() default 100;
    String[] bbb();
}
@MyTest4(value = "abc", bbb = {"aaa", "bbb"})
public class Demo {
    @MyTest4(value = "dadada", aaa = 1, bbb = {"a", "b"})
    public void test1(){

    }
}
public class AnnotationTest3 {
    @Test
    public void parseClass(){
        //获取类对象
        Class c = Demo.class;
        //解析类上的注解
        if (c.isAnnotationPresent(MyTest4.class)){
            MyTest4 myTest4 =
                    (MyTest4) c.getDeclaredAnnotation(MyTest4.class);
            System.out.println(myTest4.value());
            System.out.println(myTest4.aaa());
            System.out.println(Arrays.toString(myTest4.bbb()));
            System.out.println("====================================");
        }
    }

    @Test
    public void parseMethod() throws Exception {
        Class c = Demo.class;
        Method m = c.getDeclaredMethod("test1");
        Annotation[] annotations = m.getDeclaredAnnotations();
        if (annotations.length > 0){
            for (Annotation annotation : annotations) {
                //instanceof判断左边的对象是否是右边类的实例
                if (annotation instanceof MyTest4){
                    MyTest4 myTest4 = (MyTest4) annotation;
                    System.out.println(myTest4.value());
                    System.out.println(myTest4.aaa());
                    System.out.println(Arrays.toString(myTest4.bbb()));
                }
            }
        }
    }
}

动态代理

实现动态代理的前提是代理的对象和被代理的对象必须实现同一个接口

动态代理对应的类:java.lang.reflect.Proxy,提供了为对象产生代理对象的方法

反射、注解、元注解、动态代理_第4张图片

小案例:

//接口,必须让代理类和被代理类实现同一个接口
public interface Star {
    String sing(String SongName);
    void dance();
}
//被代理类,实现接口
public class SuperStar implements Star{
    private String name;//明星的名字

    public SuperStar(String name) {
        this.name = name;
    }

    @Override
    public String sing(String SongName) {
        System.out.println(name + "唱了一首歌:" + SongName);
        return "歌唱完了,谢谢大家!";
    }

    @Override
    public void dance() {
        System.out.println(name + "跳了一支舞!");
    }
}
//代理类
public class ProxyUtil {
    public static Star createProxy(SuperStar superStar){
        //第一个参数固定,第二个参数用于绑定被代理类实现的接口,
        // 第三个参数是一个匿名内部类,用于实现代理对象需要实现的功能
        Star starProxy =
                (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{Star.class}, new InvocationHandler() {
                    @Override
                    //第一个参数是代理对象,第二个参数是被代理对象的方法对象,第三个参数是被代理对象的方法的入参
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //代理需要完成的功能
                        if (method.getName().equals("sing")) {
                            System.out.println("准备话筒,收钱20万");
                        } else if (method.getName().equals("dance")) {
                            System.out.println("准备场地,收钱1000万");
                        }
                        //指定被代理对象的方法并执行
                        //如果有返回值,需要将返回值返回给代理对象
                        return method.invoke(superStar, args);
                    }
                });
        //返回代理对象
        return starProxy;
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        SuperStar s = new SuperStar("吴晗");
        Star proxy = ProxyUtil.createProxy(s);

        System.out.println(proxy.sing("蓝莲花"));
        proxy.dance();
    }
}

你可能感兴趣的:(java,开发语言)