Java反射

关于反射的理解

谈及反射之前我想说说对象在Java中是怎样存在的以及存放在哪里。想必很多初学Java的小伙伴会接触到对象这个概念,Java是面向对象的语言,Java程序就是通过一个又一个对象构建起来的。我们可以通过Object o = new Object()直接生成对象,那么这个o对象是存放在哪里的呢?
如果了解过JMM的同学肯定会很清楚,对象是存放在Java堆上的,我们可以通过直接new Object()的方式在堆上生成一个对象,这种方式生成的对象是在编译时就确定了的,那么有没有可以让我们在Java程序运行时生成对象的方法?没错,通过反射我们就可以在运行期动态的生成一个对象了
反射带给我们的程序很多便利,譬如运行期间检查类、接口、变量或者方法等信息。我能通过反射做什么呢?对一个运行期对象的值进行改变,也可以拿到类方法操作对象。

反射获取到的信息

我们可以通过如下三种方式获取一个类的Class对象:
1. Class pigClass = Pig.class;
2. Class pigClass = new Pig().getClass();
3. Class pigClass = Class.forName(“Pig”);

注意第三种获取Class对象的方式传入的字符串需要完整的包名以及类名

构造器

通过类的构造器,我们可以生成一个对象。在java.lang.reflect包下有一个Constructor类,这个类生成的实例可以存放关于构造器的一些信息。
通过上面pigClass对象获取的Constructor对象存放构造器的一些信息如下所示:

    public String pigConstructorTest() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        Class<Pig> pigClass = Pig.class;
        //get constructor from pigClass
        Constructor<Pig> pigConstructor = pigClass.getDeclaredConstructor();
        //access private-constructor 
        pigConstructor.setAccessible(true);
        //new-instance through constructor
        Pig pig = pigConstructor.newInstance();
        return pig.getAge() + " " + pig.getWeight() + " " + pigConstructor.getName();
    }

这里调用的是无参构造器,所以生成的对象初始值为默认值。Constructor还有一些譬如获取构造器修饰符、构造器参数类型、构造器参数个数等方法。

字段

    public void pigFiledTest() throws NoSuchFieldException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        //get class's all fields
        Field[] fields = pigClass.getDeclaredFields();
        //get one field through certain string
        Field weight = pigClass.getDeclaredField("weight");
        weight.setAccessible(true);
        for (Field f :
                fields) {
            System.out.print(f.getType() + " " + f.getName());
            System.out.println();
        }
        Pig pig = pigConstructorTest();
        System.out.print(weight.get(pig) + " after filed setting");
        weight.set(pig,1);
        System.out.print(" "+ weight.get(pig));
    }

Pig类的字段是私有的,即只有Pig对应的对象能访问,所以通过普通的pigClass.getFields()并不能获取其field数组,通过getDeclaredFields()方法即可获取其私有字段。getDeclaredFields()与getDeclaredField()的区别是一个获取所有字段,一个根据传入的字段名获取相应的field对象。Field字段对应的实例还可以在运行期间给改变对象的私有属性,上面的代码运行后结果如下:

int age
int weight
0 after filed setting 1

方法

    public void pigMethodTest() {
        //same to field,the getMethods() can not obtain private-method
        Method[] methods =
                pigClass.getMethods();
        for (Method method : methods) {
            System.out.print(method.getName() + " " + Arrays.toString(method.getParameters()));
            System.out.println();
        }
    }

同样的,要想获取类的私有方法只有通过getDeclaredMethods()。下面来看看method的一个比较重要的方法:

    public void pigMethodInvoke() throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Method setAge = pigClass.getMethod("setAge", int.class);
        Pig pig = pigConstructorTest();
        System.out.println("before method invoke:" + pig.getAge());
        setAge.invoke(pig,10);
        System.out.println("after method invoke:" + pig.getAge());
    }

对应的输出为:

before method invoke:0
after method invoke:10

可以看到,通过setAge.invoke(pig,10);我们成功地将猪的年龄设置为0。invoke方法的使用需要传入两个参数:
1. Object obj,需要对方法进行调用的对象,如果该对象中没有该方法那么会抛出一个IllegalArgumentException:object is not an instance of declaring class
2. Object… args,方法需要传递的实际参数。

其他

反射能够带给我们,如果一个类、方法、字段上面打上了注解,那么我们也可以通过反射拿到对应的注解内容。同上,若是一个类、方法、字段上面有泛型数据,反射能帮我们拿到已经处于运行期时候的泛型信息吗?答案是肯定的,毕竟反射这个利器能够带给我们的好处不是一星半点。反射的功能太多了这里我就不一一演示了~~~

反射的应用

基于JDK实现的动态代理:

public interface BookFacade {
    void addBook();
}

所有基于JDK实现的动态代理都需要一个接口,其次是具体的委托类:

public class BookFacadeImpl implements BookFacade {
    @Override
    public void addBook() {
        System.out.println("增加图书方法。。。");
    }
}

这个addBook()方法就是我们需要委托出去的方法,我们怎么将这个方法委托出去呢?需要用到一个代理类进行:

public class BookFacadeProxy implements InvocationHandler {
    /**
     * 不确定委托类是谁,被代理对象
     */
    private Object target;

    public BookFacadeProxy(Object target){
        this.target = target;
    }

    public BookFacadeProxy(){}

    /**
     * 绑定一个委托类,并返回代理类的实例
     * @param target
     * @return
     */
    public Object createProxy(Object target) {
        this.target = target;
        //需要target实现了接口
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces()
                , this);
    }
    /**
     *
     * @param proxy 代理对象
     * @param method 委托类实例的方法
     * @param args 委托类方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before proxy");
        //target是要调用method方法的对象,args是传入的参数
        //这里的method是BookFacade的addBook()方法
        //result返回invoke执行结果,可能为null
        Object result = method.invoke(target,args);
        System.out.println("after proxy");
        //从委托类方法调用返回的值
        return result;
    }
}

通过实现InvocationHandler类并重写其invoke方法来实现对委托类委托的方法增强。具体的看看invoke方法,
1. 方法参数Object proxy,这个参数一般是用不到的;Method method是委托类具体想要执行的方法,Object[] args是方法传入的实参。
2. method.invoke(target,args);这里的target就是传入的委托类实际对象。

对于这个类的createProxy()方法,其实没有必要非得在代理类里面实现,里面的Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces()
, this)
主要用于生成具体的代理对象,this传递的是BookFacadeProxy代理类本身的实例,不过想要调用委托类的委托方法,需要将返回的对象强转为接口类型:

public class BookFacadeTest {
    public static void main(String[] args) {
        BookFacadeProxy proxy = new BookFacadeProxy();
        BookFacade bookFacade = (BookFacade) proxy.createProxy(new BookFacadeImpl());
        bookFacade.addBook();

        BookFacadeImpl target = new BookFacadeImpl();
        BookFacade newProxyInstance = (BookFacade) Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), new BookFacadeProxy(target));
        newProxyInstance.addBook();
    }
}

可以看到只有将返回的代理类实例强转为接口类型,我们才能调用委托出去的方法。
有了动态代理,我们能够对一个实现了接口的类的方法的使用做一些增强,在方法的运行的前后做一些处理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值