反射和代理入门

反射

一、背景

反射和动态代理一直以来都是我比较模糊的地方,终于找了个时间好好地把反射和动态代理的内容看了一遍,算是有了一点点眉目,所以赶紧记了下来。

我们都知道,Java 的引用变量有两种类型:编译时类型、运行时类型。对于下列代码:

Person p = new Student();

这里的变量 p 的编译时类型为 Person,运行时类型为 Student。这个时候,如果我不知道 Student 类的具体实现是怎样的,但是我需要了解 Student 类的全部信息,比如它有哪些实例变量?它有哪些方法?哪些构造器?公有的或许还好说,但是如果是私有的呢?我如何去了解它呢?

再比如,如果我接收到一个外部传入的对象,它的编译时类型是 Object,得到这个对象之后我该怎么用?这真的让人很抓狂。

反射就是为了解决这些问题而存在的,反射可以在运行时获取到对象和类的全部信息。可以把反射形容为天眼,在反射面前,类和对象没有任何隐私。

二、使用反射

2.1 获取 Class 对象

要想使用反射,必须先获取 Class 对象。之所以必须这样做,是因为:每个类被加载之后,系统就会为该类生成对应的 Class 对象,通过该 Class 对象才可以访问到 JVM 中的这个类,从而获取它的所有真实信息。

在 Java 程序中,获取 Class 对象有三种方式:

  1. 使用 Class 类的 forName(String clazzName) 静态方法。该方法的参数是目标类的全限定类名。
    Class clazz = Class.forName("java.util.Random");
    
  2. 调用目标类的 class 属性来获取该类的 Class 对象。例如 Student.class 将会获取 Student 类的 Class 对象。
    Class clazz = Student.class;
    
  3. 调用某个对象的 getClass() 方法。该方法将返回对象所属类对应的 Class 对象。例如 p.getClass() 将会返回 Student 类的 Class 对象。
    Class clazz = p.getClass();
    

总结:

* 大部分情况下,使用第 2 种方式获取指定类的 Class 对象。
* 如果外部只给了一个字符串,那么只能使用第一种方式获取 Class 对象。
2.2 从 Class 中获取类或对象的信息

Class 类中提供了非常多的方法来获取该 Class 对象的全部真实信息。对于 Class 类的方法,常用的有以下几类:

  • 获取 Class 对应类所包含的构造器

    1. 获取参数类型为 String 的 public 构造方法。(可变参数)
      Class clazz = p.getClass();
      Constructor constructor = clazz.getConstructor(String.class);	
      
    2. 获取所有的 public 构造方法
      Constructor[] constructors = clazz.getConstructors();
      
    3. 获取参数为 Integer 类型的构造方法(无视访问控制权限)(可变参数)
      Constructor[] constructors = clazz.getDeclaredConstructor(Integer.class);
      
    4. 获取全部的构造方法(无视访问控制权限)
      Constructor[] constructors = clazz.getDeclaredConstructors();
      
  • 获取 Class 对应类所包含的方法

    1. 获取参数类型为 String 的 public 方法。(可变参数)

      Method method = clazz.getMethod(String.class);
      
    2. 获取所有的 public 方法

      Method[] methods = clazz.getMethod(String.class);
      
    3. 获取参数为 Integer 类型的方法(无视访问控制权限)(可变参数)

      Method method = clazz.getDeclaredMethod(Integer.class); 
      
    4. 获取全部的方法(无视访问控制权限)

      Method[] methods = clazz.getDeclaredMethod(Integer.class); 
      
  • 获取 Class 对应类所包含的成员变量

    1. 获取名为 “name” 的 public 变量

      Field field = clazz.getField("name");
      
    2. 获取所有的 public 变量

      Field[] fields = clazz.getFields();	
      
    3. 获取名为 “name” 的变量(无视访问控制权限)

      Field field = clazz.getDeclaredField("name");
      
    4. 获取所有的变量(无视访问控制权限)

      Field fields = clazz.getDeclaredFields();	
      

注意:获取到对象的 私有变量/方法 等并不意味着可以直接去使用它们发挥它们自己的功能。如果想要使用它们,还需要获取调用该 私有变量/方法 的权限。setAccessible(boolean flag) 方法可以设置权限。

例:

Class clazz = p.getClass();
Field name = clazz.getDeclaredField("name");
// 开启调用变量的权限
name.setAccessible(true);
// 打印对象 p 的变量 name。
System.out.println(name.get(p));

三、代码演示反射

案例介绍:通过反射获取接口 Person 实现类 Student 类的全部信息(构造器、方法、成员变量)。

Person.java:

/**
 * 定义接口 Person
 */
public interface Person {
    public void eat();
    public void sleep();
}

Student.java:

/**
 * 定义学生类实现接口 Person
 */
public class Student implements Person {
    private String name;
    private int age;
    public char sex;

    @Override
    public void eat() {
        System.out.println("学生吃饭了");
    }

    @Override
    public void sleep() {
        System.out.println("学生睡觉了 ");
    }

    private void study(){
        System.out.println("学生学习了");
    }
}

Test.java:

public class Test {
    public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
        Person p = new Student();
		// 获取引用 P 的 Class 对象
        Class clazz = p.getClass();

        // 通过 Class 对象获取所有的构造方法
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        System.out.println("====== 引用p 的所有构造器= =====");
        for (Constructor constructor : declaredConstructors){
            System.out.println(constructor);
        }

		// 通过 Class 对象获取所有的方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        System.out.println("====== 引用p 的所有方法 =====");
        for (Method method : declaredMethods){
            System.out.println(method);
        }

		// 通过 Class 对象获取所有的成员变量
        Field[] declaredFields = clazz.getDeclaredFields();
        System.out.println("====== 引用p 的所有成员变量 =====");
        for(Field field : declaredFields){
            System.out.println(field);
        }
    }
}

代理

一、背景

我们都知道,我们生活中绝大多数的商品都有自己的行业规范。以电动汽车为例,特斯拉生产电动车就一定要遵循行业规范,不然它生产出来的汽车不给用啊。不得不说特斯拉的电动汽车还挺好看的,假如我买了一辆特斯拉电动汽车,但是我还是觉得这车不能展示我骚气的光芒,于是我就想改装它。于是我就把我心爱的特斯拉开进了改装店进行了一次轰轰烈烈的改装…。

这个故事,很好地体现出了代理。其中,行业规范可以理解为接口,特斯拉遵循行业规范生产电动汽车可以理解为接口的实现类,而我为了给爱车增加一点骚气的功能而到改装店进行改装,这就是代理。

总结下来就是,代理是为了增强接口实现类的功能而存在的。

二、代理的定义

  • 代理模式

    为其他对象提供一种代理以控制对这个对象的访问。

  • 动态代理

    代理类在程序运行时创建的代理方式被称为动态代理。

三、动态代理的使用

3.1 创建代理对象

要创建一个代理对象,需要使用 Proxy 类的 newProxyInstance 方法。这个方法有三个参数:

Object newProxyInstance​(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
其中各参数含义如下:
	loader			一个类加载器。参数为 null 时表示使用默认的类加载器;
	interfaces		一个 Class 对象数组,每个元素都是需要实现的接口;
	h				一个调用处理器。
3.2 创建调用处理器

前面知道,代理类可以在运行时创建全新的类,这样的代理类能够实现指定的接口,不仅如此,它还须具有下列方法:

  • 指定接口所需要的全部方法;
  • Object 类中的全部方法,例如 toString、equals 等。

然而,在运行的时候不能定义这些方法的新代码。唯一的解决办法就是提供一个调用处理器( invocation handler )。调用处理器是实现了 InvocationHandler 接口的类对象。在这个接口中只有一个方法:

Object invoke(Object proxy, Method method, Object[] args)
其中各参数含义如下:
	proxy		代理对象;
	method		此刻正在调用的方法;
	args		此刻正在调用的方法的参数。

为了确保这个方法能够始终被调用,Java 提供了这样的机制:无论何时调用代理对象的方法,调用处理器的 invoke 方法都会被调用。

总结:

调用处理器作用在于具体实现增强类的功能。

四、代码演示动态代理

案例介绍:在过滤器中使用动态代理解决 Tomcat 服务器上部署网站的中文乱码问题。

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
    final HttpServletRequest request = (HttpServletRequest) req;
    HttpServletRequest MyReq = (HttpServletRequest) Proxy.newProxyInstance(EncodeFilter.class.getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
        // 重写调用处理器的 invoke 方法
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object object = null;
            // 1. 判断当前执行的方法名是否是 getParameter
            if (method.getName().equalsIgnoreCase("getParameter")){
                // 2. 获取当前请求的方式:是 get 还是 post
                String md = request.getMethod();
                // 3. 判断当前的请求方式是 get 还是 post
                if (md.equalsIgnoreCase("get")){
                    // 如果是 GET 方式的请求
                    String result = (String) method.invoke(request,args);
                    // 将字符串按照指定的编码格式转换为字节数组,然后再按照指定的编码格式转换为字符串
                    result = new String(result.getBytes("iso-8859-1"),"utf-8");
                    return result;
                } else {
                    // 如果是 POST 方式的请求
                    request.setCharacterEncoding("utf-8");
                    object = method.invoke(request,args);
                }
            } else{
                // 4. 一定也要把不是 getParameter 的方法给执行一遍
                object = method.invoke(request,args);
            }
            // 5. 返回对象
            return object;
        }
    });
    // 过滤器放行。注意:这里一定要把增强后的对象传递出去
    chain.doFilter(MyReq, resp);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值