• 第17章 反射机制


    通过本章需要理解反射机制操作的意义以及Class类的作用,掌握反射对象实例化操作,并且可以深刻理解反射机制与工厂模式结合意义。掌握类结构反射操作的实现,并且可以通过反射实现类中构造方法、普通方法、成员属性的操作。掌握反射机制与简单Java类之间的操作关联,掌握类加载起的作用,并且可以实现自定义类加载器,掌握动态代理机制的实现结构,并理解CGLIB开发包的作用,掌握Annotation定义,并且可以结合反射机制实现配置管理。
            反射机制是java语言提供的一项重要技术支持,也是Java区别于其他语言并且迅速发展的重要特征,利用反射机制可以帮助开发者编写更为灵活与高可用的代码结构。本章将完整的分析反射机制中的各个组成部分,并且重点分析反射机制与工程模式以及代理设计模式的重要关联。

    17.1 认识反射机制

    重用性是面向对象设计的核心原则。为了进一步提升代码的重用性,Java提供了反射机制。反射机制首先考虑的是“正”,“反”的操作,所谓的“正”操作,是指当开发者使用一个类的时候,一定要先导入程序所在的包,而后根据类进行对象实例化,并且依靠对象调用类中的方法;而所谓的反操作,是指可以根据实例化对象反推出器类型。
            Class类是反射机智的根源,可以通过Object类中所提供的方法获取一个Class实例。
    获取Class实例化对象:public final ClassgetClass().

    范例:获取反射信息

    package cn.mldn.demo;
    import java.util.Date;
    public class JavaReflectDemo
    {
    public static void main(String[]args)throws Exception
    {
    Date date=new Date();//正,获取类实例化对象
    System.out.println(date.getClass());//反,获取对象所属类信息
    }
    }.

    输出 class java.util.Date
    本程序通过一个类的实例化对象调用了getClass()方法,而根据输出的结果可以发现,此时返回了该实例化对象的完整名称。

    17.2 Class类对象实例化

    java.lang.Class类是反射机制操作的起源,为了适应不同情况下的反射机制操作,Java提供有3中Class类对象实例化方式。
    方式1:利用Object类中提供的getClass()方法获取实例化对象。

    package cn.mldn.demo;
    class Member{}
    public class JavaReflectDemo
    {
    public static void main(String []args)throws Exception
    {
    Member member=new Member();//操作特点,需要获取一个类的实例化独享后才可以获得Class实例
    Classclazz=member.getClass();
    System.out.println(clazz);
    }
    }

    执行结果:class cn.mldn.demo.Member
    Object类是所有类的父类,这样所有类的实例化对象都可以利用getClass()方法获取Class类实例化对象。

    方式2:使用类.class形式获取指定类或接口的Class实例化对象。
    package cn.mldn.demo;
    class Mmeber{}
    public class JavaReflectDemo
    {
    public static void main(String[]args)throws Exception
    {
    Classclazz=Member.class;//直接通过一个类的完整名称可以获取Class类实例,需要编写import或完整类名称。
    System.out.println(clazz);
    }
    }

    执行结果:class cn.mldn.demo.Member

    本程序利用JVM的支持方式,通过一个类的直接获取了Class实例化对象。

    方式3:使用Class类内部提供的forName()方法根据类的完整名称获取实例化对象
    Class实例化方法:public static ClassforName(String className)throws ClassNotFoundException

    package cn.mldn.demo;
    class Member
    {
    public class JavaReflectDemo
    {

    //操作特点通过名称字符串(包.类)可以获取Class类实例,可以不适用import导入
    Classclass=Class.forName("cn.mldn.demo");
    System.out.println(clazz);
    }
    }
    执行结果:class cn.mldn.demo.Member
    本程序直接根据一个字符串定义的类名称来获取Class类的实例化对象,由于字符串的支持较多并且拼接方便,这种获取Class类的实例方式跟我给灵活。

    注意:保证类存在

    当使用Class.forName()方法获取Class类对象实例化的时候,如火字符串定义的类名称不存在则会出现ClassNotFoundException异常,这就需要保证在所创建的项目中已经设置CLASSPATH环境属性中存在指定类。

    17.3 反射机制与对象实例化

    反射机制的设计可以更方便的帮助开发者实现解耦设计,并且可以帮助程序拜托对关键字new的依赖,通过反射获取实例化对象。

    17.3.1 反射Class类实例化对象

            当通过指定类获取了Class类实例化对象后,就可以利用反射实例化的方式替代关键字new的使用。

    范例:反射实例化对象

    package cn.mldn.demo;
    class Member
    {
    public Member()
    {
    System.out.println("构造方法");
    }
    @Override
    public String toString()
    {
    return "toString类的覆写";
    }
    }

    public class JavaReflectDemo throws Exception
    {
    Classclazz=Class.forName("cn.mldn.demo.Member");
    Object obj=clazz.getDeclaredConstrctor().newInstance();
    System.out.println(obj);
    }
    程序执行结果
    【构造方法】实例化Member类对象
    【toString()覆写】toString类的覆写

    本程序再获取Member类实例化对象时并没有使用关键字new,而是基于反射机制实现了对象实例化,即按照此类结构只要设置了正确的类名称,字符串就可以自动调用无参构造方法指定类的实例化对象。

            提示:关于不同JDK版本的反射实例化操作

    本程序中使用反射实例化方式为clazz.getDeclaredConstructor().newInstance(),这段代码的核心意义在于:获取指定类提供的无参构造方法并进行对象实例化,这一解释可以通过本章后面的内容慢慢理解,但是需要注意的是,这类操作是从JDK1.9后提倡使用的,而在JDK1.9千直接使用Class类内部提供的newInstance()方法获取实例化对象,该方法定义如下
            反射实例化对象:public T newInstance() throws InstantianException,IllegalAcessException.

    范例:直接使用newInstance()方法
    public class JavaReflectDemo
    {
    public static void main(String []args)throws Exception
    {
    Classclazz=Class.forName("cn.mldn.demo.Member");
    Object obj=clazz.newInstance();//实例化对象
    System.out.println(obj);
    }
    }

    之所以从JDK1.9之后将此方法设置为Deprecated,主要原因在于其只能够调用无参构造,而提倡的反射实例化方式可以由开发者根据构造方法的参数类型传递相应的数据后进行对象实例化操作。

    17.3.2 反射与工厂设计模式

    使用工厂模式的主要特点是解决接口与子类之间应直接使用关键字new所造成的耦合问题,但是传统的工厂设计模式中会存在两个严重的问题。
            问题1:传统工厂设计属于静态工厂设计,需要根据传入的参数并结合大量的分支判断语句来判别所需要实例化的子类,当一个工厂或抽象类扩充子类时必须修改工厂类结构,否则无法获取新的子类实例。
            问题2:工厂设计模式只能够满足一个接口或者抽象类获取实例化对象的需求,如果有更多的接口或抽象类定义时需要定义更多的工厂类或扩充工厂类中的static方法。

    范例:反射机制与工厂设计模式

    package cn.mldn.demo;
    interface Imessage{public void send();}
    class CloudMessage implements IMessage
    {
    @Override
    public void send(){System.out.println("云消息www.mldnjava.cn");}
    }

    class NetMessage implements IMessage
    {
    public void send();{System.out.println("网络消息");}
    }

    class Factory
    {
    private Factory{}//避免产生实例化对象
    @SuppressWarnings("unchecked")
    public static T getInstance(String className,Classclazz)
    {
    T instance=null;
    instance=(T)class.forName(className).getDeclaredContructor().newInstance();
    }
    }

    public class JavaReflectDemo
    {
    pubic static void main(String[]args)throws Exception
    {
    IMessage msg=Factory.getInstance("cn.mldn.demo.NetMessage",IMessage.class);
    msg.send();
    }
    }

    执行结果:网络消息
    本程序实现了一个全新的并且可用工厂类结构,为了让该工厂类适合于所有类型,程序中结合反射机制与反省获取指定类型的实例,这样可以避免向下转型所带来的安全隐患,

    17.3.3 反射与单例模式

            单例模式设计的核心本质在于:类内部的构造方法私有化,在类的内部产生实例化对象之后通过static方法获取实例化对象进行类中的结构调用。单例模式一共有两类:懒汉设计模式和饿汉是。饿汉式的单利由于其再累加载的时候就已经进行了对象实例化处理,所以不涉及多线程的访问问题:但是懒汉式单例模式在多线程访问下却有可能出现多个实例化对象的产生问题。

    范例:观察懒汉式但力涉及与多线程访问

    package cn.mldn.demo;
    class Gingleton
    {
    public static Singleton instance=null;
    private Singleton(){System.out.println(""+Thread.currentThread().getName()+"实例化Singleton对象");}
    public static Singleton getInstance()
    {
    if(instance==null){instance=new Singleyon();}
    return instance;

    }

    public void print(){System.out.println("实例化Singleton");}
    }

    public class JavaReflectDemo
    {
    public static void main(String[]args)throws Exception
    {
    for(int x=0;x<3;x++)
    {
    new Thread(()->
    {
    Singleton.getInstance().print();
    },"单例消费端-"+x).start();
    }
    }
    }

    程序执行结果:
    【单例消费端-2】实例化Singleton
    【单例消费端-1】实例化Singleton
    【单例消费端-0】实例化Singleton

    单例模式的核心在于Singleton类只允许有一个实例化对象,然而通过本程序的执行可以发现,此时产生了多个实例,而这一操作的根源来源于多线程访问不同步,即有多个线程独享在第一次使用时都产生了多个实例化对象,而这一操作的根源在于多线程访问不同步,即有多个向成都向在第一次使用时都通过了实例化对象的判断语句(if(instance==null)).所以此时只能用synchronized来进行同步处理。

    范例:解决懒汉单例模式中的多线程访问不同步问题,修改Singleton.getinstance()方法定义
    public static Singleton getInstance()
    {
    if(instance==null)
    {
    synchronized(Singleton class)
    {
    if(instance==null){instance=new Singleton();}
    }
    }
    return instance;
    }

    本程序利用同步代码块的形式对Singleton类实例化对象与实例化操作进行了判断,这样就保证了多线程模式下只能存在一个Singleton的实例化对象

    提问:关于synchronized同步处理的位置
    对于多线程的并发访问下的同步操作,为什么不直接在getinstance()方法定义上使用synchronized关键字定义:
    public static synchronized Singleton getInstance()
    {
    if(instance==null){instance=new         Singleton();}
    return instance();
    }

    此时的代码执行后,也可以实现正常的懒汉式单例模式,为什么本代码中却要使用同步代码块,又在同步代码块中多增加一次instance是否实例化的判断呢

    回答:在保证性能的同时需要提供同步支持
    synchronied的作用在于为指定范围的代码追加一把同步锁,如果直接在getInstance()方法上定义,虽然可以同步处理Singleton类对象实例化操作,但必然造成多线程并发执行,效率缓慢,所以利用同步带吗来解决。
            实际上在本程序中,只要保证instance对象是否被实例化的判断进行同步处理即可,所以使用同步代码块进行instance()对象实例化的判断与处理。

    17.4 反射机制与类操作

    Java反射机制可以在程序运行状态下,自动获取并调用任意一个类中的组成结构(成员塑性、方法等),这样的做法可以避免单一的程序调用模式,使代码开发变得更加灵活。
     

    17.4.1 反射获取类结构信息

    程序开发中,任何定义的类都需要存在继承关系,同时为了代码结构的清晰,也应该利用包保存不同功能的类,可以利用下表所示方法获取类的相关信息
     

    No方法类型描述
    1public Package getPackage()普通获取包信息
    2public ClassgetSuperclass()普通获取继承父类
    3public Class[]getinterfaces()普通获取实现接口

    范例:反射获取类结构信息

    package cn.mldn.demo;
    interface IMessageService{public void send();}
    interface IChannelService{public boolean connect();}
    abstract class AbstractBase()
    class Mail extends AbstractBase implements IMessageService,IChannelService
    {
    @Override
    public boolean connect()
    {
    return true;
    }
    @Override
    public void send()
    {
    if(this.connect()){return true;}
    }
    }

    public class JavaReflectDemo
    {
    Classcls=Mail.class;//获取指定类的Class对象
    Package pack=cls.getPakage();//获取指定类的保定易
    System.out.println(pack.getName());//获取包名称
    Classparent=cls.getSuperclass();//获取父类对象
    System.out.println(parent.getName());//父类信息
    System.out.println(parent.getSuperclass().getName());//父类信息
    Classclazz[]=cls.getInterfaces();//获取接口信息
    for(Classtemp:clazz)
    {
    System.out.println(temp.getName());
    }
    }

    17.4.2 反射调用构造方法

    构造方法是类的重要组成部分,也是实例化对象时必须调用的方法,在Class类中可以通过下表所示方法获取
     范例:调用构造方法

    package cn.mldn.demo;
    import java.lang.reflect.Constructor;
    class Mail
    {
    private String msg;
    public Mail(){}
    public Mail(String msg)
    {
    System.out.println("构造方法调用Mail类单参构造方法,实例化对象");
    this.msg=msg;
    }

    @Override
    public String toString()
    {
    return "消息内容"+this.msg;
    }
    }

    public class JavaReflectDemo
    {
    public static void main(String []args)throws Exception
    {
    Classcls=Mail.class;
    Contructor[]constructors=cls.getDeclaredContructors();
    for(Constructorcons:constructors){System.out.println(cons);}
    //获取单参构造并且参数类型为String的构造方法对象实例
    Constructorcons=cls.getDeclaredConstructor(String.class);
    Object obj=cons.newInstance("AAA");
    System.out.println(obj);
    }
    }

    本程序通过反射机制获取类中的全部购总爱方法并进行信息展示,随后有获取了一个指定类型的构造方法并通过Constructor类的newInstance()方法实现了对象反射实例化操作。

    17.4.3 反射调用方法

    每个类都有不同的功能,所有的功能都可以通过方法进行定义。在Java中除了通过具体的实例化对象实现方法调用外,也可以利用反射基于实例化对象的形式实现方法调用:
    范例:获取类中的方法信息

    package cn.mldn.demo;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    class Mail
    {
    public boolean connect(){return true;}
    public void send(){System.out.println("消息发送");}
    }

    public class JavaReflectDemo
    {
    public static void main(String[]args)throws Exception
    {
    Classcls=Mail.class;
    Method methord[]=cls.getMethords();
    for(Method met=methods)
    {
    int mod=met.getModifier();
    System.out.print(Modifier.toString(mod)+"");
    System.out.print(met.getReturnType().getName()+"");
    System.out.print(met.getName()+"(");
    Classparams[]=met.getParameterTypes();//获取参数类型
    for(int x=0;x {
    System.out.println(params[x].getName()+" "+"arg-"+x);
    if(x System.out.print(",");
    }
    }

    System.out.println(")");
    Classexp[]=met.getExceptionTypes();//获取异常信息
    if(exp.length>0){System.out.print("throws");}
    for(int x=0;x {
    System.out.print(exp[x].getName());
    if(x {
    System.out.println(",");
    }
    }
    System.out.println(",");
    }
    }
    }

    本程序通过反射机制获取了一个类中定义的所有方法,随后将获取到的每一个方法对象中的信息拼凑输出。
            反射机制编程中除了获取类中的方法定义外,最为重要的功能就是可以利用Method类中的invoke()方法并结合实例化对象(Object类型即可)实现反射方法调用。下面编写一个程序利用反射机制实现类中setter、getter方法调用。

    范例:反射调用类中的setter、getter方法

    package cn.mldn.demo;
    import java.lang.reflect.Method;
    class Member
    {
    private String name;
    public void setName(String name)
    {
    this.name=name;
    }
    public String getName(){return name;}
    }

    public class JavaReflectDemo
    {
    public static void main(String[]args)throws Exception
    {
    Classcls=Member.class;
    String value="小李";//设置内容
    //通过反射实例化才可以调用类中的成员属性和方法
    Object obj=cls.getDeclaredConstructor().newInstance();//调用无参构造实例化
    //反射调用方法需要明确地指导方法的名称以及方法中的参数类型
    String setMethodName="setName";
    Method setMethod=cls.getDeclaredMethod(setMethodName,String.class);
    setMethod.invoke(obj,value);
    String getMethodName="getName";
    Method getMethod=cls.getDeclaredMethod(getMethodName);
    System.out.println(getMethod.invoke(obj));

    }
    }

    通过反射实现的方法调用最大的特点是可以直接利用Object类型的实例化对象进行方法调用,但是在获取方法对象时需要明确知道方法名称以及方法的参数类型。

    17.4.4 反射调用成员属性

    成员属性保存着每一个对象的具体信息,Class类可以获取类中的成员信息,其提供的操作方法如下

            范例:获取类中的成员属性信息

    package cn.mldn.demo;
    import java.lang.reflect.Filed;
    interface IChannelService
    {
    public static finale String NAME="mldnjava";
    }
    abstract class AbstractBase
    {
    protected static finale String BASE="QQQ";
    private String info="hello";
    }
    class Member extends AbstractBase implements IChannelService
    {
    privaye String name;
    private int age;
    }

    public class JavaReflectDemo
    {
    public static void main(String[]args) throws Exception
    {
    Classcls=Member.class;//指定类class对象
    {
    Field fields[]=cls.getFields();
    for(Field fie:fields)
    {
    System.out.println(file);
    }
    }
    {

    Field fields[]=cls.getDeclaredFields();//获取本类成员属性
    for(Field fie:fields){System.out.pritnln(fie);}
    }
    }
    }

    本程序获取了父类继承而来的public成员塑性以及从本类定义的private成员属性信息。而获取Field成员属性对象的核心意义在于可以通过Field类并结合实例华独享实现属性赋值与获取。

    范例:反射操作成员属性内容
    package cn.mldn.demo;
    import java.lang.reflectField;
    class Member{private String name;}
    public class JavaReflectDemo
    {
    public static void main(String[]args)throws Exception
    {
    Classcls=Member.class;
    Object obj=cls.getDeclaredConstructoe().newInstance();
    Field nameFiled=cls.getDeclaredFiled("name");//获取指定名称成员属性信息
    nameFiled.setAccessible(true);//取消封装
    nameField.set(obj,"小李老师");
    System.out.pri
    }
    }

    17.6 ClassLoader

    Java程序的执行需要依靠JVM,JVM在进行类执行时会通过设置的CLASSPATH环境塑性进行指定路径的字节码文件的加载,而JVM加载字节码文件的操作就需要使用到类加载器(ClassLoader)

    17.6.1 类加载器简介

    JVM解释的程序类需要通过类加载器进行加载后才可以执行,为了保证Java程序的执行安全性,JVM提供有3种类加载器

    Bootstrap(根加载器,又称系统类加载器):由C++语言编写的类加载器,是在Java虚拟机启动后进行初始化操作,主要的目的施加在Java底层系统提供的核心类库。
    PlatformClassLoader类加载器(平台类加载器),JDK1.8之前为ExtClassLoader,使用Java编写的类加载器,主要功能是进行模块加载。
    AppClassLoader(应用程序加载器),加载CLASSPATH所制定的类文件或JAR文件
    范例:获取系统类加载器

    package cn.mldn.demo;
    public class JavaReflectDemo
    {
    public static void main(String []args)throws Exception
    {
    String str="AAA";
    System.out.println(str.getClass().getClassLoader());

    }
    }
    程序执行结果:null
    本程序获取了String类对应的类加载器信息,但是输出结果却是null,这是因为Bootstrap根加载器不是由于Java编写,所以只能返回null。

    范例:获取自定义类加载器

    package cn.mldn.demo;
    class Member()
    {

    }

    public class JavaReflectDemo
    {
    Member member=new Member();
    System.out.println(member.getClass().getClassLoader());
    System.out.println(member.getClass().getClassLoader().getParent());
    System.out.println(member.getClass().getClassLoader().getParent()).getParent);
    }

    本程序自定义了一个Member类,并且获得了该类的所有加载器,自定义类和系统类所使用的加载器是不相同的。

    提问:JVM为什么提供3类加载器
            程序定义类的目的是在JVM中使用它,那么为什么要划分出3中类加载器,如果直接设计为一个类加载器不是更方便吗
            回答:为系统安全,设置了不同级别的类加载器
            在Java装载类的时候使用的是“全盘负责委托制度”,这里面有两层含义
            全盘负责:是指当一个ClassLaoder进行类加载时,除非显示的使用了其他的类加载器,该类所依赖及引用的类也是同样的ClassLoader进行加载。
            责任委托:先委托父类加载器进行加载,在找不到父类时才由自己负责加载,并且类不会重复加载。
            这样设计的优点在于当有一个伪造系统类(假设伪造java.lang.String)出现时,利用全盘负责委托机制就可以保证java.lang.String类永远都是由Bootstrap类加载器加载,这样就保证了系统的安全,所以此类加载又称为“双亲加载”,即由不同的类加载器负责加载指定的类。

    17.6.2 自定义ClassLoader类

    除了系统提供的内置类加载器外,也可以利用继承ClassLoader的方法实现自定义类加载器的定义,本次将利用次机制实现磁盘类的加载操作。
    (1)定义一个要加载的程序类
    package cn.mldn.util;
    public class message
    {
    public void send()
    {
    System.out.println("AAA");
    }
    }

    将生成的Message.class文件保存在D盘(路径D:\Message.class),此时不要求将其保存在相应的包中。
    (2)自定义类加载器。由于需要将加载的二进制数据文件转为Class类的处理,所以可以使用ClassLoader提供的defineClass()方法实现转换
    package cn.mldn.util;
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.InputStream;
    public class MLDNClassLoader extends ClassLoader
    {
    private static final String MESSAGE_CLASS_PATH="D:"+File.separator+"Message.class";
    //定义要加载的类文件完整路径
    //进行指定类的加载操作
    //@param className类的完整名称“包.类”;
    //@return 返回一个指定类的Class对象
    //throws Exception如果类文件不存在则无法加载
    public ClassloadData(String className)throws Exception
    {
    byte[]data=this.loadClassData();//读取二进制数据文件
    if(data!=null)
    {
    return super.defineClass(className,data,0,data.length);
    }
    return null;
    }

    public byte[] loadClassData()throws Exception
    {
    InputStream input=null;
    ByteArrayOutputStream bos=null;
    byte data[]=null;
    bos=new ByteArrayOutputStream();//实例化内存流
    input=new FileInputStream(new File(MESSAGE_CLASS_PATH));//文件流加载
    input.transferTo(bos);
    data=bos.toByteArray();//字节数据取出
    input.close();
    bos.close();
    }
    }

    (3)使用自定义类加载器进行类加载并调用方法
    package cn.mldn.java;
    import java.lang.reflectMethod;
    import cn.mldn.util.MLDNClassLoader;
    public class JavaReflectDemo
    {
    public static void main(String []args)throws Exceotion
    {
    MLDNClassLoader classLoader=new MLDNClassLoader();//实例化自定义类加载器
    Classcls=classLoader.loadData("cn.mldn.util.Message");//进行类的加载
    //由于Message类并不在CLASSPATH中,所以无法直接讲对象转为Message类型,只能能反射调用
    Object obj=cls.getDeclaredConstructor().newInstance();//实例化对象
    Method method=cls.getDeclaredMethod("send");//获取方法
    method.invoke(obj);
    }
    }

    本程序利用自定义类的加载器的形式直接加载磁盘上的二进制字节码文件,并利用ClassLoader提供的defineClass()方法将二进制数据转为了Class类的实例,这样就可以利用反射进行对象实例化与方法的调用。

    提示:观察当前的类加载器
    本程序利用自定义类加载器实现了类的加载操作,此时可以观察一下类加载器的执行顺序。
    范例:观察类加载器执行顺序
    package cn.mldn.demo;
    import cn.mldn.util.MLDNClassLoader;
    public class JavaReflectDemo
    {
    public static void main(String []args)throws Exception
    {
    MLDNClassLoader classLoader=new MLDNClassLoader();
    Classvls=classLoader.loadData("cn.mldn.util.Message");
    System.out.println(cls.getClassLaoder());
    System.out.println(cls.getClassLaoder().getParent());
    System.out.println(cls.getClassLaoder().getParent().getParent());

    System.out.println(cls.getClassLaoder().getParent().getParent().getParent());
    }
    }

    17.7 反射与代理设计模式

    代理设计模式可以有效地进行真实业务和代理业务之间的拆分,让开发者可以更加专注度实现核心业务在本章所给的基础代理模式中,每一个代理类都需要为特定的一个真实业务类服务,这样就造成一个严重的问题:如果项目中有3000个接口,并且每个接口的代理操作流程类似,则需要创建3000个重复的代理类。所以在实际项目开发中,代理类的设计不应该与具体的接口产生耦合关系,而这就需要通过动态代理设计模式解决。

    17.7.1 动态代理设计模式

    动态代理设计模式的最大特点是可以同时为若干个功能相近类提供统一的代理支持,这就要求必须定义一个公共的代理类。在Java中针对此动态代理提供了一个公共的标准接口:java.lang.reflect.InvocationHandler,此接口的定义如下
    public lang.reflect.InovacationHandler
    public interface InvocationHandler
    {
    //代理操作方法。可以提供统一的代理支持
    //@param proxy 代理对象
    //@param method 要执行的目标类方法
    //@param args 执行方法所需要的参数
    //@param 方法执行结果
    //throws Throwable方法调用时产生多个异常
    public Object invoke(Object proxy,Method method,Object[]args)throws Throwable;

    }

    除了提供统一的代理操作类外,还需要类在运行依据被代理类所实现的负借口动态的创建出一个临时代理对象,而这一操作就可以通过java.lang.reflect.Proxy来来实现范例:实现动态代理设计模式
    package cn.mldn.demo;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    interface IMessage{public void send();}//传统的代理设计必须要有接口,业务方法
    class MessageReal implements IMessage{//真实实现类
    @Override
    public void send(){System.out.println("发送消息");}
    }

    class MLDNProxy implements InvocationHandler //代理类
    {
    private Object target;//真实业务对象
    //进行真实业务对象与代理业务对象之间的绑定处理
    //      @param target 真实业务对象
    //@return Proxy生成的代理业务对象
    //@return Proxy生成的代理业务对象
    public Object bind(Object target)
    {
    this.target=target;
    //依据真实对象的类加载器、实现接口以及代理调用类(InvocationHandler子类)动态创建代理对象
    return Proxy.newProxyInstance(atrget.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    public boolean connect()
    {
    System.out.println("进行消息发送通道的连接");
    return true;
    }
    public void close()
    {
    System.out.println("关闭消息通道")
    }
    @Override
    public Object invoke(Object pro,Method method,Objectp[]args)throws Throwable
    {
    Object returnData=null;//真实业务处理结果
    if(this.connect())
    {
    returnData=method.invoke(this.target,args);//调用真实业务
    this.close();
    }
    return returnData;//返回执行结果
    }
    }

    public class JavaReflectDemo
    {
    public static void main(String[]args)throws Exception
    {
    IMessage msg=(IMessage)new MLDNProxy().bind(new MessageReal());
    msg.send();
    }
    }

    本程序利用InvocationHandler接口定义了一个代理类,该代理类不与任何接口有耦合关系,并且所有的代理对象都是通过Proxy根据真实对象的结构动态创建而来。由于动态代理类具有通用性的特点,所以每当用户调用方法时都会执行代理类中的invoke方法,该方法将通过反射的方式调用真实方法。

    17.7.2 CGLIB实现动态代理设计模式

    代理设计模式是基于接口的设计,所以在官方给出的Proxy类创建代理时都需要传递该对象所有的接口信息。
            Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    但是有一部分的开发者认为不应该强迫的基于接口实现代理模式,所以他们就开发出了一个CGLIB的开发包,利用这个开发包就可以实现基于类的代理设计模式
    提示:需要进行开发包的设置
    CGLIB开发包是一个第三方包(本次使用的是cglib-nodep-3.2.9.jar文件),要想在项目中使用它,则必须将CGLIB的jar文件设置到CLASSPATH中。如果没有使用开发工具,则必须需要CLASSPATH环境属性配置,如果基于开发工具开发,则应该在开发工具中进行配置
    范例:使用CGLIB实现类代理结构
    package cn.mldn.demo;
    import java.lang.reflect.Method;
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodIntercepter;
    import net.sf.cglib.proxy.MethodProxy;
    class Message{public void send(){System.out.println("发送消息");}}
    class MLDNProxy implements MethodIntercepter
    {//代理类(方法拦截)
    parivate Object targt;//真实业务对象
    public MLDNProxy(Object target){this.target=target;}//保存真实主体对象
    public boolean connect(){System.out.println("进行消息发送通道的连接");return true;}
    public void close(){System.out.println("关闭消息通道");}
    @Override
    public Object intercept(Object proxy,Method method,Object[]args,
    MethodProxy methodProxy)throws Throwable
    {
    Object returnData=null;//真实业务处理结果
    if(this.connect())//通道是否连接
    {
    returnData=method.invoke(this.target,args);//调用真实业务
    this.close();
    }
    return returnData;//返回执行结果
    }

    }

    public class JavaReflectDemo
    {
    public static void main(String []args)throws Exception
    {
    Message realObject=new Message();
    Enhancer enhancer=new Enhancer();
    enhancer.setSuperclass(realObject.getClass());
    enhancer.setCallback(new MLDNProxy(realObject));
    Message proxyObject=(Message)enhancer.create();
    proxyObject.send();
    }
    }

    本程序在定义Message类的时候并没有让其实现任何业务接口,这就表明该操作将无法使用JDK所提供的动态代理设计模式来进行代理操作,所以只能依据定义的父类并通过CGLIB组件包模拟出动态代理设计模式的结构。

    17.8 反射与Annotation

    JDK1.5提供了很多新的特性。其中一个很重要的特性,就是对元数据(Metadata)的支持。在J2SE5.0中,这种元数据被称为注解(Annotation),通过使用注解使程序开发人员可以在不改变原有逻辑的情况下,在原文件嵌入一些补充信息。

    17.8.1 反射取得Annotation信息

    在进行类或方法定义的时候都可以使用一些列Annotation进行声明,于是如果要想获取这些Annotation的信息,那么就可以直接通过反射来完成。在java.lang.reflect里有一个AccessibleObject类(Constructor、Method、Field类的父类),提供有Annotation类的方法1

    范例:获取接口和接口子类上的Annotation信息
    package cn.mldn.demo;
    import java.io.Serializable;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    @FunctionlInterface
    @Deprecated(since="1.0")
    interface IMessage
    {
    //有两个Annotation
    public void send(String msg);

    }
    @SuppressWarnings("serial");//无法在程序执行的时候获取
    class MessageImpl implements IMessage,Serializable
    {
    @Override //无法在程序执行的时候获取
    public void send(String msg)
    {
    System.out.println("消息发送"+msg);
    }
    }

    public class JavaReflectDemo
    {
    public static void main(String[]args)throws Exception
    {
    {
    //获取接口上的Annotation信息
    Annotation annotations[]=IMessage.class.getAnnotation();
    for(Annotation temp:annotations)
    {
    System.out.println(temp);
    }
    }

    {
    //获取MessageImpl子类上的Annotation
    Annotation annotations[]=MessageImpl.class.getAnnotation();
    for(Annotation temp:annotations)
    {
    System.out.println(temp);
    }
    }
    {
    //获取MessageImpl.toString()方法上的Annotation
    Annotation annotations[]=MessageImpl.class.getDeclaredMethod("send",String.class);
    for(Annotation temp:annotations)
    {
    System.out.println(temp);
    }
    }
    }
    }
    程序执行结果
    @java.lang.FunctionalInterface()
    @java.lang.Deprecated(forRemoval=false,since="1.0");
    无法获取类上的Annotation
    无法获取类上的Annotation
    本程序主要进行接口、类、方法上的Annotation信息获取,而最终通过结果可以发现,程序最终只获得了接口上定义的两个Annotation信息,之所以无法获得某些Annotation,这主要和Annotation的定义范围有关

    在每一个Annotation定义中都可以通过Retention来对Annotation适用范围进行定义,该类是一个枚举类,有三种操作范围

    17.8.2 自定义Annotation

    除了使用系统定义的Annotation外,开发者也可以根据需要自定义Annotation。而Java中Annotation的定义需要使用@interface进行标记,同时也可以使用@Target定义Annotation的范围

    范例:自定义Annotation
    pakcage cn.mldn.demo;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Tartget;
    import java.lang.reflect.Method;
    @TARGET({ElementType.TYPE,ElementType.METHOD})//此Annotation只能用在类和方法上
    @Retention(RetentionPolicy.RUNTIME)//定义Annotation的运行策略
    @interface DfaultAnnotation//自定义Annotation
    {
    public String title();//获取数据
    public String url() default "AAA";//获取数据,提供默认值
    }

    class Message
    {
    @DefaultAnnotation(title="mldn");
    public void send(String msg){System.out.println("消息发送"+msg);}
    }

    public class JavaReflectDemo
    {
    public static void main(String[]args)throws Exception
    {
    Method methos=Message.class.getMethod("send",String.class);//获取指定方法
    DefaultAnnotation anno=method.getAnnotation(DefaultAnnotation.class);//获取指定Annotation
    String msg=anno.title()+"("+anno.url()+")";
    method.invoke(Message.class.getDeclaredConstructor().newInstance(),msg);

    }
    }

    本程序自定义DefauldAnnotation注解并为其设置了两个操作属性(title,url),由于url属性已经设置了默认值,这样在程序中执行中可以不设置气质,但是title属性必须设置具体内容

    提示:属性简化设置
    在Annotation定义中,如果其Annotation只有一个需要用户设置的必要属性时,可以使用value作为属性名称,这样在进行内容设置时可以不写属性名称。
    范例:使用默认属性名称
    @Target({ElementsType.TYPE,ElementType.METHOS})
    @Retention(RetentionPolicy.RUNTIME)
    @interface DefaultAnnotation
    {
    public String value();
    public String url() default "AAA";
    }
    class Message
    {
    //也可以使用@DefaultAnnotaion(value="mldn")
    @DefaultAnnotaion("AAA");
    public void send(String msg){System.out.println("消息发送"+msg);}
    }

    17.8.3 Annotation整合工厂设计模式

    package cn.mldn.demo;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.lang.annotation.InvocationHandler;
    import java.lang.reflect.Proxy;
    interface IMessage{public void send(String msg);}//业务接口,输出业务
    class CloudMessageImpl implements IMessage//业务接口实现子类
    {
    @Override
    public void send(String msg)
    {
    System.out.println("云消息发送"+msg);
    }
    }
    class NetMessageImpl implements IMessage
    {
    //业务接口实现子类
    @Override
    public void send(String msg){System.out.println("网络消息发送"+msg);}
    }

    class Factory
    {
    private Factory(){}
    public static T getInstance(Classclazz)//返回实例化对象
    {
    return (T)new MessageProxy().bind(clazz.getDeclaredConstructor().newInstance());
    }
    }

    @Target({ElemenType.TYPE,ElementType.METHOD})//只能用在类和方法上
    @Retention(RetentionPolicy.RUNTIME)
    @interface UseMessage{
    public Classclazz();//定义要使用的类型
    }

    @UseMessage(clazz=NetMessageImpl.class)//Annotaion定义实用类

    class MessageService
    {
    private IMessage message;//定义业务处理类
    public MessageService()
    {
    UseMessage use=MessageService.class.getAnnotation(UseMessage.class);
    this.message=(IMessage)Factory.getInstance(use.clazz));//通过Annotaiton获取
    }
    public void send(String msg)
    {
    this.message.send(msg);
    }
    }

    public MessageProxy implements InvocationHandler
    {
    //代理类
    private Objecy target;
    public Object bind(Object target)//对象绑定
    {
    this.targte=target;
    return Proxy.newProxyInstance(targte.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    public boolean connect(){...;return true;}
    public void close(){...;}
    @Override
    public Object  invoke(Object proxy,Method method,Object[]args)throws Throwable
    {
    if(this.connect()){return method.invoke(this.target,args);}
    }
    }

    finally class JavaReflectDemo
    {
    public static void main(String[]args)throws Exception
    {
    MessageService=new MessageService();//实例化接口对象
    messageService.send("AAA");
    }
    }
     

  • 相关阅读:
    二刷力扣--数组
    01-SpringMVC项目构建
    【Python自学笔记】python os.getcwd文件目录找不对
    Java 21 新特性:Unnamed Patterns and Variables
    zimo221软件和PCtoLCD2002软件的使用
    TNF6TOA TNF5TOA 全新板卡8路任意速率业务支路处理板
    Html飞机大战(十七): 优化移动端
    tqdm高级使用方法(类keras进度条)
    .NET开源且免费的Windows远程桌面管理软件
    树莓派3 虚拟机部署streamlit流程 2022
  • 原文地址:https://blog.csdn.net/qq_52299902/article/details/134186376