作为JavaSE的两个个基础知识,【反射】和【动态代理】被广泛运用到spring、spring boot、mybatis......等等各种地方,等到后面的学习中反复提到这个知识点就会开始懵逼,而且这两个知识点其实也是紧密相连的,很多文章和课程里也并没有把他们联系起来,因此在看代码的时候会非常懵逼,这里我就来把他们联系起来讲讲。
一、反射
1、反射是啥
反射简单来说就是:能把类里的所有东西(包括这个类的父类里的东西)全部解析出来,扒得干干净净、一丝不剩!
那么细讲就是,首先我们创建了一个class类的时候,是先将这个【.class】字节码文件存入磁盘
然后当我们创建这个类的对象的时候,jvm(java虚拟机)就会去找有没有这么个类,比如创建Student对象,jvm就回去找有没有Student.class这个字节码文件,有的话就把它解析了,然后根据创建的实例化对象来在内存里分配一个对应的对象空间。
与此同时!!jvm会自动为这个类创建一个【class对象】,一个类只对应产生一个class对象,不管你创建了几个这个类的实例化对象(区分一下【对象空间】和【class对象】,【对象空间】只是给你这个对象一个存放数据的 “空间” )
;
那么反射!就是获取到【class对象】之后,反向分析该类的每一个对象的信息!!!
那么总结,我们反射的机制,就是要解析class字节码文件,然后获取到【Class对象】,用这个【Class对象】解析各个对象的信息!!!!
2、怎么获取class对象
首先我们需要导入一个包:【import java.lang.reflect.Method;】
然后要获取【Class对象】,有三种方式:
方式一:Class c = 类名.class;
//获取Class对象1: Class c = 类名.class;
方式二:Class c = 对象名.getClass;
//获取Class对象2: 某个类 obj = new 某个类(); Class c = obj.getClass();
方式三:Class c = Class.forName("全限定包名");
这个方法是最常用的
//获取Class对象3: Class c = Class.forName("包全名");
其中"全限定包名"这样获取:
3、反射获取类里的构造方法
语法:
例子:(我懒得写,直接拿黑马的)
最后,利用反射根据指定的构造方法创建对象
例子:
4、反射获取类的成员变量
语法:
例子:
最后,获取变量值和修改变量值
5、反射获取成员方法
这个是最重要的!!!后面经常用到!!!尤其记住!!!尤其是这个【invoke】方法!!!
语法:
例子:
通常我们最常用的就是获取【所有方法(包括父类)】,那么首先获取到Class对象之后,我们就可以用【Method[ ]】这么一个数组接收这个类的【所有方法】,其中包括【他的父类的方法】
语法格式就是:【Method[ ] 变量 = class对象.getMethods( )】
//获取Class对象: Class c = Class.forName("com.xxx.xxx"); //获取所有公共方法: Method[] methods = c.getMethods();
如果不想获取父类的方法,只要自己的就:【Method[ ] 变量 = class对象.getDeclareMethods( )】
//获取Class对象: Class c = Class.forName("com.xxx.xxx"); //获取所有公共方法: Method[] methods = c.getDeclareMethods();
单个方法(且包括私有)
获取单个方法名称,调用单个Method对象.getName()
获取单个方法的参数名,调用单个Method对象.getParameters()
重点来了!!重点!!!重点!!!重点!!!重点!!!重点!!!重点!!!重点!!!
invoke方法!!!!!
语法:
例子:
首先创建一个类为例子:
package com.czm.tliaswebmanagement.reflect;
public class MyClass {
private String name;
//构造方法
public MyClass(String name){
this.name = name;
}
//公有方法,有参数
public void sing(String song){
System.out.println(this.name + "正在唱《" + song + "》");
}
//私有方法,并且有返回值
private String eat(){
return "老八秘制小汉堡";
}
}
然后我们现在要通过反射机制,来使用这个类的方法,其中包括一个【公有的sing】和一个【私有的eat】方法
基本准备流程就是:1、获取MyClass的Class对象 2、通过Class对象获取到想要的方法,并用Method对象接收 3、创建MyClass的实例化对象
接下来就是invoke登场了,我们只需要【Method对象.invoke()】就可以使用方法了,然后只需要传入 “一或两” 个参数,如果方法里没有参数就只用传入MyClass的实例化对象(就是把方法参数化,因为反射机制利用invoke是去使用所有这个Method方法,那么假设有class1、class2...这么多个对象,到底是使用哪一个对象的这个Method方法呢?所以得传一个实例化对象);如果这个方法有参数,那就传入第二个参数,是这个方法的参数类型,格式就是【数据类型.class】
那么还有三种情况:
1、正常公用方法,无返回值,那就正常【Method对象.invoke()】就行
2、有返回值,那么记住【Method对象.invoke()】返回的是Object对象
3、私有方法,在使用【Method对象.invoke()】之前必须得先【Method对象.setAccessible(true)】来强制获取权限访问私有方法
完整代码:(注意:运行的时候要throws Exception抛出一下异常)
package com.czm.tliaswebmanagement.reflect; import java.lang.reflect.Method; public class TestReflect { public static void main(String[] args) throws Exception { //获取Class对象 Class c = Class.forName("com.czm.tliaswebmanagement.reflect.MyClass"); //分别获取这个类的getter方法、sing方法和eat方法 //.getDeclaredMethod()第一个参数是方法名,(有参数的情况下)第二个参数是这个方法的参数的类型,就是【数据类型.class】 Method singMethod = c.getDeclaredMethod("sing", String.class); Method eatMethod = c.getDeclaredMethod("eat"); //创建一个这个类的对象,然后调用【Method对象.invoke()】来调用类里的方法 //invoke()的第一个参数是【这个类的实例化对象】,(该方法有参数的情况)第二是方法里的参数值 MyClass myClass = new MyClass("KobeManba"); singMethod.invoke( myClass, "See you agin" ); //这里别忘了,因为我们把eat方法设置成private私有方法 //所以我们需要用【Method对象.setAccessible(true)】来强制获取权限访问私有方法 eatMethod.setAccessible(true); Object str1 = eatMethod.invoke( myClass ); //然后有返回值的方法,通过invoke会返回成Object数据 String str2 = (String) eatMethod.invoke( myClass ); //那如果我们知道返回值是什么类型,就可以直接强制类型转化就行了 System.out.println(str1); System.out.println(str2); } }
二、动态代理
1、动态代理是啥
举个通俗易懂的例子:
假设大明星杨超越的工资只有两个事,唱歌、跳舞,那么在她的唱歌跳舞工作中,除了唱歌跳舞,还有一些前期准备比如:准备话筒、收钱.....
但是杨超越作为一个歌星,她只想要干好唱歌跳舞这两件纯粹的事就行,准备话筒、收钱.....这些事她不想干
那她就要找一个经纪公司、中介公司来帮她打理这些繁琐事,让她能够专心去唱歌跳舞。那么加入这个时候一个节目组或者一个音乐主办方来找杨超越出席表演唱歌跳舞,杨超越就会说:“你去找我经纪人,别来烦我”,然后主办方去找经纪人来请她唱歌跳舞,但是......经纪人怎么会唱歌跳舞呢?经纪人只会帮忙准备话筒、收钱...干这些琐碎事。
那么经纪人就必须得有对应解决唱歌跳舞的处理方法,除了干准备话筒、收钱...干这些琐碎事,当有主办方找她协商唱歌跳舞的事,经纪人就得把这些事情整理好找到杨超越,跟她协商好然后请她出面完成唱歌跳舞的表演。
那么经纪人去处理对应解决唱歌跳舞的处理方法就是唱歌跳舞这个功能的接口,然后杨超越就要有实现这些接口的实现方法。
2、怎么弄?
上面例子说得轻松,但是其实要真正代码实现还是不容易,我们先好好再举个例子好理解
首先我们要先设置一个【接口】,我们理解为【一个明星基本要具备的唱跳能力、业务】吧,首先我们要包装一个明星,你必须得具备一个能唱歌跳舞的能力业务吧?那具体是什么类型风格的唱歌、跳舞还得根据明星自己来实现。
例子代码:
package com.czm.tliaswebmanagement.proxy;
public interface Star {
/**
* 代理接口调用【唱歌】方法
* @param songName
* @return
*/
String sing(String songName);
/**
* 代理接口调用【跳舞】方法
*/
void dance();
}
然后就是创建一个【实现类】了,让一个明星去实现这些唱跳能力,这里我设置了它的starName明星名字这个成员变量,然后就是具体实现唱歌和跳舞的方法
package com.czm.tliaswebmanagement.proxy;
public class BigStar implements Star {
private String starName;
public BigStar(String starName){
this.starName = starName;
}
/**
* 大明星实现【唱歌】
* @param songName
* @return
*/
@Override
public String sing(String songName) {
System.out.println(this.starName + "本人正在唱《" + songName + "》");
return "结束后发布官方微博:“谢谢歌迷支持这场演唱会!”";
}
/**
* 大明星实现【跳舞】
*/
@Override
public void dance() {
System.out.println(this.starName + "本人正在跳舞......");
}
}
然后重点来了!!最难的部分!!!
我们需要创建一个【代理工具大类】,我们理解为【代理公司、经纪人公司】,你明星找经纪人也不可能直接找一个人当吧?肯定得去一个经纪公司签订合同,然后再选经纪人。那么这里【代理工具大类】就是这样,里面存在对应各种类的【代理、经纪人】,也就是【代理类】
那么接下来就是怎么去写这些【代理、经纪人】,也就是【代理类】了,首先这些【代理类】都是这个【代理工具大类】的【静态方法】,是因为写成【静态方法】的形式就可以直接通过【代理工具大类.静态方法( )】这样创建【代理类的实例化对象】
补充一下知识点,老实说我之前学java也直接跳过了这里.......
;
我们都知道,如果要创建一个类的【内部类】的实例化对象很麻烦,首先你得先创建这个类的对象,然后再根据这个对象创建这个内部类的对象,而且写法【外部类.内部类 对象名 = 外部类对象. new 内部类()】还很容易错;
;
那么【静态方法】创建实例化对象就很方便,直接【静态方法的返回值类型 对象名 = 外部类.静态方法()】,创建静态方法也很简单,记得加上static修饰符和记得return一个对象出去就行。
public class OuterClass{ public class innerClass{ //这是一个内部类 } public static OuterClass innerMethod( 参数 ){ //这是一个静态方法 return new OuterClass(); } } //在外部创建它两的实例化对象 //创建内部类innnerClass的对象 OuterClass c = new OuterClass(); OuterClass.innerClass inner1 = c. new innerClass(); //创建静态方法innerMethod的对象 OuterClass inner2 = OuterClass.innerMethod( 参数 );
然后,因为我们需要在外部调用创建这个静态方法的实例化对象,再次注意是这个【代理工具大类】的【代理类】的实例化对象,而不是【代理工具大类】的实例化对象,那么就得【代理工具大类.静态方法( )】这样创建对象。
然后这还只是【代理类】的 “外形”,你得好好修炼、培训一下吧,不能单有有个 “人形”,你还得有内部的 “能力”吧?而且我们也知道【静态方法】创建的对象,必须得在静态方法里return一个对象,那么我们就得再好好写一个【代理类对象】,通过【代理类】这个静态方法return出去。
那么接下来看怎么把这里里面这个【代理类对象】创建。
简单来分析,很简单就是【Proxy.newProxyInstance( )】这个方法,就能生成一个【代理类对象】,但是注意【Proxy.newProxyInstance( )】返回的是一个Object类型对象,我们要变成我们想要的【代理类对象】,就得用它要代理的那个类的接口对象来接收,注意是接口!具体为什么其实我也不知道,反正大家记住就行,就当是一种规范吧,然后用 (接口) 这样强制类型转换,将Object转换成接口类型的对象,就成功“转型”成【代理类对象】了
然后,【Proxy.newProxyInstance( )】这个方法需要接收三个参数:
参数1:用于指定一个类加载器,具体啥意思不用管照抄就行
ProxyUtil.class.getClassLoader()
参数2:指定生成的代理里有哪些方法,一样别管那么多,照抄
new Class[]{Star.class}
参数3:【重要!!】指定代理对象需要干什么
new InvocationHandler() {...}
ok,最后我们剖析最难的最核心的这一个参数,第三个参数,就是它,决定了这个【代理类对象】需要干什么!
那么也就是现在【代理类对象】就是一个完完整整的经纪人了,当我们在外面调用【代理类】的实例化对象(其实也就是调用【代理类对象】)的方法的时候,就相当于一个主办方去委托经纪人去接收处理一下明星的唱歌、跳舞...等等表演,那么经纪人就会利用他的业务能力来处理这些事
那么这里第三个参数,也就是【InvocationHandler()对象】就是他的【业务能力】,那么我们只需要写好一个【InvocationHandler()对象】,在里面写好这个【经纪人、代理】该做的事,然后放到【Proxy.newProxyInstance( )】的第三个参数的位置进去就行了。不过我们通常习惯直接在第三个参数那里通过【new InvocationHandler() { ... }】的方式,直接写好【InvocationHandler()对象】里的内容。
然后这个【InvocationHandler()对象】其实是继承了别的接口,具体是啥我也没细看,总之我们要@Override重写它里面的invoke这个方法,这里注意!!!这个【invoke】方法可不是我们上面学的【反射里的那个invoke】,这里这个【invoke】他只是【InvocationHandler()对象】里的一个内置Api方法,通过看参数也可以看出来,这个【invoke】需要三个参数,而【反射里的那个invoke】是两个参数
然后这给invoke方法需要的三个参数是:
第一个参数:proxy,接收获取外部创建的【代理类】对象,比如
第二个参数:method,代表是【代理类】对象(也就是proxy对象)的哪一个方法,比如
第三个参数:args,代表proxy对象调用方法时传入的参数,比如
;
整体的invoke方法写法:(记得throws Throwable这个异常)
最后,完成我们的invoke方法里的内容,既然是【经纪人、代理】该做的事,那么我们就要根据第二个参数(method)来判断,现在外面【代理类】对象调用的是哪一个方法,针对不同的方法做不同的前期准备琐碎事,比如调用唱歌,【经纪人】就要准备话筒、收800万唱歌费;调用跳舞,【经纪人】就要准备场地、服装、收1000万跳舞费......
最后的最后,我们这个【InvocationHandler()对象】的【invoke】方法是【经纪人】干的活,那么回到最初那个问题:【经纪人】他不会唱歌跳舞啊!唱歌跳舞肯定不是他来干,那就只能利用【反射机制!!!!】,用【反射机制的invoke方法】来实现【明星的唱歌跳舞方法】
最后,将这整个【代理类】,也就是【静态方法】return出去,就完成了一个完完整整的代理类了。
完整代码:
package com.czm.tliaswebmanagement.proxy;
import com.czm.tliaswebmanagement.pojo.Emp;
import com.czm.tliaswebmanagement.service.EmpService;
import com.czm.tliaswebmanagement.service.Userservice;
import org.apache.catalina.User;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//这是一个【代理工具大类】,这里可以对应不同的 [代理接口],来创建管理不同的【代理类】
public class ProxyUtil {
//而每一个【代理类】,都是【代理工具大类】的一个【静态方法】,通过【静态方法】可以在外界直接通过【类.静态方法()】的形式创建实例化对象
//比如外界要创建【createProxy代理类】对象,就这样:Star starProxy = ProxyUtil.createProxy(BigStar实例化对象);
public static Star createProxy(BigStar bigStar){
// newProxyInstance(ClassLoader, loaderClass<?>[] interfaces,InvocationHandler h)需要三个参数:
//参数1:用于指定一个类加载器;
//参数2:指定生成的代理里有哪些方法;
//参数3:用来指定生成的代理对象要干什么事情
// Proxy.newProxyInstance()返回的是一个Object对象,所以我们需要将Object强转成Star接口类型
Star starProxy = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(), //参数1,照抄就行
new Class[]{Star.class}, //参数2,一样,别管那么多,照抄
new InvocationHandler() { //参数3,指定代理对象需要干什么
//这个【invoke】很重要很重要!!它是一个回调函数
//但是区别一下这个【不是】下面那个invoke,这个是供代理对象调的invoke方法,只是InvocationHandler的内置api
//在外面调用starProxy.sing("断了的弦") or starProxy.dance()的时候其实就回调用这个invoke函数
//第一个参数proxy,接收获取外部创建的proxy对象,比如starProxy.sing("断了的弦")的【starProxy】
//第二个参数method,代表是proxy对象的哪一个方法,比如starProxy.sing("断了的弦")就是【sing()方法】
//第三个参数args,代表proxy对象调用方法时传入的参数,比如starProxy.sing("断了的弦")就是【“断了的弦”】
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理需要干的事,这个代理类对象会调用不同的代理方法:starProxy.sing("断了的弦")、starProxy.dance()
//判断proxy对象调用的是哪一个方法,针对不同的方法来做“代理该干的琐碎事”
//比如主办方说要请杨超越唱歌,那就先准备话筒、收取费用
if ( method.getName().equals("sing") ){
System.out.println("代理开始干活:准备话筒,【唱歌】要收费800万出场费");
}else if ( method.getName().equals("dance") ){
System.out.println("代理开始干活:准备场地、服装,【跳舞】收费1000万一场");
}
//那么我们学过了【反射机制的invoke方法】,可以知道要使用【某个对象】里的【某个方法】
//那就利用【Method对象.invoke(该对象, 这个Method方法的参数)】
//再次提醒,【Method对象】就代表要调用的那个【方法】
//invoke第一个参数是这个类的一个实例化对象,也就是指明是调用【哪一个对象】的【这个方法】
//invoke第二个参数是这个方法的参数,那么直接用这外层“大invoke”方法里的args参数就行
return method.invoke(bigStar, args);
}
}
);
return starProxy;
}
}
接着我们在外面测试一下,先创建一个普通的接口实现类对象(实现“明星”接口的“明星类”),然后用我们刚刚写的【代理工具大类.静态方法( )】——>【ProxyUtil.createProxy()】,传入刚刚那个普通的接口实现类对象(实现“明星”接口的“明星类”),就创建出了一个【代理类对象】
;
最后利用【代理类对象】去调用各种方法,比如【sing()】【dance()】这些方法,就会在执行完【代理类对象】针对【sing()】【dance()】所要做的事之后,紧接着完成普通的接口实现类对象(实现“明星”接口的“明星类”)里真正的【sing()】【dance()】
完整代码:
package com.czm.tliaswebmanagement.proxy; public class Test { public static void main(String[] args){ //创建一个【明星】对象 BigStar bigStar = new BigStar("杨超越"); //然后创建一个【代理类】对象 Star starProxy = ProxyUtil.createProxy(bigStar); //调用这个【代理类】对象的【sing唱歌方法】 String rs = starProxy.sing("断了的弦"); System.out.println(rs); //调用这个【代理类】对象的【dance跳舞方法】 System.out.println(); starProxy.dance(); } }
其实我说到这,感觉自己废话很多,写了一坨屎,但是我也没办法,我只会用这样非常详尽的方法来讲明白这之间的逻辑,最后附上两张图,自行理解吧(点击放大来查看)