装修网站设计需求说明分析下载文档,那些做网站的那些软件都叫啥,二次开发接口,企业网站用什么套站通过本章需要理解反射机制操作的意义以及Class类的作用#xff0c;掌握反射对象实例化操作#xff0c;并且可以深刻理解反射机制与工厂模式结合意义。掌握类结构反射操作的实现#xff0c;并且可以通过反射实现类中构造方法、普通方法、成员属性的操作。掌握反射机制与简单J…通过本章需要理解反射机制操作的意义以及Class类的作用掌握反射对象实例化操作并且可以深刻理解反射机制与工厂模式结合意义。掌握类结构反射操作的实现并且可以通过反射实现类中构造方法、普通方法、成员属性的操作。掌握反射机制与简单Java类之间的操作关联掌握类加载起的作用并且可以实现自定义类加载器掌握动态代理机制的实现结构并理解CGLIB开发包的作用掌握Annotation定义并且可以结合反射机制实现配置管理。 反射机制是java语言提供的一项重要技术支持也是Java区别于其他语言并且迅速发展的重要特征利用反射机制可以帮助开发者编写更为灵活与高可用的代码结构。本章将完整的分析反射机制中的各个组成部分并且重点分析反射机制与工程模式以及代理设计模式的重要关联。
17.1 认识反射机制
重用性是面向对象设计的核心原则。为了进一步提升代码的重用性Java提供了反射机制。反射机制首先考虑的是“正”“反”的操作所谓的“正”操作是指当开发者使用一个类的时候一定要先导入程序所在的包而后根据类进行对象实例化并且依靠对象调用类中的方法而所谓的反操作是指可以根据实例化对象反推出器类型。 Class类是反射机智的根源可以通过Object类中所提供的方法获取一个Class实例。 获取Class实例化对象:public final Class?getClass().
范例获取反射信息
package cn.mldn.demo; import java.util.Date; public class JavaReflectDemo { public static void main(String[]args)throws Exception { Date datenew 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 membernew Member();//操作特点需要获取一个类的实例化独享后才可以获得Class实例 Class?clazzmember.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 { Class?clazzMember.class;//直接通过一个类的完整名称可以获取Class类实例需要编写import或完整类名称。 System.out.println(clazz); } }
执行结果:class cn.mldn.demo.Member
本程序利用JVM的支持方式通过一个类的直接获取了Class实例化对象。
方式3使用Class类内部提供的forName()方法根据类的完整名称获取实例化对象 Class实例化方法public static Class?forName(String className)throws ClassNotFoundException
package cn.mldn.demo; class Member { public class JavaReflectDemo {
//操作特点通过名称字符串(包.类)可以获取Class类实例可以不适用import导入 Class?classClass.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 { Class?clazzClass.forName(cn.mldn.demo.Member); Object objclazz.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 { Class?clazzClass.forName(cn.mldn.demo.Member); Object objclazz.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 T getInstance(String className,ClassTclazz) { T instancenull; instance(T)class.forName(className).getDeclaredContructor().newInstance(); } }
public class JavaReflectDemo { pubic static void main(String[]args)throws Exception { IMessage msgFactory.getInstance(cn.mldn.demo.NetMessage,IMessage.class); msg.send(); } }
执行结果网络消息 本程序实现了一个全新的并且可用工厂类结构为了让该工厂类适合于所有类型程序中结合反射机制与反省获取指定类型的实例这样可以避免向下转型所带来的安全隐患
17.3.3 反射与单例模式 单例模式设计的核心本质在于类内部的构造方法私有化在类的内部产生实例化对象之后通过static方法获取实例化对象进行类中的结构调用。单例模式一共有两类懒汉设计模式和饿汉是。饿汉式的单利由于其再累加载的时候就已经进行了对象实例化处理所以不涉及多线程的访问问题但是懒汉式单例模式在多线程访问下却有可能出现多个实例化对象的产生问题。
范例观察懒汉式但力涉及与多线程访问
package cn.mldn.demo; class Gingleton { public static Singleton instancenull; private Singleton(){System.out.println(Thread.currentThread().getName()实例化Singleton对象);} public static Singleton getInstance() { if(instancenull){instancenew Singleyon();} return instance; }
public void print(){System.out.println(实例化Singleton);} }
public class JavaReflectDemo { public static void main(String[]args)throws Exception { for(int x0;x3;x) { new Thread(()- { Singleton.getInstance().print(); },单例消费端-x).start(); } } }
程序执行结果: 【单例消费端-2】实例化Singleton 【单例消费端-1】实例化Singleton 【单例消费端-0】实例化Singleton
单例模式的核心在于Singleton类只允许有一个实例化对象然而通过本程序的执行可以发现此时产生了多个实例而这一操作的根源来源于多线程访问不同步即有多个线程独享在第一次使用时都产生了多个实例化对象而这一操作的根源在于多线程访问不同步即有多个向成都向在第一次使用时都通过了实例化对象的判断语句(if(instancenull)).所以此时只能用synchronized来进行同步处理。
范例解决懒汉单例模式中的多线程访问不同步问题修改Singleton.getinstance()方法定义 public static Singleton getInstance() { if(instancenull) { synchronized(Singleton class) { if(instancenull){instancenew Singleton();} } } return instance; }
本程序利用同步代码块的形式对Singleton类实例化对象与实例化操作进行了判断这样就保证了多线程模式下只能存在一个Singleton的实例化对象
提问:关于synchronized同步处理的位置 对于多线程的并发访问下的同步操作为什么不直接在getinstance()方法定义上使用synchronized关键字定义 public static synchronized Singleton getInstance() { if(instancenull){instancenew Singleton();} return instance(); }
此时的代码执行后也可以实现正常的懒汉式单例模式为什么本代码中却要使用同步代码块又在同步代码块中多增加一次instance是否实例化的判断呢
回答:在保证性能的同时需要提供同步支持 synchronied的作用在于为指定范围的代码追加一把同步锁如果直接在getInstance()方法上定义虽然可以同步处理Singleton类对象实例化操作但必然造成多线程并发执行效率缓慢所以利用同步带吗来解决。 实际上在本程序中只要保证instance对象是否被实例化的判断进行同步处理即可所以使用同步代码块进行instance()对象实例化的判断与处理。 17.4 反射机制与类操作
Java反射机制可以在程序运行状态下自动获取并调用任意一个类中的组成结构(成员塑性、方法等)这样的做法可以避免单一的程序调用模式使代码开发变得更加灵活。 17.4.1 反射获取类结构信息
程序开发中任何定义的类都需要存在继承关系同时为了代码结构的清晰也应该利用包保存不同功能的类可以利用下表所示方法获取类的相关信息
No方法类型描述1public Package getPackage()普通获取包信息2public Class?super TgetSuperclass()普通获取继承父类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 { Class?clsMail.class;//获取指定类的Class对象 Package packcls.getPakage();//获取指定类的保定易 System.out.println(pack.getName());//获取包名称 Class?parentcls.getSuperclass();//获取父类对象 System.out.println(parent.getName());//父类信息 System.out.println(parent.getSuperclass().getName());//父类信息 Class?clazz[]cls.getInterfaces();//获取接口信息 for(Class?temp: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.msgmsg; }
Override public String toString() { return 消息内容this.msg; } }
public class JavaReflectDemo { public static void main(String []args)throws Exception { Class?clsMail.class; Contructor?[]constructorscls.getDeclaredContructors(); for(Constructor?cons:constructors){System.out.println(cons);} //获取单参构造并且参数类型为String的构造方法对象实例 Constructor?conscls.getDeclaredConstructor(String.class); Object objcons.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 { Class?clsMail.class; Method methord[]cls.getMethords(); for(Method metmethods) { int modmet.getModifier(); System.out.print(Modifier.toString(mod)); System.out.print(met.getReturnType().getName()); System.out.print(met.getName()(); Class?params[]met.getParameterTypes();//获取参数类型 for(int x0;xparams.length;x) { System.out.println(params[x].getName() arg-x); if(xparams.length-1){ System.out.print(,); } }
System.out.println()); Class?exp[]met.getExceptionTypes();//获取异常信息 if(exp.length0){System.out.print(throws);} for(int x0;xexp.length;x) { System.out.print(exp[x].getName()); if(xexp.length-1) { 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.namename; } public String getName(){return name;} }
public class JavaReflectDemo { public static void main(String[]args)throws Exception { Class?clsMember.class; String value小李;//设置内容 //通过反射实例化才可以调用类中的成员属性和方法 Object objcls.getDeclaredConstructor().newInstance();//调用无参构造实例化 //反射调用方法需要明确地指导方法的名称以及方法中的参数类型 String setMethodNamesetName; Method setMethodcls.getDeclaredMethod(setMethodName,String.class); setMethod.invoke(obj,value); String getMethodNamegetName; Method getMethodcls.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 NAMEmldnjava; } abstract class AbstractBase { protected static finale String BASEQQQ; private String infohello; } class Member extends AbstractBase implements IChannelService { privaye String name; private int age; }
public class JavaReflectDemo { public static void main(String[]args) throws Exception { Class?clsMember.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 { Class?clsMember.class; Object objcls.getDeclaredConstructoe().newInstance(); Field nameFiledcls.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 strAAA; System.out.println(str.getClass().getClassLoader()); } } 程序执行结果null 本程序获取了String类对应的类加载器信息但是输出结果却是null这是因为Bootstrap根加载器不是由于Java编写所以只能返回null。
范例获取自定义类加载器
package cn.mldn.demo; class Member() { }
public class JavaReflectDemo { Member membernew 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_PATHD:File.separatorMessage.class; //定义要加载的类文件完整路径 //进行指定类的加载操作 //param className类的完整名称“包.类”; //return 返回一个指定类的Class对象 //throws Exception如果类文件不存在则无法加载 public Class?loadData(String className)throws Exception { byte[]datathis.loadClassData();//读取二进制数据文件 if(data!null) { return super.defineClass(className,data,0,data.length); } return null; }
public byte[] loadClassData()throws Exception { InputStream inputnull; ByteArrayOutputStream bosnull; byte data[]null; bosnew ByteArrayOutputStream();//实例化内存流 inputnew FileInputStream(new File(MESSAGE_CLASS_PATH));//文件流加载 input.transferTo(bos); databos.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 classLoadernew MLDNClassLoader();//实例化自定义类加载器 Class?clsclassLoader.loadData(cn.mldn.util.Message);//进行类的加载 //由于Message类并不在CLASSPATH中所以无法直接讲对象转为Message类型只能能反射调用 Object objcls.getDeclaredConstructor().newInstance();//实例化对象 Method methodcls.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 classLoadernew MLDNClassLoader(); Class?vlsclassLoader.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.targettarget; //依据真实对象的类加载器、实现接口以及代理调用类(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 returnDatanull;//真实业务处理结果 if(this.connect()) { returnDatamethod.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.targettarget;}//保存真实主体对象 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 returnDatanull;//真实业务处理结果 if(this.connect())//通道是否连接 { returnDatamethod.invoke(this.target,args);//调用真实业务 this.close(); } return returnData;//返回执行结果 } }
public class JavaReflectDemo { public static void main(String []args)throws Exception { Message realObjectnew Message(); Enhancer enhancernew 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(since1.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(forRemovalfalse,since1.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(titlemldn); public void send(String msg){System.out.println(消息发送msg);} }
public class JavaReflectDemo { public static void main(String[]args)throws Exception { Method methosMessage.class.getMethod(send,String.class);//获取指定方法 DefaultAnnotation annomethod.getAnnotation(DefaultAnnotation.class);//获取指定Annotation String msganno.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(valuemldn) 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 staticT T getInstance(ClassTclazz)//返回实例化对象 { return (T)new MessageProxy().bind(clazz.getDeclaredConstructor().newInstance()); } }
Target({ElemenType.TYPE,ElementType.METHOD})//只能用在类和方法上 Retention(RetentionPolicy.RUNTIME) interface UseMessage{ public Class?clazz();//定义要使用的类型 }
UseMessage(clazzNetMessageImpl.class)//Annotaion定义实用类
class MessageService { private IMessage message;//定义业务处理类 public MessageService() { UseMessage useMessageService.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.targtetarget; 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 { MessageServicenew MessageService();//实例化接口对象 messageService.send(AAA); } }