第17章 反射机制
通过本章需要理解反射机制操作的意义以及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 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实例
Class<?>clazz=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
{
Class<?>clazz=Member.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<?>class=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
{
Class<?>clazz=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
{
Class<?>clazz=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> T getInstance(String className,Class<T>clazz)
{
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 | 方法 | 类型 | 描述 |
1 | public Package getPackage() | 普通 | 获取包信息 |
2 | public Class<?super T>getSuperclass() | 普通 | 获取继承父类 |
3 | public 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<?>cls=Mail.class;//获取指定类的Class对象
Package pack=cls.getPakage();//获取指定类的保定易
System.out.println(pack.getName());//获取包名称
Class<?>parent=cls.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.msg=msg;
}
@Override
public String toString()
{
return "消息内容"+this.msg;
}
}
public class JavaReflectDemo
{
public static void main(String []args)throws Exception
{
Class<?>cls=Mail.class;
Contructor<?>[]constructors=cls.getDeclaredContructors();
for(Constructor<?>cons:constructors){System.out.println(cons);}
//获取单参构造并且参数类型为String的构造方法对象实例
Constructor<?>cons=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
{
Class<?>cls=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()+"(");
Class<?>params[]=met.getParameterTypes();//获取参数类型
for(int x=0;x<params.length;x++)
{
System.out.println(params[x].getName()+" "+"arg-"+x);
if(x<params.length-1){
System.out.print(",");
}
}
System.out.println(")");
Class<?>exp[]=met.getExceptionTypes();//获取异常信息
if(exp.length>0){System.out.print("throws");}
for(int x=0;x<exp.length;x++)
{
System.out.print(exp[x].getName());
if(x<exp.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.name=name;
}
public String getName(){return name;}
}
public class JavaReflectDemo
{
public static void main(String[]args)throws Exception
{
Class<?>cls=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
{
Class<?>cls=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
{
Class<?>cls=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 Class<?>loadData(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();//实例化自定义类加载器
Class<?>cls=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();
Class<?>vls=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> T getInstance(Class<T>clazz)//返回实例化对象
{
return (T)new MessageProxy().bind(clazz.getDeclaredConstructor().newInstance());
}
}
@Target({ElemenType.TYPE,ElementType.METHOD})//只能用在类和方法上
@Retention(RetentionPolicy.RUNTIME)
@interface UseMessage{
public Class<?>clazz();//定义要使用的类型
}
@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");
}
}
相关文章:

第17章 反射机制
通过本章需要理解反射机制操作的意义以及Class类的作用,掌握反射对象实例化操作,并且可以深刻理解反射机制与工厂模式结合意义。掌握类结构反射操作的实现,并且可以通过反射实现类中构造方法、普通方法、成员属性的操作。掌握反射机制与简单J…...

如何在在线Excel文档中对数据进行统计
本次我们将用zOffice表格的公式与数据透视表分析样例(三个班级的学生成绩)。zOffice表格内置了大量和Excel相同的统计公式,可以进行各种常见的统计分析,如平均值、标准差、相关性等。同时,zOffice也有数据透视表功能&a…...

redis配置文件详解
一、配置文件位置 以配置文件启动 Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf ( Windows名为redis.windows. conf) 例: # 这里要改成你自己的安装目录 cd ./redis-6.0.8 vim redis.conf redis对配置文件对大小写不敏感 二、配置文件 1、获取当前服务的…...
前端设计模式之【工厂模式】
文章目录 前言什么时候不用介绍工厂模式的流程例子优点缺陷后言 前言 hello world欢迎来到前端的新世界 😜当前文章系列专栏:前端设计模式 🐱👓博主在前端领域还有很多知识和技术需要掌握,正在不断努力填补技术短板。…...

Python与ArcGIS系列(一)ArcGIS中使用Python
目录 0 简述1 arcgis中的python窗口2 开始编写代码 0 简述 按照惯例,作为本系列专栏的第一篇,先简单地介绍下本系列文章的内容:通过python语言创建arcgis环境脚本、将脚本以工具箱形式存放在arcgis中、通过脚本自动执行地理处理、数据修复、…...

LeetCode(2)移除元素【数组/字符串】【简单】
目录 1.题目2.答案3.提交结果截图 链接: 27. 移除元素 1.题目 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原…...

原型模式(创建型)
一、前言 原型模式是一种创建型设计模式,它允许在运行时通过克隆现有对象来创建新对象,而不是通过常规的构造函数创建。在原型模式中,一个原型对象可以克隆自身来创建新的对象,这个过程可以通过深度克隆或浅克隆来实现。简单说原型…...
Linux命令(118)之paste
linux命令之paste 1.paste介绍 linux命令paste命令是把每个文件以列对列的方式,一列列地加以合并 2.paste用法 paste [参数] filename... paste参数 参数说明-d使用指定的分隔符进行合并-s以行来指定文件 3.实例 3.1.使用冒号(:)合并文件 命令: …...
使用零拷贝技术实现消息转发功能
零拷贝技术介绍:史上最全零拷贝总结-CSDN博客 这是一个简单的基于epoll的Linux TCP代理程序,通过匿名管道和零拷贝技术的splice函数,将两个TCP端口相互连接,并转发数据。 #define _GNU_SOURCE 1 #include <sys/socket.h> …...

【编程语言发展史】SQL的发展历史
目录 目录 SQL概述 SQL发展历史 SQL特点 SQL基本语句 SQL是结构化查询语言(Structure Query Language)的缩写,它是使用关系模型的数据库应用语言,由IBM在70年代开发出来,作为IBM关系数据库原型System R的原型关系语言,实现了…...
2023NOIP A层联测28-小猫吃火龙果
给你一个长为 n n n 的序列,每个位置是 A , B , C A,B,C A,B,C 三个中的一个物品。 A A A 吃 B B B, B B B 吃 C C C, C C C 吃 A A A。 现在有 m m m 次操作,每次操作有两种: 区间修改:给出 l , r…...

C# Dictionary与List的用法区别与联系
C#是一门广泛应用于软件开发的编程语言,其中Dictionary和List是两种常用的集合类型。它们在存储和操作数据时有着不同的特点和用途。本文将详细探讨C# Dictionary和List的用法区别与联系,并通过代码示例进行对比,以帮助读者更好地选择适合自己…...

Git应用(1)
一、Git Git(读音为/gɪt/。中文 饭桶 )是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。 了解更多可到GIT官网:Git - Downloads GIT一般工作流程如下: 1.从远程仓库中克隆 Git 资源作为本地…...

【Java】Netty创建网络服务端客户端(TCP/UDP)
😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍Netty创建网络服务端客户端示例。 学其所用,用其所学。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下次更…...
Android 设计模式--单例模式
一,定义 单例模式就是确保某一个类只有一个实例,而且自行实例化,并向整个系统提供这个实例 二,使用场景 确保某个类只有一个对象的使用场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有…...

语音识别与自然语言处理(NLP):技术前沿与未来趋势
语音识别与自然语言处理(NLP):技术前沿与未来趋势 随着科技的快速发展,语音识别与自然语言处理(NLP)技术逐渐成为人工智能领域的研究热点。这两项技术的结合,使得机器能够更好地理解和处理人类语…...

k8s-docker二进制(1.28)的搭建
二进制文件-docker方式 1、准备的服务器 角色ip组件k8s-master1192.168.11.111kube-apiserver,kube-controller-manager,kube-scheduler,etcdk8s-master2192.168.11.112kube-apiserver,kube-controller-manager,kube-scheduler,etcdk8s-node1192.168.11.113kubelet,kube-prox…...

【代码随想录】算法训练计划18
1、513. 找树左下角的值 题目: 给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 思路: 递归,规则,基本可以自己写出来 var maxDepth int var res int fun…...

Leetcode刷题详解—— 组合总和
1. 题目链接:39. 组合总和 2. 题目描述: 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些…...

Echarts柱状体实现滚动条动态滚动
当我们柱状图中X轴数据太多的时候,会自动把柱形的宽度挤的很细,带来的交互非常不好,因此就有一个属性来解决:dataZoom 第一种简易的版本,横向滚动。 dataZoom: {show: true, // 为true 滚动条出现realtime: true, // 实…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...