Java代理簡(jiǎn)述
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
作者 | 碼猿手
來(lái)源 | urlify.cn/qQBvMf
1.什么是代理?
對(duì)類或?qū)ο螅繕?biāo)對(duì)象)進(jìn)行增強(qiáng)功能,最終形成一個(gè)新的代理對(duì)象,(Spring Framework中)當(dāng)應(yīng)用調(diào)用該對(duì)象(目標(biāo)對(duì)象)的方法時(shí),實(shí)際調(diào)用的是代理對(duì)象增強(qiáng)后的方法,比如對(duì)功能方法login實(shí)現(xiàn)日志記錄,可以通過(guò)代理實(shí)現(xiàn);
PS:
目標(biāo)對(duì)象--被增強(qiáng)的對(duì)象;
代理對(duì)象--增強(qiáng)后的對(duì)象;
2.為什么需要代理?
一些類里面的方法有相同的代碼或類中有相同的功能,可以將這些相同抽取出來(lái)形成一個(gè)公共的方法或功能,但Java有兩個(gè)重要的原則:
單一職責(zé)(對(duì)類來(lái)說(shuō)的,即一個(gè)類應(yīng)該只負(fù)責(zé)一項(xiàng)職責(zé))和開(kāi)閉原則(開(kāi)放擴(kuò)展,修改關(guān)閉),如果每個(gè)類的每個(gè)功能都調(diào)用了公共功能,就破壞了單一職責(zé),如下圖;
如果這個(gè)類是別人已經(jīng)寫(xiě)好的,你動(dòng)了這個(gè)代碼,同時(shí)也破壞了開(kāi)閉原則(同時(shí)改動(dòng)代碼很麻煩,里面可能涉及其他很多的調(diào)用,可能帶出無(wú)數(shù)的bug,改代碼比新開(kāi)發(fā)功能還難/(ㄒoㄒ)/~~);
由于有上面的問(wèn)題存在,使用代理來(lái)實(shí)現(xiàn)是最好的解決辦法;
3.Java實(shí)現(xiàn)代理有哪些?
?。?)靜態(tài)代理:通過(guò)對(duì)目標(biāo)方法進(jìn)行繼承或聚合(接口)實(shí)現(xiàn);
(會(huì)產(chǎn)生類爆炸,因此在不確定的情況下,盡量不要使用靜態(tài)代理,避免產(chǎn)生類爆炸) 1)繼承:代理對(duì)象繼承目標(biāo)對(duì)象,重寫(xiě)需要增強(qiáng)的方法;
//業(yè)務(wù)類(目標(biāo)對(duì)象)
public class UserServiceImpl {
public void query(){
System.out.println("業(yè)務(wù)操作查詢數(shù)據(jù)庫(kù)....");
}
}
//日志功能類
public class Log {
public static void info(){
System.out.println("日志功能");
}
}
//繼承實(shí)現(xiàn)代理(代理對(duì)象)
public class UserServiceLogImpl extends UserServiceImpl {
public void query(){
Log.info();
super.query();
}
}
從上面代碼可以看出,每種增強(qiáng)方法會(huì)產(chǎn)生一個(gè)代理類,如果現(xiàn)在增強(qiáng)方法有日志和權(quán)限,單個(gè)方法增強(qiáng)那需要兩個(gè)代理類(日志代理類和權(quán)限代理類),如果代理類要同時(shí)擁有日志和權(quán)限功能,那又會(huì)產(chǎn)生一個(gè)代理類,同時(shí)由于順序的不同,可能會(huì)產(chǎn)生多個(gè)類,比如先日志后權(quán)限是一個(gè)代理類,先權(quán)限后日志又是另外一個(gè)代理類。
由此可以看出代理使用繼承的缺陷:
產(chǎn)生的代理類過(guò)多(產(chǎn)生類爆炸),非常復(fù)雜,維護(hù)難;
2)聚合:
代理對(duì)象和目標(biāo)對(duì)象都實(shí)現(xiàn)同一接口,使用裝飾者模式,提供一個(gè)代理類構(gòu)造方法(代理對(duì)象當(dāng)中要包含目標(biāo)對(duì)象),參數(shù)是接口,重寫(xiě)目標(biāo)方法;
//接口
public interface UserDao {
void query();
}
//接口實(shí)現(xiàn)類:目標(biāo)對(duì)象
public class UserDaoImpl implements UserDao {
@Override
public void query() {
System.out.println("query......");
}
}
//日志功能類
public class Log {
public static void info(){
System.out.println("日志功能");
}
}
//聚合實(shí)現(xiàn)代理:同樣實(shí)現(xiàn)接口,使用裝飾者模式;(代理對(duì)象)
public class UserDaoLogProxy implements UserDao {
UserDao userDao;
public UserDaoLogProxy(UserDao userDao){//代理對(duì)象包含目標(biāo)對(duì)象
this.userDao = userDao;
}
@Override
public void query() {
Log.info();
userDao.query();
}
}
//測(cè)試
public static void main(String[] args) {
UserDaoImpl target = new UserDaoImpl();
UserDaoLogProxy proxy = new UserDaoLogProxy(target);
proxy.query();
}聚合由于利用了面向接口編程的特性,產(chǎn)生的代理類相對(duì)繼承要少一點(diǎn)(雖然也是會(huì)產(chǎn)生類爆炸,假設(shè)有多個(gè)Dao,每個(gè)Dao產(chǎn)生一個(gè)代理類,所以還是會(huì)產(chǎn)生類爆炸),如下案例:
//時(shí)間記錄功能
public class Timer {
public static void timer(){
System.out.println("時(shí)間記錄功能");
}
}
//代理類:時(shí)間功能+業(yè)務(wù)
public class UserDaoTimerProxy implements UserDao {
UserDao userDao;
public UserDaoTimerProxy(UserDao userDao){
this.userDao = userDao;
}
@Override
public void query() {
Timer.timer();
userDao.query();
}
}
//測(cè)試
public static void main(String[] args) {
//timer+query
UserDao target = new UserDaoTimerProxy(new UserDaoImpl());
//log+timer+query
UserDao proxy = new UserDaoLogProxy(target);
proxy.query();
}
public static void main(String[] args) {
//log+query
UserDao target = new UserDaoLogProxy(new UserDaoImpl());
//timer+log+query
UserDao proxy = new UserDaoTimerProxy(target);
proxy.query();
}PS:
裝飾和代理的區(qū)別:
代理不需要指定目標(biāo)對(duì)象,可以對(duì)任何對(duì)象進(jìn)行代理;
裝飾需要指定目標(biāo)對(duì)象,所以需要構(gòu)造方法參數(shù)或set方法指定目標(biāo)對(duì)象;
幾個(gè)io的Buffer流(BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)就是使用了裝飾模式的靜態(tài)代理;
public class BufferedReader extends Reader {
private Reader in;
........
public BufferedReader(Reader in, int sz) {
super(in);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz];
nextChar = nChars = 0;
}
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
}?。?)動(dòng)態(tài)代理:Java有JDK動(dòng)態(tài)代理和CGLIB代理;
(Spring Framework通過(guò)Spring AOP實(shí)現(xiàn)代理,底層還是使用JDK代理和CGLIB代理來(lái)實(shí)現(xiàn))
模擬動(dòng)態(tài)代理:不需要手動(dòng)創(chuàng)建類文件(因?yàn)橐坏┦謩?dòng)創(chuàng)建類文件,會(huì)產(chǎn)生類爆炸),通過(guò)接口反射生成一個(gè)類文件,然后調(diào)用第三方的編譯技術(shù),動(dòng)態(tài)編譯這個(gè)產(chǎn)生的類文件成class文件,然后利用URLclassLoader把這個(gè)動(dòng)態(tài)編譯的類加載到j(luò)vm中,然后通過(guò)反射把這個(gè)類實(shí)例化。
(通過(guò)字符串產(chǎn)生一個(gè)對(duì)象實(shí)現(xiàn)代理);
PS:
Java文件 -> class文件 -> byte字節(jié)(JVM)-> class對(duì)象(類對(duì)象)-> new(對(duì)象);
所以步驟如下:
1)代碼實(shí)現(xiàn)一個(gè)內(nèi)容(完整的Java文件內(nèi)容,包含包名、變量、構(gòu)造方法、增強(qiáng)后的方法等),使其通過(guò)IO產(chǎn)生一個(gè)Java文件;
2)通過(guò)第三方編譯技術(shù)產(chǎn)生一個(gè)class文件;
3)加載class文件利用反射實(shí)例化一個(gè)代理對(duì)象出來(lái);
public class ProxyUtil {
/**
* content --->string
* |
* |生成
* v
* .java <-----通過(guò)io產(chǎn)生
* .class <-----Java文件編程產(chǎn)生
*
* .new <-----class文件反射產(chǎn)生實(shí)例對(duì)象
* @return
*/
public static Object newInstance(Object target){
Object proxy=null;
//根據(jù)對(duì)象獲取接口
Class targetInf =target.getClass().getInterfaces()[0];
//獲取接口的所有方法
//getMethods(),該方法是獲取本類以及父類或者父接口中所有的公共方法(public修飾符修飾的)
//getDeclaredMethods(),該方法是獲取本類中的所有方法,包括私有的(private、protected、默認(rèn)以及public)的方法
Method[] declaredMethods = targetInf.getDeclaredMethods();
String line="\n";
String tab ="\t";
//接口名稱
String targetInfName = targetInf.getSimpleName();
String content ="";
//包位置
String packageContent = "package com;"+line;
//接口
String importContent = "import "+targetInf.getName()+";"+line;
String clazzFirstLineContent = "public class $Proxy implements "+targetInfName+"{"+line;
//屬性
String filedContent =tab+"private "+targetInfName+" target;"+line;
//構(gòu)造方法
String constructorContent =tab+"public $Proxy ("+targetInfName+" target){" +line
+tab+tab+"this.target =target;"
+line+tab+"}"+line;
//方法
String methodContent = "";
for(Method method:declaredMethods){
//返回值
String returnTypeName = method.getReturnType().getSimpleName();
//方法名
String methodName = method.getName();
// Sting.class String.class 參數(shù)類型
Class<?>[] parameterTypes = method.getParameterTypes();
String argsContent = "";
String paramsContent="";
int flag = 0;
for(Class args :parameterTypes){
//String,參數(shù)類型
String simpleName = args.getSimpleName();
//String p0,Sting p1,
argsContent+=simpleName+" p"+flag+",";
paramsContent+="p"+flag+",";
flag++;
}
if (argsContent.length()>0){
argsContent=argsContent.substring(0,argsContent.lastIndexOf(",")-1);
paramsContent=paramsContent.substring(0,paramsContent.lastIndexOf(",")-1);
}
methodContent+=tab+"public "+returnTypeName+" "+methodName+"("+argsContent+") {"+line
//增強(qiáng)方法先寫(xiě)死
+tab+tab+"System.out.println(\"log\");"+line;
if(returnTypeName.equals("void")){
methodContent+=tab+tab+"target."+methodName+"("+paramsContent+");"+line
+tab+"}"+line;
}else{
methodContent+=tab+tab+"return target."+methodName+"("+paramsContent+");"+line
+tab+"}"+line;
}
}
content+=packageContent+importContent+clazzFirstLineContent+filedContent
+constructorContent+methodContent+"}";
//生成Java文件
File file = new File("D:\\com\\$Proxy.java");
try{
if(!file.exists()){
file.createNewFile();
}
//創(chuàng)建
FileWriter wr = new FileWriter(file);
wr.write(content);
wr.flush();
wr.close();
//編譯Java文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(file);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
//new --> 反射
URL[] urls = new URL[]{new URL("file:D:\\\\")};
//加載外部文件
URLClassLoader classLoader = new URLClassLoader(urls);
Class<?> proxyClass = classLoader.loadClass("com.$Proxy");
Constructor constructor = proxyClass.getConstructor(targetInf);
//構(gòu)造方法創(chuàng)建實(shí)例
proxy = constructor.newInstance(target);
//clazz.newInstance();//根據(jù)默認(rèn)構(gòu)造方法創(chuàng)建對(duì)象
//Class.forName()
}catch (Exception e){
e.printStackTrace();
}
return proxy;
}
}
自定義代理的缺點(diǎn):
生成Java文件;
動(dòng)態(tài)編譯文件;
需要一個(gè)URLClassLoader;
涉及到了IO操作,軟件的最終性能體現(xiàn)到了IO操作,即IO操作影響到軟件的性能;
案例:
1)接口:
public interface UserDao {
void query();
void query(String name);
String getName(String id);
}
2)實(shí)現(xiàn)類:
public class UserService implements UserDao {
@Override
public void query() {
System.out.println("query");
}
@Override
public void query(String name) {
System.out.println(name);
}
@Override
public String getName(String id) {
System.out.println("id:"+id);
return "李四";
}
}
3)測(cè)試:
public static void main(String[] args) {
UserDao dao = (UserDao) ProxyUtil.newInstance(new UserService());
dao.query();
dao.query("張三");
}
--------結(jié)果--------
log
query
log
張三

package com;
import com.hrh.dynamicProxy.dao.UserDao;
public class $Proxy implements UserDao{//自定義動(dòng)態(tài)代理生成的文件
private UserDao target;
public $Proxy (UserDao target){
this.target =target;
}
public String getName(String p) {
System.out.println("log");
return target.getName(p);
}
public void query(String p) {
System.out.println("log");
target.query(p);
}
public void query() {
System.out.println("log");
target.query();
}
}JDK動(dòng)態(tài)代理:
基于反射實(shí)現(xiàn),通過(guò)接口反射得到字節(jié)碼,然后將字節(jié)碼轉(zhuǎn)成class,通過(guò)一個(gè)native(JVM實(shí)現(xiàn))方法來(lái)執(zhí)行;
案例實(shí)現(xiàn):
實(shí)現(xiàn) InvocationHandler 接口重寫(xiě) invoke 方法,其中包含一個(gè)對(duì)象變量和提供一個(gè)包含對(duì)象的構(gòu)造方法;
public class MyInvocationHandler implements InvocationHandler {
Object target;//目標(biāo)對(duì)象
public MyInvocationHandler(Object target){
this.target=target;
}
/**
* @param proxy 代理對(duì)象
* @param method 目標(biāo)對(duì)象的目標(biāo)方法
* @param args 目標(biāo)方法的參數(shù)
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("log");
return method.invoke(target,args);
}
}
public class MyInvocationHandlerTest {
public static void main(String[] args) {
//參數(shù): 當(dāng)前類的classLoader(保證MyInvocationHandlerTest當(dāng)前類可用)
// 接口數(shù)組:通過(guò)接口反射得到接口里面的方法,對(duì)接口里面的所有方法都進(jìn)行代理
// 實(shí)現(xiàn)的InvocationHandler:參數(shù)是目標(biāo)對(duì)象
UserDao jdkproxy = (UserDao) Proxy.newProxyInstance(MyInvocationHandlerTest.class.getClassLoader(),
new Class[]{UserDao.class},new MyInvocationHandler(new UserService()));
jdkproxy.query("query");
//-----結(jié)果------
//log
//query
}
}下面查看底層JDK生成的代理類class:
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy18", new Class[]{UserDao.class});
try {
FileOutputStream fileOutputStream = new FileOutputStream("xxx本地路徑\\$Proxy18.class");
fileOutputStream.write(bytes);
fileOutputStream.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}或在代碼的最前面添加下面代碼:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "xxx本地路徑");代理類class反編譯后的內(nèi)容:
下面的內(nèi)容驗(yàn)證了JDK動(dòng)態(tài)代理為什么基于聚合(接口)來(lái)的,而不能基于繼承來(lái)的?
因?yàn)镴DK動(dòng)態(tài)代理底層已經(jīng)繼承了Proxy,而Java是單繼承,不支持多繼承,所以以接口來(lái)實(shí)現(xiàn);
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.hrh.dao.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy18 extends Proxy implements UserDao {
private static Method m1;
private static Method m4;
private static Method m5;
private static Method m2;
private static Method m0;
private static Method m3;
public $Proxy18(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void query(String var1) throws {
try {
super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void query() throws {
try {
super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String getName(String var1) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("com.hrh.dao.UserDao").getMethod("query", Class.forName("java.lang.String"));
m5 = Class.forName("com.hrh.dao.UserDao").getMethod("query");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.hrh.dao.UserDao").getMethod("getName", Class.forName("java.lang.String"));
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
CGLIB代理:
借助asm(一個(gè)操作字節(jié)碼的框架)實(shí)現(xiàn)代理操作;
CGLIB基于繼承來(lái)的(前文有CGLIB代理類class的反編譯可以看出);
public class UserService {
public void query(){
System.out.println("query");
}
public static void main(String[] args) {
// 通過(guò)CGLIB動(dòng)態(tài)代理獲取代理對(duì)象的過(guò)程
Enhancer enhancer = new Enhancer();
// 設(shè)置enhancer對(duì)象的父類
enhancer.setSuperclass(UserService.class);
// 設(shè)置enhancer的回調(diào)對(duì)象
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before method run...");
Object result = proxy.invokeSuper(obj, args);
System.out.println("after method run...");
return result;
}
});
//創(chuàng)建代理對(duì)象
UserService bean = (UserService) enhancer.create();
bean.query();
}
}//-----------結(jié)果------before method run...queryafter method run...
PS:如果只是對(duì)項(xiàng)目中一個(gè)類進(jìn)行代理,可以使用靜態(tài)代理,如果是多個(gè)則使用動(dòng)態(tài)代理;
粉絲福利:Java從入門(mén)到入土學(xué)習(xí)路線圖
??????

??長(zhǎng)按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
