第17章 反射机制

news2024/11/25 18:44:32

通过本章需要理解反射机制操作的意义以及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方法类型描述
1public Package getPackage()普通获取包信息
2public Class<?super T>getSuperclass()普通获取继承父类
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<?>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");
}
}
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1186637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

19.13 Boost Asio 发送TCP流数据

Boost框架中默认就提供了针对TCP流传输的支持&#xff0c;该功能可以用来进行基于文本协议的通信&#xff0c;也可以用来实现自定义的协议。一般tcp::iostream会阻塞当前线程&#xff0c;直到IO操作完成。 首先来看服务端代码&#xff0c;如下所示在代码中首先通过GetFileSize…

2023.11.08 homework

小学五年级数学

Vim编辑器学习

B站学习vim指令链接 1&#xff1a;vim下有两种模式&#xff0c;一种是命令模式&#xff0c;一种是编辑模式 2&#xff1a;命令到编辑模式&#xff0c;按键盘i&#xff0c;编辑到命令格式按Esc 3&#xff1a;&#xff1a;wq 保存并退出 &#xff1a;wq code.c保存并把文件命名为…

【机器学习基础】机器学习概述

目录 前言 一、机器学习概念 二、机器学习分类 三、机器学习术语 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x…

Mysql数据库 11.SQL语言 储存过程 下 储存过程管理和游标

一、存储过程管理 1.查询存储过程 查询所有储存过程 语法 show procedure status; 代码实现 #查询存储过程 show procedure status; 运行结果 加入条件查询储存过程 语法 show procedure status where db储存过程名; 代码实现 #查询带有条件的储存过程 查询名字为pro…

第五届泰迪杯数据分析技能赛B题源码图片分享

需要B题源码以及第六届带队”指导“请私信本人&#xff0c;团队包含技能赛双一等&#xff0c;数学建模省一&#xff0c;泰迪杯挖掘国一&#xff0c;研究生队友。 去年一等作品可视化图如下&#xff0c;私信获取源码

clickhouse通过java jdbc实现增删改查,保姆级教程

一、clickhouse是一款开源的用于在线分析处理查询(OLAP :Online Analytical Processing)MPP架构的列式存储数据库。 二、clickhouse可以做用户行为分析&#xff0c;流批一体 三、我们现在用java通过jdbc的方式来操作clickhouse 四、先安装clickhouse&#xff0c;安装资料自行…

长春理工大学漏洞报送证书

获取来源&#xff1a;edusrc&#xff08;教育漏洞报告平台&#xff09; url&#xff1a;主页 | 教育漏洞报告平台 兑换价格&#xff1a;10金币 获取条件&#xff1a;提交长春理工大学任意中危或以上级别漏洞

尚硅谷大数据项目《在线教育之实时数仓》笔记007

视频地址&#xff1a;尚硅谷大数据项目《在线教育之实时数仓》_哔哩哔哩_bilibili 目录 第9章 数仓开发之DWD层 P053 P054 P055 P056 P057 P058 P059 P060 P061 P062 P063 P064 P065 第9章 数仓开发之DWD层 P053 9.6 用户域用户注册事务事实表 9.6.1 主要任务 读…

kafka笔记要点和集群安装、消息分组、消费者分组以及与storm的整合机制

kafka笔记 1/kafka是一个分布式的消息缓存系统 2/kafka集群中的服务器都叫做broker 3/kafka有两类客户端&#xff0c;一类叫producer&#xff08;消息生产者&#xff09;&#xff0c;一类叫做consumer&#xff08;消息消费者&#xff09;&#xff0c;客户端和broker服务器之间…

SAP BASIS SET_PARAMETER_ID_TOO_LONG

ji 原因 DATA:curvbelnid(40) TYPE c,"问题在这里curposnrid(40) TYPE c. "问题在这里curvbelnid sy-uname && VN.curposnrid sy-uname && PR.SET PARAMETER ID curvbelnid FIELD i_vbeln . SET PARAMETER ID curposnrid FIELD i_posnr . 改成 D…

【Proteus仿真】【STM32单片机】汽车尾灯控制设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用按键、LED模块等。 主要功能&#xff1a; 系统运行后&#xff0c;系统运行后&#xff0c;系统开始运行&#xff0c;K1键控制左转向灯&#xff…

web前端-Gulp入门

web前端-Gulp入门 gulp的概述使用gulp准备工作gulp的常用APIgulp的常用插件gulpfile.js的初体验打包css文件打包scss文件打包js打包html打包images创建一个默认任务创建一个删除任务gulp启动服务创建一个监控任务 gulp的概述 gulp&#xff1a; 前端自动化打包固件工具&#xf…

uniapp在不需要后端数据的情况下 怎么记录用户进一次记录一次

目录 前言&#xff1a; html部分 js部分 完整代码 前言&#xff1a; 一时兴起&#xff0c;不喜勿喷&#xff0c;今天听到了这个问题想到了一个方法&#xff0c;解决方式如下。 html部分 他用于显示访问次数&#xff08;visitCount变量的值&#xff09;。 <template&…

Day24力扣打卡

打卡记录 寻找峰值&#xff08;二分法&#xff09; class Solution { public:int findPeakElement(vector<int> &nums) {int left -1, right nums.size() - 1; // 开区间 (-1, n-1)while (left 1 < right) { // 开区间不为空int mid left (right - left) / …

【Vue】vant2使用van-tree-select实现【全选、反选、搜索】,自定义组件,拿去即用。2.0版本保姆级教程

系列文章目录 这是原篇教程&#xff0c;本篇为升级版&#xff0c;旧版已废弃。对你们不友好。 【Vue】vue2移动端 &#xff0c;vant2使用van-tree-select分类选择实现【全选】和【取消全选】、【搜索过滤当前children】&#xff0c;只影响当前显示children&#xff0c;并且去重…

clickhouse.22.8.3.13单机版安装

介绍 1、clickhouse是一款优秀的开源MPP数据库。 安装ClickHouse的步骤如下&#xff1a; 2、下载clickhouse https://repo.clickhouse.tech/tgz/ 但是这个下载太慢了&#xff0c;找个国内的镜像 https://mirrors.aliyun.com/clickhouse/ 我们采用阿里云的镜像地址。 cli…

An error occurred while filtering resources

Description Path Resource Location Type An error occurred while filtering resources PMS line 1 Maven Java EE Configuration Problem不知道怎么跑出来了&#xff0c;update project 还是不行 但是不影响运行&#xff0c;奇…

记录两个Excel导出出现的问题

问题一&#xff1a;导出数据时&#xff0c;这行代码返回null&#xff0c;导致导出excel失败&#xff1b; Workbook workbook ExcelExportUtil.exportExcel(params, map);解决&#xff1a;排查出来&#xff0c;是因为版本问题&#xff0c;autopoi版本是1.2.1&#xff1b; 升级…

MCU系统的调试技巧

MCU系统的调试技巧对于确保系统稳定性和性能至关重要。无论是在嵌入式系统开发的初期阶段还是在产品维护和优化的过程中&#xff0c;有效的调试技巧可以帮助开发人员快速发现和解决问题&#xff0c;本文将讨论一些MCU系统调试的技巧。 首先&#xff0c;使用调试工具是非常重要…