帮建网站的人/上海网优化seo公司
一、反射概述
Java Reflection
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Java反射机制提供的功能
Ø 在运行时判断任意一个对象所属的类
Ø 在运行时构造任意一个类的对象
Ø 在运行时判断任意一个类所具有的成员变量和方法
Ø 在运行时调用任意一个对象的成员变量和方法
Ø 生成动态代理
看两个范例
范例一:
//在有反射以前,如何创建一个类的对象,并调用其中的方法、属性
@Testpublic void test1(){Person p = new Person();p.setAge(10);p.setName("TangWei");System.out.println(p);p.show();p.display("HK");}
范例二:
//有了反射,可以通过反射创建一个类的对象,并调用其中的结构
@Test
public void test2() throws Exception{
Class clazz= Person.class;
//1.创建clazz对应的运行时类Person类的对象
Person p = (Person)clazz.newInstance();
//2.通过反射调用运行时类的指定的属性
//2.1 调用的是public String name;公共的属性。
Field f1 = clazz.getField("name"); //获取Person的属性,下面设值。不管属性里面是什么类型的,都要用""双引号包着。
f1.set(p, "LiuDeHua"); //给属性设置,第一参数代表哪个对象设值,第二个代表值。
System.out.println(p);
//结果:Person [name=LiuDeHua, age=0]
//2.2 获取私有的属性并赋值:private int age;
//通过getDeclaredField()方法获取私有的。
Field f2 = clazz.getDeclaredField("age"); //获取的方法和获取私有的方法不一样
f2.set(p, "20");
java.lang.IllegalAccessException:报异常:非法访问。
System.out.println(p); //结果:Person [name=LiuDeHua, age=0]
//需要设置访问为true。
f2.setAccessible(true);
f2.set(p, 20);
System.out.println(p); //结果:Person [name=LiuDeHua, age=20]
3.通过反射调用运行时类的指定的方法
//3.1调用无参的方法:getMethod(String name, Class... parameterTypes):第一个参数写方法名,第二个无参则后面的去掉。
Methodm1 = clazz.getMethod("show");
//调用方法:invoke(Object obj, Object... args):第二个参数是用来传参的,没有就不写。
m1.invoke(p); //就是方法的调用,等于p.show()
/*public void show(){
System.out.println("我是一个人");
}*/
//结果:我是一个人
//3.2调用有参的方法:getMethod(String, Class...):第二个参数也要是Class类型的,所以是String.class。
Method m2 = clazz.getMethod("display",String.class);
m2.invoke(p, "China");
/*public void display(String nation){
System.out.println("我的国籍是:" + nation);
}*/
// 结果:我的国籍是: China二、理解反射的源头之Class类
Person.java
package me.gorden.reflection;public class Person {public String name;private int age;public void show(){System.out.println("show()无参方法");}public void display(String param){System.out.println("display(param)有参数方法");}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + "]";}}
2.1、Class 类
在Object类中定义了以下的方法,此方法将被所有子类继承:
public final Class getClass()
以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。
java.lang.Class:是反射的源头。
我们创建了一个类,通过编译(javac.exe),生成对应的.class文件。之后我们使用java.exe加载(JVM的类加载器完成的)
此.class文件,此.class文件加载到内存以后,就是一个运行时类,存在在缓存区。那么这个运行时类本身就是一个Class的实例!
1.每一个运行时类只加载一次!
2.有了Class的实例以后,我们才可以进行如下的操作:
1)*创建对应的运行时类的对象
2)获取对应的运行时类的完整结构(属性、方法、构造器、内部类、父类、所在的包、异常、注解、...)
3)*调用对应的运行时类的指定的结构(属性、方法、构造器)
4 )反射的应用:动态代理范例:getClass()方法的使用:得到完整的“包类”名称
@Testpublic void demo3(){Person person = new Person();//通过运行时类的对象,调用其getClass(),返回其运行时类。Class clazz = person.getClass();System.out.println(clazz);//结果:class me.gorden.reflection.Person}
2.2获取Class类的实例的4种方式
@Testpublic void demo4() throws ClassNotFoundException{//第一种:1.调用运行时类本身的.class属性Class clazz1 = Person.class;//getName():得到类名System.out.println(clazz1.getName());//结果:me.gorden.reflection.Person//第二种:2.通过运行时类的对象获取 Person person = new Person();Class clazz2 = person.getClass();System.out.println(clazz2.getName());//结果:me.gorden.reflection.Person//第三种:3.通过Class的静态方法获取.通过此方式,体会一下,反射的动态性。String className = "me.gorden.reflection.Person";Class clazz3 = Class.forName(className);System.out.println(clazz3.getName());//结果:me.gorden.reflection.Person//第四种:4.通过类的加载器ClassLoader classLoader = this.getClass().getClassLoader();Class clazz4 = classLoader.loadClass(className);System.out.println(clazz4.getName());//结果:me.gorden.reflection.Person}
2.3了解类的加载器ClassLoader
了解:ClassLoader
类加载器是用来把类(class)装载进内存的。JVM 规范定义了两种类型的类加载器:启动类加载器(bootstrap)和用户自定义加载器(user-defined class loader)。 JVM在运行时会产生3个类加载器组成的初始化加载器层次结构 ,如下图所示:1.获取一个系统类加载器
ClassLoader classLoader1 = ClassLoader.getSystemClassLoader();System.out.println(classLoader1);//结果:sun.misc.Launcher$AppClassLoader@15253d5
2.
获取系统类加载器的父类加载器,即扩展类加载器
ClassLoader classLoader2 = classLoader1.getParent();System.out.println(classLoader2);//结果:sun.misc.Launcher$ExtClassLoader@1fddc31
3.
获取扩展类加载器的父类加载器,即引导类加载器。无法直接获取,所以返回
null.
ClassLoader classLoader3 = classLoader2.getParent();System.out.println(classLoader3);//结果:null
4.测试当前类由哪个类加载器进行加载。自定义的类是由系统类加载器。
ClassLoader classLoader4 = Class.forName("me.gorden.reflection.Person").getClassLoader();System.out.println(classLoader4);//结果:sun.misc.Launcher$AppClassLoader@15253d5
5.
测试
JDK
提供的
Object
类由哪个类加载器加载
ClassLoader classLoader5 = Class.forName("java.lang.Object").getClassLoader();System.out.println(classLoader5);//结果:null
*6.
关于类加载器的一个主要方法:
getResourceAsStream(String str):
获取类路径下的指定文件的输入流
方法一:在哪个包中:格式如下
ClassLoader loader = this.getClass().getClassLoader();InputStream is = loader.getResourceAsStream("me\\gorden\\reflection\\jdbc.properties");
方法二:在当前工程下
:
格式如下
FileInputStream fis = new FileInputStream(new File("jdbc1.properties"));Properties prop = new Properties();prop.load(fis);String username = prop.getProperty("username");
三、创建运行时类的对象
3.1 创建类对象并获取类的完整结构
有了Class对象,能做什么?
创建类的对象:调用Class对象的newInstance()方法
要 求:
1)类必须有一个无参数的构造器。
2)类的构造器的访问权限需要足够。
难道没有无参的构造器就不能创建对象了吗?
不是!只要在操作的时候明确的调用类中的构造方法,并将参数传递进去之后,才可以实例化操作。
步骤如下:
1)通过Class类的getDeclaredConstructor(Class… parameterTypes)取得本类的指定形参类型的构造器
2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
3)通过Constructor实例化对象。
范例一:创建对象Class clazz = Class.forName("me.gorden.reflection.Person");/*创建对应的运行时类的对象。使用newInstance(),实际上就是调用了运行时类的空参的构造器。要想能够创建成功:①要求对应的运行时类要有空参的构造器。②构造器的权限修饰符的权限要足够。*/Person person = (Person) clazz.newInstance();System.out.println(person);//结果:Person [name=null, age=0]
3.2通过反射获取类的完整结构1_属性&方法
1.getFields(): 只能获取到 运行时类中及其父类中声明为public的属性 , 私有的其他的获取不到 。Class clazz = Person.class;Field[] fields = clazz.getFields();for (Field field : fields) {System.out.println(field.getName());}//结果:name
2.getDeclaredFields():
获取运行时类本身声明的所有的属性,包括私有的
。
Class clazz = Person.class;Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {System.out.println(field.getName());}//结果:name age
3.获取每个属性的权限修饰符
权限修饰符 变量类型变量名
获取属性的各个部分的内容Class clazz = Person.class;Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {//因为获取的是一个int类型的,所以需要调用Modifier类的方法。才能获取修饰符名称。int i = field.getModifiers();//Modifier对类和成员访问修饰符进行解码String str = Modifier.toString(i);System.out.println(str + "=="+field.getName());//权限修饰符+属性名}/*结果:public==nameprivate==age*/
4.
getType()获取属性的类型
Class clazz = Person.class;Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {Class type = field.getType();System.out.println("属性类型:"+type);}/**属性类型:class java.lang.String属性类型:int*/
5.获取对应的运行时类的方法
Class clazz = Person.class;//1、getMethods():获取运行时类及其父类中所有的声明为public的方法Method[] methods = clazz.getMethods();for (Method method : methods) {System.out.println(method.getName());}
结果:
Object类大纲
Class clazz = Person.class;//2.getDeclaredMethods():获取运行时类本身声明的所有的方法Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {System.out.println(method.getName());}
结果:
还可以获取以下内容:
1.getAnnotations() 注解
2.getReturnType() 返回值类型
3.getParameterTypes() 形参列表
4.getExceptionTypes() 异常类型
5.getConstructors():返回此 Class 对象所表示的类的所有public构造方法
6.getInterfaces():获取实现的接口
7.getPackage()获取所在的包
四、通过反射调用类中的指定方法、指定属性
1.调用指定方法
通过反射,调用类中的方法,通过Method类完成。步骤:
1.通过Class类的getMethod(Stringname,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
2.之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
invoke(对象,参数)
示例一:调用指定的属性
public void test3() throws Exception{Class clazz = Person.class;//1.获取指定的属性//getField(String fieldName):获取运行时类中声明为public的指定属性名为fieldName的属性Field name = clazz.getField("name");//2.创建运行时类的对象Person p = (Person)clazz.newInstance();System.out.println(p);//Person [name=null, age=0]//3.将运行时类的指定的属性赋值name.set(p, "Jerry");
System.out.println(p);//Person [name=Jerry, age=0]System.out.println();//getDeclaredField(String fieldName):获取运行时类中指定的名为fieldName的属性//因为age是private修饰的,所以调用getDeclaredField方法,为了保证可以给属性赋值,还要设置可访问的权限Field age = clazz.getDeclaredField("age");age.setAccessible(true);age.set(p, 10);System.out.println(p);//Person [name=Jerry, age=10]}
示例二:调用指定的方法
//调用运行时类中指定的方法@Testpublic void test3() throws Exception{Class clazz = Person.class;//1.调用无参无返回值的方法//getMethod(String methodName,Class ... params):获取运行时类中声明为public的指定的方法Method m1 = clazz.getMethod("show");Person p = (Person) clazz.newInstance();
//调用指定的方法:Object invoke(Object obj,Object ... obj)Object returnVal = m1.invoke(p);//调用show方法,因为show是无参无返回值的方法,所以只要写名字就行System.out.println(returnVal);//null,因为是无返回值,所以返回null//2.调用无参有返回值的方法Method m2 = clazz.getMethod("toString");Object returnVal1 = m2.invoke(p);System.out.println(returnVal1);//Person [name=null, age=0]//3.对于运行时类中静态方法的调用Method m3 = clazz.getMethod("info");m3.invoke(Person.class);//中国人!//getDeclaredMethod(String methodName,Class ... params):获取运行时类中声明了的指定的方法Method m4 = clazz.getDeclaredMethod("display", String.class,Integer.class);//因为参数类型是类类型,所以都要String.class得到类类型m4.setAccessible(true);Object value = m4.invoke(p,"CHN",10);//我的国籍是:CHNSystem.out.println(value);//10}
示例三:调用指定的构造器,创建运行时类的对象
@Testpublic void test3() throws Exception{String className = "com.atguigu.java.Person";Class clazz = Class.forName(className);
调用私有的构造器,同私有的方法一样Constructor cons = clazz.getDeclaredConstructor(String.class,int.class);cons.setAccessible(true);Person p = (Person) cons.newInstance("张三",20);System.out.println(p);//Person [name=张三, age=20]}
五、反射的应用之动态代理
Java动态代理
之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
最好可以通过一个代理类完成全部的代理功能
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
动态代理使用场合:
调试
远程方法调用
代理设计模式的原理:
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理. 代理对象决定是否以及何时将方法调用转到原始对象上。
静态代理:要求被代理类和代理类同时实现相应的一套接口;通过代理类的对象调用重写接口的方法时,实际上执行的是被代理类的同样的方法的调用。
动态代理:在程序运行时,根据被代理类及其实现的接口,动态的创建一个代理类。当调用代理类的实现的抽象方法时,就发起对被代理类同样方法的调用。
涉及到的技术点:
①提供一个实现了InvocationHandler接口实现类,并重写其invoke()方法
②Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),h);
//注:obj:被代理类对象; h:实现了InvocationHandler接口的实现类的对象。
设计模式之代理模式范例
一、静态代理模式
//接口
interface ClothFactory{void productCloth();
}
//被代理类
class NikeClothFactory implements ClothFactory{@Overridepublic void productCloth() {System.out.println("Nike工厂生产一批衣服");}
}
//代理类
class ProxyFactory implements ClothFactory{ClothFactory cf;//创建代理类的对象时,实际传入一个被代理类的对象public ProxyFactory(ClothFactory cf) {this.cf = cf;}@Overridepublic void productCloth() {System.out.println("代理类开始执行,收代理费$1000");cf.productCloth();}
}public class TestClothProduct {public static void main(String[] args) {NikeClothFactory nike = new NikeClothFactory();ProxyFactory proxy = new ProxyFactory(nike);proxy.productCloth();//代理类开始执行,收代理费$1000//Nike工厂生产一批衣服}
}
二、动态代理模式
//动态代理的使用,必须实现InvocationHandler接口
//体会反射是动态语言的关键
interface Subject {void action();
}// 被代理类
class RealSubject implements Subject {public void action() {System.out.println("我是被代理类,记得要执行我哦!么么~~");}
}
//思路:当调用invoke的时候,相当于调用action方法,怎么实现呢?
//声明一个Object对象,这个obj相当于RealSubject的对象,再通过方法赋值
class MyInvocationHandler implements InvocationHandler {Object obj;// 实现了接口的被代理类的对象的声明//这个方法有两个作用:①给被代理的对象实例化②返回一个代理类的对象public Object blind(Object obj) {this.obj = obj;return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);}//当通过代理类的对象发起对被重写的方法的调用时,都会转换为对如下的invoke方法的调用@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {//method方法的返回值时returnValObject returnVal = method.invoke(obj, args);return returnVal;}
}public class TestProxy {public static void main(String[] args) {//1.被代理类的对象RealSubject real = new RealSubject();//2.创建一个实现了InvacationHandler接口的类的对象MyInvocationHandler handler = new MyInvocationHandler();//3.调用blind()方法,动态的返回一个同样实现了real所在类实现的接口Subject的代理类的对象。Object obj = handler.blind(real);Subject sub = (Subject)obj;//此时sub就是代理类的对象sub.action();//转到对InvacationHandler接口的实现类的invoke()方法的调用//再举一例NikeClothFactory nike = new NikeClothFactory();ClothFactory proxyCloth = (ClothFactory)handler.blind(nike);//proxyCloth即为代理类的对象proxyCloth.productCloth();}
}
六、动态代理与AOP
符合以下原则:
1.接口
2.被代理类:实现接口
3.代理类 :实现 InvocationHandler接口
动态代理与AOP
前面介绍的Proxy和InvocationHandler,很难看出这种动态代理的优势,下面介绍一种更实用的动态代理机制
改进后的说明:代码段1、代码段2、代码段3和深色代码段分离开了,但代码段1、2、3又和一个特定的方法A耦合了!最理想的效果是:代码块1、2、3既可以执行方法A,又无须在程序中以硬编码的方式直接调用深色代码的方法。
动态代理与AOP(Aspect OrientProgramming)
使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理
这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理interface Human {void info();void fly();
}// 被代理类
class SuperMan implements Human {public void info() {System.out.println("我是超人!我怕谁!");}public void fly() {System.out.println("I believe I can fly!");}
}class HumanUtil {public void method1() {System.out.println("=======方法一=======");}public void method2() {System.out.println("=======方法二=======");}
}class MyInvocationHandler implements InvocationHandler {Object obj;// 被代理类对象的声明public void setObject(Object obj) {this.obj = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {HumanUtil h = new HumanUtil();h.method1();Object returnVal = method.invoke(obj, args);h.method2();return returnVal;}
}class MyProxy {// 动态的创建一个代理类的对象public static Object getProxyInstance(Object obj) {MyInvocationHandler handler = new MyInvocationHandler();handler.setObject(obj);return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);}
}public class TestAOP {public static void main(String[] args) {SuperMan man = new SuperMan();// 创建一个被代理类的对象Object obj = MyProxy.getProxyInstance(man);// 返回一个代理类的对象Human hu = (Human) obj;hu.info();// 通过代理类的对象调用重写的抽象方法System.out.println();hu.fly();}
}
运行结果: