反射以及动态代理

反射

  反射之中包含了一个'反'字,那就肯定有正,那我们就先从'正'开始
一般情况下,我们使用类的时候必定知道他是什么类,是用来做什么的,于是我们直接对这个类进行实例化,之后使用类的对象进行操作。
  反射则是一开始我们并知道我要初始化的这个类的对象是什么,自然也无法使用new关键字来创建对象了,这个时候,我们就要引入JDK 给我们提供的反射api 进行反射调用,反射就是在运行时才知道要操作的类是什么,并且可以再运行时获取类的完整构造,并调用对应的方法。
Reflection(反射)是java被视为动态语言的关键,反射几只允许程序在执行期借助Reflection API 取得任何类的内部信息,并直接操作任意对象的内部属性及方法。

Java反射机制主要提供了一下功能

  • 在运行时构造任意一个类的对象
  • 在运行时获取任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法(属性)

  java是一门面向对象的语言,在面向对象的世界里,万事万物皆对象,既然万事万物皆对象,那么我们的类是不是对象呢?我们写的每一个类都可以看成一个对象,是java.lang.Class类的对象,每一个类对应的Class放在哪里呢?当我们写完一个类的java文件,编译成class文件的时候,编译期都会将这个类的对应的calss对象放在class的末尾,里面都保存了些什么?大家可以理解保存了类的元数据信息,一个类的元数据信息包括什么?有哪些属性,方法,构造器,实现了哪些接口等等,那么这些信息在java里都有对应的类来表示。

Class

Class是一个类,封装了当前对象多对应的类的信息

  一个类中有属性,方法,构造器等,比如说有一个person类,一个Order类,一个Book类,这些都是不同的类,现在需要一个称呼用来描述类,这就是Class,他应该有类名,属性,方法,构造器等,Class是用来描述类的类(这个有点拗口)
  Class类就像是对象本身自己照镜子一样,可以看到自己的全部,有哪些属性,方法,构造器,实现了哪些接口等
  对于每个类而且,JRE都为期保留一个不变的Class类型的对象,一个Class对象包含了特定某个类的有关信息。
  对象只能由系统建立对象,一个类(而不是一个对象)在JVM中只会有一个Class实例

获取Class对象的三种方式
名称 方式
1.通过类名获取 类型.class
2.通过对象获取 对象名.getClass()
3.通过全类名获取 Class.forName(全类名)
        //实例化对象的标准用法,也就是所谓的正
        Servant servant = new Servant();
        //获取class三种方法
        Class servantClass = Servant.class;
        Class servantClass1 = servant.getClass();
        Class servantClass3 = Class.forName("com.company.Reflect.Servant");
Class类的常用方法
方法

接下来我们就来使用反射来获取属性,方法,构造器等。

ps:因为都是固定的调一些API 此处就直接上代码了

public class Person {
    String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        System.out.println("this is setName()");
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
        System.out.println("this is setAge()");
    }
    //包含一个带参的构造器和一个不带参的构造器
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public Person() {
        super();
    }
    private void privateMethod(){
        System.out.println("this is private method");
    }
}
构造器相关
内容 方法
获取全部constructor对象 getConstructors()
获取某一个constructor对象 需要传入参数列表 getConstructor(Object...)
调用构造器的 newInstance() 方法创建对象 有参数需传入 构造器.newInstance(Object...)
public class TestConstructor {
    /*构造器相关*/
    public void testConstructor() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        String className ="com.company.Reflect.Person";
        Class<Person> clazz = (Class<Person>) Class.forName(className);
        System.out.println("获取全部constructor对象");
        Constructor<Person>[] constructors = (Constructor<Person>[]) clazz.getConstructors();
        for (Constructor<Person> constructor:constructors) {
            System.out.println(constructor);
        }

        System.out.println("获取某一个constructor对象 需要参数列表-----");
        Constructor<Person> constructor = clazz.getConstructor(String.class, int.class);
        System.out.println(constructor);

        System.out.println("调用构造器的 newInstance() 方法创建对象-----");
        Person hellow = constructor.newInstance("hellow", 18);
        System.out.println(hellow.getName());
    }
}

接下来我们看一下打印结果


打印结果
方法相关
内容 方法
获取对应类中的所有方法,不能获取private方法,且获取从父类继承来的所有方法 getMethods()
获取所有方法,包括私有方法 所有声明的方法,都可以获取到,且只获取当前类的方法 getDeclaredMethods()
获取指定的方法 需要参数名称和参数列表,无参数则不需要写 clazz.getDeclaredMethod(String name,Class<?>... paramterTypes)
执行方法,第一个参数表示执行那个对象的方法,剩下的参数时执行方法时需要的参数 invoke(Object obj,Obkect... obj)
执私有方法的执行,必须在调用invoke之前加上一句 method.setAccessible(ture)不然会报错 setAccessible(ture)
public class TestMethod {
    public void testMethod() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        String className ="com.company.Reflect.Person";
        Class clazz =Class.forName(className);
        System.out.println("获取clazz中对应类中的素有方法 不能获取private方法,且获取从父类继承来的所有方法");
        Method[] methods = clazz.getMethods();
        for (Method method :methods) {
            System.out.println(" "+method.getName()+"()");
        }
        System.out.println("--------------------------");
        System.out.println("获取所有方法,包括私有方法 所有声明的方法,都可以获取到,且只获取当前类的方法");
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method method :declaredMethods) {
            System.out.println(" "+method.getName()+"()");
        }
        System.out.println("--------------------------");
        System.out.println("获取指定的方法 需要参数名称和参数列表,无参数则不需要写");
        Method method = clazz.getDeclaredMethod("setAge", int.class);
        System.out.println(method);
        System.out.println("--------------------------");

        System.out.println("执行方法,第一个参数表示执行那个对象的方法,剩下的参数时执行方法时需要的参数");
        Object instance = clazz.newInstance();
        method.invoke(instance,18);

        System.out.println("执私有方法的执行,必须在调用invoke之前加上一句 method.setAccessible(ture)");
        Method privateMethod = clazz.getDeclaredMethod("privateMethod");
        System.out.println(privateMethod);
        System.out.println("--------------------------");
        System.out.println("执行私有方法");
        privateMethod.setAccessible(true);
        privateMethod.invoke(instance);

    }
打印结果

当不加setAccessible(true)时的结果


不加时报错结果
属性相关
内容 方法
获取公用和私有的所有字段 但是不能获取父类的字段 getDeclaredFields()
获取指定字段 getDeclaredField(String name)
获取指定字段的值 字段.get(Object obj)
设置指定对象指定字段的值 字段.set(Object obj)
字段是私有的,不管是读还是写都必须先调用 setAccessible(ture) 方法 setAccessible(true)

动态模式

代理模式和静态代理

  代理模式 给某一个对象提供一个代理对象,并有代理对象控制对原对象的引用,通俗的来讲代理模式就是我们生活中常见的中介
  举个例子:张三想买某种生活用品,虽然他可以自己去找,但是有点浪费时间和精力,或者自己不好意思去买,于是张三就通过中介Mark来买,Mark来帮张三买一个,张三只是负责选择喜欢的尺寸,然后付钱给Mark就可以了

目的:
1.通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;
2.通过代理对象对原有的业务增强;
代理模式一般会有三个角色:

  • 抽象角色:指代理角色和真实角色对外提供的公共方法,一般为一个接口
  • 真实角色:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用。也就是真正的业务逻辑在此。
  • 代理角色:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制都放到代理角色中处理!
      而访问者不再访问真实角色,而是去访问代理角色。
      静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。一般来说,被代理对象和代理对象是一对一的关系,当然一个代理对象对应多个被代理对象也是可以的。
      静态代理,一对一则会出现时静态代理对象量多、代码量大,从而导致代码复杂,可维护性差的问题,一对多则代理对象会出现扩展能力差的问题。

接下来我们用代码来简单的表述一下静态代理

//代表真实角色
public class AaFactory implements ManToolsFactory {
    @Override
    public void saleManTools(String size) {
        System.out.println("按需求定制了一个size为"+size+"的女model");
    }
}
//抽象角色
public interface ManToolsFactory {
    void saleManTools(String size);
}

/*代理角色*/
public class Mark1 implements ManToolsFactory {
    
    public ManToolsFactory factory;

    public Mark1(ManToolsFactory factory) {
        this.factory = factory;
    }
    
    /*后置处理器*/
    private void doSthAfter() {
        System.out.println("精美包装,快递一条龙服务");
    }

    /*前置处理器*/
    private void doSthBefore() {
        System.out.println("根据需求,进行市场调研和产品分析");
    }

    @Override
    public void saleManTools(String size) {
        doSthBefore();
        factory.saleManTools(size);
        doSthAfter();
    }
}

public static void main(String[] args) {
        ManToolsFactory factory = new AaFactory();
        Mark mark = new Mark(factory);
        mark.saleManTools("D");
    }

静态代理运行结果

  通过运行我们发现确实完成了整个工作逻辑,但是有一个问题,如果这时张三的老婆来了,但是需求不同,需要另一种生活用品,这时怎么办呢?智能是再去增加一个新的抽象接口,去做新的逻辑,这样就造成了一个问题,需要不停去新增,不停的去修改,这就违反了面向对象的开闭原则。同时扩展能力差 可维护性差,所以在一般的正常使用中我们都是采用动态代理来实现。

动态代理

  是指在使用时再创建代理类和实例
  优点
  只需要1个动态代理类就可以解决创建多个静态代理的问题,避免重复、多余代码,更强的灵活性
  缺点
  效率低,相比静态代理中 直接调用目标对象方法,动态代理则需要先通过Java反射机制 从而 间接调用目标对象方法
  应用场景局限,因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),即只能针对接口 创建 代理类,不能针对类创建代理类。
  在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler接口、另一个则是 Proxy类,这个类和接口是实现我们动态代理所必须用到的。
  InvocationHandler接口是给动态代理类实现的,负责处理被代理对象的操作的,而Proxy是用来创建动态代理类实例对象的,因为只有得到了这个对象我们才能调用那些需要代理的方法。

下面简单的通过代码来实现一下

public class AaFactory implements ManToolsFactory {
    @Override
    public void saleManTools(String size) {
        System.out.println("按需求定制了一个size为"+size+"的女model");
    }
}

public class BbFactory implements WomanToolsFactory {
    @Override
    public void saleWomanTools(float length) {
        System.out.println("按需求定制了一个高度为"+length+"的男model");
    }
}

public class MarkCompany implements InvocationHandler {

    /*持有的真实对象*/
    private Object factory;

    public Object getFactory() {
        return factory;
    }

    public void setFactory(Object factory) {
        this.factory = factory;
    }

    /*通过proxy获得动态代理对象*/
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(factory.getClass().getClassLoader(),factory.getClass().getInterfaces(),this);
    }


    /*通过动态代理对象方法进行增强*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        doSthBefore();
        Object result = method.invoke(factory, args);
        doSthAfter();
        return result;
    }

    /*后置处理器*/
    private void doSthAfter() {
        System.out.println("精美包装,快递一条龙服务");
    }

    /*前置处理器*/
    private void doSthBefore() {
        System.out.println("根据需求,进行市场调研和产品分析");
    }
}

public interface ManToolsFactory {
    void saleManTools(String size);
}

public interface WomanToolsFactory {

    void saleWomanTools(float length);

}

public static void main(String[] args) {
        /*动态代理*/
        ManToolsFactory manToolsFactory = new AaFactory();
        MarkCompany company = new MarkCompany();
        company.setFactory(manToolsFactory);
        ManToolsFactory employee = (ManToolsFactory) company.getProxyInstance();
        employee.saleManTools("E");

        WomanToolsFactory womanToolsFactory = new BbFactory();
        company.setFactory(womanToolsFactory);
        WomanToolsFactory employee1 = (WomanToolsFactory) company.getProxyInstance();
        employee1.saleWomanTools(1.9f);
    }


动态代理运行结果

通过打印结果可以看到我们同样的实现了需求,但是工作量要比静态代理少,同时后期维护的话要更省事

  今天的介绍,对于庞大的java来说只是冰山一角,只是通过了一个小的demo来介绍一下反射和代理,所说的这些并不能面面俱到,只能对自己的学习所一个总结,以便于以后的查阅。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,401评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,011评论 3 413
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,263评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,543评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,323评论 6 404
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,874评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,968评论 3 439
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,095评论 0 286
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,605评论 1 331
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,551评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,720评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,242评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,961评论 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,358评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,612评论 1 280
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,330评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,690评论 2 370

推荐阅读更多精彩内容