Java反射机制

一、概述

        反射机制是Java语言的一项特性,就是把Java类中的各个组成部分映射成相应的Java类。允许程序在运行时动态地获取类的信息,如类名、方法、属性等,并且可以在运行时调用这些信息。Java中的反射机制提供了一个强大的工具,可以实现很多灵活的编程方式,例如在运行时动态处理对象、生成动态代理等。

二、一般应用

2.1 反射的基础-Class

        Java当中的类用来表示具有相同属性的方法和对象的结合,是抽象的概念。对象是类创造的,同一个类的不同对象具有不同的属性值。

Java当中定义的所有类都属于用一类事物,可以用Class来表示。

2.1.1 Class类的对象-字节码

Class类的对象就是不同的类对应的字节码。

获取Class对象的三种方法:

  1. 对象名.getClass()
  2. 类名.class
  3. Class.forName()方法(常用)
package reflect.Class;

public class Test1 {
    public static void main(String[] args) throws Exception{
        Class<String> aClass1= String.class;

        String s = "dwadwadw";
        Class<? extends String> aClass2 = s.getClass();

        Class<?> aClass3 = Class.forName("java.lang.String");

        System.out.println(aClass1.hashCode());
        System.out.println(aClass2.hashCode());
        System.out.println(aClass3.hashCode());
    }
}
1523554304
1523554304
1523554304

2.1.2 基本数据类型的Class对象

Java的基本数据类型都有各自的Class对象。

void也有自己对应的Class对象。

基本数据类型对应的封装类有属性TYPE,这个属性代表了封装类所封装的基本数据类型的Class对象。

package reflect;

public class Test2 {

    public static void main(String[] args) {
        System.out.println(int.class == Integer.TYPE);

        System.out.println(boolean.class == Boolean.TYPE);

        System.out.println(float.class == Float.TYPE);

        System.out.println(void.class.getName());
    }
}
true
true
true
void

2.2 通过反射创建对象

Object obj = Class.forName(“java.xxx.xxx”).newInstance();

package reflect.Class;

import java.nio.file.Files;
import java.nio.file.Paths;

public class Test3 {
    public static void main(String[] args) throws Exception{
        String className = Files.readAllLines(Paths.get("d:\\test\\21.txt")).get(0);

        Class aClass = Class.forName(className);

        Object o1 = aClass.newInstance();
        Object o2 = aClass.newInstance();
        Object o3 = aClass.newInstance();

        System.out.println(o1);
        System.out.println(o2);
        System.out.println(o3);
    }
}
reflect.Class.Order@4d405ef7
reflect.Class.Order@6193b845
reflect.Class.Order@2e817b38

通过这种方法只能调用该类public无参构造。

2.3 获取构造方法

Constructor类用来描述类中所定义的构造方法 。

创建几个工具类,方便功能实现:

package reflect.Class;

public class Example {
    public Example(){
        System.out.println("无参");
    }

    public Example(int a){
        System.out.printf("有参: a=%d\n", a);
    }

    public Example(int a, double b){
        System.out.printf("有参: a=%d,b=%f\n", a, b);
    }

    private Example(String s){
        System.out.printf("有参: s=%s\n",s);
    }

    protected Example(String s, int a){
        System.out.printf("有参: s=%d,a=%f",s,a);
    }
}
package reflect.Class;

import java.util.Date;

public class Book {

    public String bookName;
    public int bookId;
    private double date;

    public Book(String bookName, int bookId, double date) {
        this.bookName = bookName;
        this.bookId = bookId;
        this.date = date;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    public double getDate() {
        return date;
    }

    public void setDate(double date) {
        this.date = date;
    }

    private void privateMethod(){
        
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookName='" + bookName + '\'' +
                ", bookId=" + bookId +
                ", date=" + date +
                '}';
    }
}

2.3.1 public构造方法

  1. 得到类的所有构造方法

Constructor constructors[]=Class.forName(“java.lang.String”).getConstructors();

     2.得到类中某个具体的构造方法,在getConstructor中传入参数类型所对应的字节码

Constructor constructor=Example.class.getConstructor(int.class);

package reflect.Class;

import java.lang.reflect.Constructor;

public class Test4 {
    public static void main(String[] args) throws Exception{
        Class cls = Example.class;

        Example ex1 = (Example) cls.newInstance();
        System.out.println(ex1);

        System.out.println("所有构造方法");
        Constructor[] constructors = cls.getConstructors();
        for (Constructor construct : constructors) {
            System.out.println(construct);
        }

        Constructor constructor1 = cls.getConstructor();//获取无参构造方法
        Constructor constructor2 = cls.getConstructor(int.class);//获取参数类型为int的构造方法
        Constructor constructor3 = cls.getConstructor(int.class, double.class);//获取参数类型为(int, double)的构造方法
        Example ex2 = (Example)constructor3.newInstance(434, 3.131);
        System.out.println(ex2);
    }
}
无参
reflect.Class.Example@5acf9800
所有构造方法
public reflect.Class.Example(int,double)
public reflect.Class.Example(int)
public reflect.Class.Example()
有参: a=434,b=3.131000
reflect.Class.Example@2ff4acd0

2.3.2获取所有构造方法并调用(包括不公开的)

package reflect.Class;

import java.lang.reflect.Constructor;

public class Test5 {
    public static void main(String[] args) throws Exception{
        Class classs = Example.class;
        Constructor[] declaredConstructors = classs.getDeclaredConstructors();
        for (Constructor construct :
                declaredConstructors) {
            System.out.println(construct);
        }

        //获取一个私有方法
        Constructor constructor = classs.getDeclaredConstructor(String.class);
        constructor.setAccessible(true);//允许调用私有的构造方法
        Example ret = (Example)constructor.newInstance("鬼强");
        System.out.println(ret);
    }
}
protected reflect.Class.Example(java.lang.String,int)
private reflect.Class.Example(java.lang.String)
public reflect.Class.Example(int,double)
public reflect.Class.Example(int)
public reflect.Class.Example()
有参: s=鬼强
reflect.Class.Example@279f2327

2.4 获取接口、父类、该类所在package

package reflect.Class;

public class Test2 {
    public static void main(String[] args) {
        Class classs = String.class;
        printClassInfo(classs);
    }

    public static void printClassInfo(Class cls) {
        //类名
        System.out.println("类(接口)的名称:" + cls.getSimpleName());
        System.out.println("完全限定名:" + cls.getName());
        System.out.println("类(接口)的类型名称" + cls.getTypeName());

        //父类
        Class superCls= cls.getSuperclass();
        System.out.println("类的父类:" + superCls.toString());

        //实现的接口
        Class[] interfaceCls = cls.getInterfaces();
        System.out.println("当前类实现的接口");
        for (Class icls :
                interfaceCls) {
            System.out.println(icls);
        }

        //package包
        Package pck = cls.getPackage();
        if (pck != null){
            System.out.println("类所在的包的名称" + pck.getName());
        }

        //判断类型
        System.out.println("是否为接口:" + cls.isInterface());
        System.out.println(cls.isArray());
        System.out.println(cls.isEnum());
    }
}
类(接口)的名称:String
完全限定名:java.lang.String
类(接口)的类型名称java.lang.String
类的父类:class java.lang.Object
当前类实现的接口
interface java.io.Serializable
interface java.lang.Comparable
interface java.lang.CharSequence
类所在的包的名称java.lang
是否为接口:false
false
false

2.5 获取Field字段

Field类用来表示类中的属性(字段)。

  1. Class,getFields():得到Class对象的所有字段,返回的是Field数组。
  2. Class.getField(String name)返回一个Field对象,它反映此Class对象所表示的类或接口的指定公有成员字段。
  3. Field的对象所代表的某一个类的属性,而不是那个类的某一个对象的属性。要得到某个对象对应的属性值,需要通过get(Obiect obj)方法与某个对象具体关联。
  4. 对于非公有属性只能通过Class的getDeclaredField(String fieldName)方法得到。
  5. 对于私有属性要得到他所关联到的对象的值,需通过Field的setAccessible(Boolean boolean)方法设置。
  6. Field类的getType()方法用来得到字段所属的类型。
package reflect.Class;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class Test8 {
    public static void main(String[] args) {
        Class cls = Book.class;

        Field[] fields = cls.getDeclaredFields();
        for (Field field :
                fields) {
            System.out.println("成员变量修饰符" + field.getModifiers());
            System.out.println("成员变量访问修饰符:" + Modifier.toString(field.getModifiers()));
            System.out.println("成员变量名称:" + field.getName());
            System.out.println("成员变量类型:" + field.getType());
        }
    }
}
成员变量修饰符1
成员变量访问修饰符:public
成员变量名称:bookName
成员变量类型:class java.lang.String
成员变量修饰符1
成员变量访问修饰符:public
成员变量名称:bookId
成员变量类型:int
成员变量修饰符2
成员变量访问修饰符:private
成员变量名称:date
成员变量类型:double

2.6 获取Method字段

Method用来表示类中的方法。通过Class对象的如下方法得到Method对象

Method getMethod()

按名称得到某个特定的pubilc方法(包括从父类或接口继承的方法)

Method[] getMethods()

得到public方法(包括从父类或接口继承的方法)

Method[] getDeclaredMethods()

得到所有方法(不包括继承的方法)

Method getDeclaredMethod()

按名称得到某个特定的方法(不包括继承的方法)

2.6.1 获取public字段

public class Main {
 public static void main(String[] args) throws Exception {
 // String对象:
 String s = "Hello world";
 
 // 获取String substring(int)方法,参数为int:
 Method m = String.class.getMethod("substring", int.class);
 
 // 在s对象上调用该方法并获取结果:
 String r = (String) m.invoke(s, 6);
 
 // 打印调用结果:
 System.out.println(r);
 }
}

2.6.2 获取非public字段

package reflect.Class;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;

public class Test11 {
    public static void main(String[] args) {
        Class cls = Book.class;
        //获取所有定义的方法(当前类)
        Method[] declaredMethods = cls.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println("方法的访问修饰符:" + Modifier.toString(method.getModifiers()));
            System.out.println("方法的返回值类型:" + method.getReturnType());
            System.out.println("方法的名称:" + method.getName());

            Parameter[] parameters = method.getParameters();
            for (Parameter p : parameters) {
                System.out.println(p.getName());
                System.out.println(p.getType());
            }

        }

    }
}
方法的访问修饰符:public
方法的返回值类型:class java.lang.String
方法的名称:toString
方法的访问修饰符:public
方法的返回值类型:double
方法的名称:getDate
方法的访问修饰符:public
方法的返回值类型:class java.lang.String
方法的名称:getBookName
方法的访问修饰符:public
方法的返回值类型:void
方法的名称:setBookName
arg0
class java.lang.String
方法的访问修饰符:public
方法的返回值类型:int
方法的名称:getBookId
方法的访问修饰符:public
方法的返回值类型:void
方法的名称:setDate
arg0
double
方法的访问修饰符:private
方法的返回值类型:void
方法的名称:privateMethod
方法的访问修饰符:public
方法的返回值类型:void
方法的名称:setBookId
arg0
int

得到某个方法对应的Method对象后,需要调用如下方法来在某个对象上执行该方法:

  1. invoke()方法用来调用Method所表示的方法。
package reflect.Class;

import java.lang.reflect.Method;
import java.util.Random;

public class Test12 {
    public static void main(String[] args) throws Exception{

        Class cls = Base.class;
        Object o = cls.newInstance();

        Method method1 = cls.getMethod("create");

        Method method2 = cls.getMethod("create", int.class);

        int r1 = (int)method1.invoke(o);
        int r2 = (int)method2.invoke(o,1000);

        System.out.println(r1);
        System.out.println(r2);
    }
}

class Base {
    public int create(){
        Random random = new Random();
        int i = random.nextInt(10);
        return i;
    }

    public int create(int a){
        Random random = new Random();
        int i = random.nextInt(a);
        return i;
    }
}
7
589

2.6.3 多态

package reflect.Class;

import java.lang.reflect.Method;

public class Test14 {
    public static void main(String[] args) throws Exception {
        // 获取Person的hello方法:
        Method h = Person.class.getMethod("hello");

        // 对Student实例调用hello方法:
        h.invoke(new Student());
    }
}
class Person {
    public void hello() {
        System.out.println("Person:hello");
    }
}
class Student extends Person {
    public void hello() {
        System.out.println("Student:hello");
    }
}
Student:hello

三、代理模式

代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。

代理 模式是一种结构型设计模式。 代理模式角色分为 3 种:

Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,通常被设计成接

RealSubject(真实主题角色):真正实现业务逻辑的类;

Proxy(代理主题角色):用来代理和封装真实主题; 代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象 和代理对象,在代理模式中引入了抽象层。

        如果根据字节码的创建时机来分类,可以分为静态代理和动态代理: 

        所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色 的关系在运行前就确定了。

        动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行 前并不存在代理类的字节码文件

3.1 静态代理模式

//接口
public interface UserService {
    void select();
    void update();
}


//接口实现类(目标代理对象)
public class UserServiceImpl implements UserService {

    @Override
    public void select() {
        System.out.println("select * ..................");
        System.out.println("!查询执行");
    }

    @Override
    public void update() {
        System.out.println("update ...................");
        System.out.println("更新执行");
    }

}


//代理对象
public class UserServiceProxy implements UserService{
    private UserService target;
    public UserServiceProxy(){
        target = new UserServiceImpl();
    }

    @Override
    public void select(){
        before();
        target.select();
    }

    @Override
    public void update(){
        target.update();
        after();
    }

    private void before(){
        System.out.println("before-----------");
    }

    private void after(){
        System.out.println("after-----------");
    }
}
before-----------
select * ..................
!查询执行
update ...................
更新执行
after-----------

        通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一 个优点。虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。

1、当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式: 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类。

2、当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。

3.2动态代理模式

        JDK动态代理主要涉及两个类: java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler 。我们通过编写一个调用逻辑处理器 LogHandler 类案例来提供日志增强功能,并实现 InvocationHandler接口;在LogHandler中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke() 方法中编写方法调用的逻辑处理。


动态代理的处理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler {
 Object target; // 被代理的对象,实际的方法执行者
 public LogHandler(Object target) {
 this.target = target;
 }
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
 before();
 Object result = method.invoke(target, args); // 调用 target 的 method 方
法
 after();
 return result; // 返回方法的执行结果
 }
 // 调用invoke方法之前执行
 private void before() {
 System.out.println(String.format("log start time [%s] ", new Date()));
 }
 // 调用invoke方法之后执行
 private void after() {
 System.out.println(String.format("log end time [%s] ", new Date()));
 }
}

测试类:

import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
 public static void main(String[] args) throws IllegalAccessException,
InstantiationException {
 
 // 1. 创建被代理的对象,UserService接口的实现类
 UserServiceImpl userServiceImpl = new UserServiceImpl();
 
 // 2. 获取对应的 ClassLoader
 ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
 
 // 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口
UserService,
 Class[] interfaces = userServiceImpl.getClass().getInterfaces();
 
 // 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
 // 这里创建的是一个自定义的日志处理器,须传入实际的执行对象
userServiceImpl
 InvocationHandler logHandler = new LogHandler(userServiceImpl);
 /*
 5.根据上面提供的信息,创建代理对象 在这个过程中,
 a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节
码
 b.然后根据相应的字节码转换成对应的class,
 c.然后调用newInstance()创建代理实例
 */
 UserService proxy = (UserService) Proxy.newProxyInstance(classLoader,
interfaces, logHandler);
 
 // 调用代理的方法
 proxy.select();
 proxy.update();
 }
}

结果:

方法select开始执行
select * ..................
!查询执行
方法select结束执行
方法update开始执行
update ...................
更新执行
方法update结束执行

3.3 动态代理的常见用途

  1. 实现AOP(面向切面编程):在不改变原有代码的情况下,为程序中的某个方法添加额外的功能,如日志记录、性能监控、事务管理等。

  2. 实现RPC(远程过程调用):通过动态代理可以将网络通信、序列化等技术抽象出来,从而实现远程方法调用。

  3. 实现延迟加载:在访问某个对象的属性或方法时,可以通过动态代理在必要时才实例化该对象,从而节省内存和资源。

  4. 实现数据校验:在对某个对象进行操作时,可以通过动态代理对其进行校验,以确保数据的正确性和合法性。

  5. 实现权限控制:通过动态代理可以对某些方法进行访问控制,从而限制某些用户的访问权限。

四、总结

        反射是Java语言中一种强大的特性,它允许程序在运行时动态地获取和操作类、对象、方法以及字段等信息。以下是反射的一些总结:

  1. 反射提供了一种可以在运行时获取类的信息的机制,比如类的名称、修饰符、字段信息、方法信息、构造方法信息等。

  2. 反射可以动态地创建类的实例,可以通过Class类的newInstance()方法创建一个类的对象。

  3. 反射可以动态地获取和设置对象的属性值,可以通过反射获取对象的指定字段的值或设置它的值。

  4. 反射可以动态地调用对象的方法,可以通过反射获取对象的指定方法并调用它。

  5. 反射可以用于框架设计,可以通过反射动态地获取类和方法等信息,从而实现一些通用的功能,比如ORM框架等。

  6. 反射可能会对性能产生一定的影响,因为反射在运行时需要进行许多的额外操作,比如方法查找、字段查找等,所以在使用反射的时候应该尽可能地减少不必要的操作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值