一、反射概述
Java Reflection
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Java反射机制提供的功能
Ø 在运行时判断任意一个对象所属的类
Ø 在运行时构造任意一个类的对象
Ø 在运行时判断任意一个类所具有的成员变量和方法
Ø 在运行时调用任意一个对象的成员变量和方法
Ø 生成动态代理
看两个范例
范例一:
//在有反射以前,如何创建一个类的对象,并调用其中的方法、属性
@Test
public 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;
}
@Override
public 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()方法的使用:得到完整的“包类”名称
@Test
public void demo3(){
Person person = new Person();
//通过运行时类的对象,调用其getClass(),返回其运行时类。
Class clazz = person.getClass();
System.out.println(clazz);
//结果:class me.gorden.reflection.Person
}
2.2获取Class类的实例的4种方式
@Test
public 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==name
private==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]
}
示例二:调用指定的方法
//调用运行时类中指定的方法
@Test
public 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);//我的国籍是:CHN
System.out.println(value);//10
}
示例三:调用指定的构造器,创建运行时类的对象
@Test
public 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{
@Override
public void productCloth() {
System.out.println("Nike工厂生产一批衣服");
}
}
//代理类
class ProxyFactory implements ClothFactory{
ClothFactory cf;
//创建代理类的对象时,实际传入一个被代理类的对象
public ProxyFactory(ClothFactory cf) {
this.cf = cf;
}
@Override
public 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方法的调用
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//method方法的返回值时returnVal
Object 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;
}
@Override
public 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();
}
}
运行结果: