反射与代理举例

  java中反射跟代理是有点关系的,反射的基础,代理需要借助反射来实现动态代理。反射本质就运行时加载类编译后的class文件,然后根据java.lang.Class类对象所提供的API进行操作,包括获取该类的包名、所实现的接口名、所继承的父类名,以及该类自己的类名、方法名、字段名、构造函数名,真正有用的是利用java.lang.reflect提供的API直接调用方法,修改字段值,用构造函数实例化对象,这就是反射为啥能动态的秘密。具体参见下面代码:

package com.wulf.io;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class Reflection {

    public static void main(String[] args) throws Exception {
        Class clz = Member.class;
        printClassInfo(Member.class);

        // 通过构造函数实例化对象
        Constructor c = clz.getConstructor(String.class, String.class, int.class, String.class);
        Member m = (Member) c.newInstance("9527", "liangchaowei", 53, "18912346987");
        System.out.println(m.toString());

        // 构造方法名调用java.lang.reflect.Method的invoke方法重置字段值
        Map<String, Object> hashMap = new HashMap<>();
        hashMap.put("number", "0001");
        hashMap.put("name", "wulf");
        hashMap.put("age", 33);
        hashMap.put("phone", "13812345678");
        for (Entry<String, Object> entry : hashMap.entrySet()) {
            String key = entry.getKey();
            Method method = clz.getMethod("set" + key.substring(0, 1).toUpperCase() + key.substring(1),
                    entry.getValue().getClass());
            if (Modifier.isPublic(method.getModifiers())) {
                method.invoke(m, entry.getValue());
            }
        }
        System.out.println(m.toString());

        // 私有字段需要通过设置Accessible为true才能重新设值
        Field name = clz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(m, "zhangtianyou");

        System.out.println(m.toString());
    }

    private static void printClassInfo(Class clz) {
        System.out.printf("类名:%s,是否接口:%b,是否基本类型:%b,是否数组:%b,父类:%s\n", clz.getName(), clz.isInterface(),
                clz.isPrimitive(), clz.isArray(),
                clz.getSuperclass() != null ? clz.getSuperclass().getName() : "父类不存在");
        System.out.printf("包名:%s,修饰符:%s\n", clz.getPackage() != null ? clz.getPackage().getName() : "包不存在",
                Modifier.toString(clz.getModifiers()));

        for (Constructor c : clz.getConstructors()) {
            System.out.printf("构造函数名:%s\t", c.getName());
        }
        System.out.println();

        for (Field field : clz.getDeclaredFields()) {
            System.out.printf("字段名:%s\t", field.getName());
        }
        System.out.println();

        for (Method m : clz.getMethods()) {
            System.out.printf("方法:%s\t", m.getName());
        }
        System.out.println();

    }
}

 

package com.wulf.io;

import java.io.Serializable;
public class Member implements Serializable {

    static {
        System.out.println("I'm wumanshu!");
    }

    private String number;
    private String name;
    private Integer age;
    private String phone;

    public Member(String number, String name, int age, String phone) {
        this.number = number;
        this.name = name;
        this.age = age;
        this.phone = phone;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return String.format("(%s,%s,%d,%s)", number, name, age, phone);
    }

}

  运行结果:

类名:com.wulf.io.Member,是否接口:false,是否基本类型:false,是否数组:false,父类:java.lang.Object
包名:com.wulf.io,修饰符:public
构造函数名:com.wulf.io.Member    
字段名:number    字段名:name    字段名:age    字段名:phone    
方法:getNumber    方法:toString    方法:getName    方法:setName    方法:getAge    方法:setNumber    方法:setAge    方法:getPhone    方法:setPhone    方法:wait    方法:wait    方法:wait    方法:equals    方法:hashCode    方法:getClass    方法:notify    方法:notifyAll    
I'm wumanshu!
(9527,liangchaowei,53,18912346987)
(0001,wulf,33,13812345678)
(0001,zhangtianyou,33,13812345678)

 

  代理本身就是设计模式的一种,就是一种行为,可以有多种实现方式。比如你是个歌手,可以所有事情自己包揽过来做,也可以找个经纪人帮你做一些杂七杂八的事,至于登台唱歌这件核心的事还是得你自己起来做。代理从类角度分接口代理和类代理,接口代理jdk本身提供了对应的API实现,但必须要有接口的存在;类代理由cglib提供支持,可以没有接口。从实现角度分静态代理和动态代理,静态代理需要先定义好一个接口,多个实现类,其中一个实现类调用了另一个实现类的接口方法,在编译期就完成了代理;动态代理用到了上面提及的反射,在运行时生成对象实例,并通过实现java.lang.reflect.InvocationHandler的invoke方法完成代理。下面举例:

   1、静态代理:

package com.wulf.io;

public interface Log {

    void warn(String name);

}
package com.wulf.io;

public class HelloLog implements Log {

    @Override
    public void warn(String name) {
        System.out.printf("hello %s, you have be traced.\n", name);
    }

}
package com.wulf.io;

import java.util.logging.Level;
import java.util.logging.Logger;

public class HelloLogProxy implements Log {

    private HelloLog helloLog;

    public HelloLogProxy(HelloLog helloLog) {
        this.helloLog = helloLog;
    }

    public void warn(String name) {
        log("方法开始****");
        helloLog.warn(name);
        log("方法结束****");
    }

    private void log(String msg) {
        Logger.getLogger(HelloLog.class.getName()).log(Level.WARNING, msg);
    }
    
    public static void main(String[] args) {;
        Log log = new HelloLogProxy(new HelloLog());
        log.warn("wumanshu");
        
    }
}

  运行结果:

三月 20, 2017 12:58:51 上午 com.wulinfeng.io.HelloLogProxy log
警告: 方法开始****
hello wumanshu, you have be traced.
三月 20, 2017 12:58:51 上午 com.wulinfeng.io.HelloLogProxy log
警告: 方法结束****

  2、动态代理:

package com.wulf.io;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.logging.Level;
import java.util.logging.Logger;

public class LogHander implements InvocationHandler {
    private Object target;

    // 生成目标类
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log("begin log: " + method.getName());
        System.out.println("begin print: " + method.getName());
        Object result = method.invoke(target, args);
        log("finish log: " + method.getName());
        System.out.println("finish print: " + method.getName());
        return result;
    }

    /**
     * 日志打印
     * 
     * @param msg
     */
    private void log(String msg) {
        Logger.getLogger(HelloLog.class.getName()).log(Level.WARNING, msg);
    }

    public static void main(String[] args) {
        LogHander lh = new LogHander();
        Log log = (Log) lh.bind(new HelloLog());
        log.warn("world");
    }

}

  运行结果:

begin print: warn
hello world, you have be traced.
finish print: warn
三月 20, 2017 12:59:50 上午 com.wulf.io.LogHander log
警告: begin log: warn
三月 20, 2017 12:59:50 上午 com.wulf.io.LogHander log
警告: finish log: warn

  这里从结果看日志打印并不符合我们预期,我们预期日志打印位置应该跟静态代理运行结果一致才对。从标准输出来看代理是没问题的,所以问题出现在日志打印滞后了,这跟虚拟机执行日志打印到控制台的速度有关,因为加载并打印log的速度不及标准输出,所以产生了延时,再跑一次就好可以看到日志打印的位置正常了。

  3、CGLIB:先在pom.xml中新增依赖,使用cglib对类创建子类代理:  

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

 

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class Test implements MethodInterceptor {
    private HelloWorld helloWorld; // 目标代理对象,没有实现接口

    public Test(HelloWorld helloWorld) {
        this.helloWorld = helloWorld;
    }

    /**
     * 创建代理对象
     *
     * @return
     */
    public HelloWorld create() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloWorld.class); // 设置目标代理对象为父类
        enhancer.setCallback(this); // 设置回调对象,MethodInterceptor本身就是CallBack的子类,所以设置代理对象本身
        return (HelloWorld) enhancer.create(); // 生成代理对象——目标代理对象的子类
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("盘古天辟地.");
        Object invoke = method.invoke(helloWorld, objects); // 调用目标代理对象实际方法
        System.out.println("世界鸿蒙初开.");
        return invoke;
    }

    public static void main(String[] args) {
        HelloWorld proxy = new Test(new HelloWorld()).create(); // 创建代理对象的子类

        System.out.println("方法1:");
        proxy.say(); // 代理对象调用目标代理对象方法
        System.out.println("方法2:");
        proxy.hello();
    }

}

class HelloWorld {
    public void say() {
        System.out.println("hello, world.");
    }

    public void hello(){
        System.out.println("hell, wulf.");
    }
}

 

  运行结果:

方法1:
盘古天辟地.
hello, world.
世界鸿蒙初开.
方法2:
盘古天辟地.
hell, wulf.
世界鸿蒙初开.

 

  CGLIB会在运行时动态的生成一个被代理类的子类,子类重写了被代理类中所有非final的方法,在子类中采用方法拦截的技术拦截所有父类方法的调用,不需要被代理类对象实现接口。

posted on 2017-03-20 01:03  不想下火车的人  阅读(238)  评论(0编辑  收藏  举报

导航