java面試常見模式問題---代理模式
- 本篇總結(jié)的是 代理設(shè)計(jì)模式,后續(xù)會經(jīng)常更新~
- 代理模式最直觀的解釋就是,通過代理,將被代理對象 “增強(qiáng)”!(即,擴(kuò)展被代理對象的功能)
- 代理模式分為靜態(tài)代理,和動(dòng)態(tài)代理:動(dòng)態(tài)代理的代理類是動(dòng)態(tài)生成的 , 靜態(tài)代理的代理類是我們提前寫好的邏輯。
- Java 中實(shí)現(xiàn)動(dòng)態(tài)代理的方式有 2 種:
- JDK 動(dòng)態(tài)代理
- CGLIB 動(dòng)態(tài)代理
1、靜態(tài)代理
靜態(tài)代理角色分析:
- 抽象角色 :一般使用接口或者抽象類來實(shí)現(xiàn)。
- 真實(shí)角色 :被代理的角色。
- 代理角色: 代理真實(shí)角色 , 代理真實(shí)角色后 ,一般會做一些附屬的操作。
- 調(diào)用方:使用代理角色來進(jìn)行一些操作。
我們以租客租客租房子為例,涉及到的對象有:租客、中介、房東。(房東即為被代理對象,中介即為代理對象)
租客通過中介之手租住房東的房子,代理對象中介需要尋找租客租房,并從中獲取中介費(fèi)用。
代碼實(shí)現(xiàn):
Rent.java 即抽象角色
// 抽象角色:租房
public interface Rent {
public void rent();
}
Host.java 即真實(shí)角色
// 真實(shí)角色: 房東,房東要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
Proxy.java 即代理角色
//代理角色:中介
public class Proxy implements Rent {
private Host host;
public Proxy() { }
public Proxy(Host host) {
this.host = host;
}
// 租房
public void rent(){
seeHouse();
host.rent();
fare();
}
// 看房
public void seeHouse(){
System.out.println("帶房客看房");
}
// 收中介費(fèi)
public void fare(){
System.out.println("收中介費(fèi)");
}
}
Client.java 調(diào)用方,即客戶
// 客戶類,一般客戶都會去找代理!
public class Client {
public static void main(String[] args) {
// 房東要租房
Host host = new Host();
// 中介幫助房東
Proxy proxy = new Proxy(host);
// 你去找中介!
proxy.rent();
}
}
靜態(tài)代理的缺點(diǎn):
需要手動(dòng)創(chuàng)建代理類,如果需要代理的對象多了,那么代理類也越來越多。
為了解決,這個(gè)問題,就有了動(dòng)態(tài)代理 !
2、動(dòng)態(tài)代理
說到動(dòng)態(tài)代理,面試的時(shí)候肯定會問動(dòng)態(tài)代理的兩種實(shí)現(xiàn)方式:
先來看公共的 UserService 接口,和 UserServiceImpl 實(shí)現(xiàn)類:
/**
* @author csp
* @date 2021-06-03
*/
public interface UserService {
/**
* 登錄
*/
void login();
/**
* 登出
*/
void logout();
}
/**
* @author csp
* @date 2021-06-03
*/
public class UserServiceImpl implements UserService{
@Override
public void login() {
System.out.println("用戶登錄...");
}
@Override
public void logout() {
System.out.println("用戶推出登錄...");
}
}
JDK 動(dòng)態(tài)代理
代碼如下:
/**
* @author csp
* @date 2021-06-03
*/
public class JDKProxyFactory implements InvocationHandler {
// 目標(biāo)對象(被代理對象)
private Object target;
public JDKProxyFactory(Object target) {
super();
this.target = target;
}
/**
* 創(chuàng)建代理對象
*
* @return
*/
public Object createProxy() {
// 1.得到目標(biāo)對象的類加載器
ClassLoader classLoader = target.getClass().getClassLoader();
// 2.得到目標(biāo)對象的實(shí)現(xiàn)接口
Class<?>[] interfaces = target.getClass().getInterfaces();
// 3.第三個(gè)參數(shù)需要一個(gè)實(shí)現(xiàn)invocationHandler接口的對象
Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this);
return newProxyInstance;
}
/**
* 真正執(zhí)行代理增強(qiáng)的方法
*
* @param proxy 代理對象.一般不使用
* @param method 需要增強(qiáng)的方法
* @param args 方法中的參數(shù)
* @return
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK 動(dòng)態(tài)代理:登錄/登出前邏輯校驗(yàn)......");
Object invoke = method.invoke(target, args);
System.out.println("JDK 動(dòng)態(tài)代理:登錄/登出后日志打印......");
return invoke;
}
public static void main(String[] args) {
// 1.創(chuàng)建對象
UserServiceImpl userService = new UserServiceImpl();
// 2.創(chuàng)建代理對象
JDKProxyFactory jdkProxyFactory = new JDKProxyFactory(userService);
// 3.調(diào)用代理對象的增強(qiáng)方法,得到增強(qiáng)后的對象
UserService userServiceProxy = (UserService) jdkProxyFactory.createProxy();
userServiceProxy.login();
System.out.println("==================================");
userServiceProxy.logout();
}
}
輸出結(jié)果如下:
JDK 動(dòng)態(tài)代理:登錄/登出前邏輯校驗(yàn)......
用戶登錄...
JDK 動(dòng)態(tài)代理:登錄/登出后日志打印......
==================================
JDK 動(dòng)態(tài)代理:登錄/登出前邏輯校驗(yàn)......
用戶推出登錄...
JDK 動(dòng)態(tài)代理:登錄/登出后日志打印......
CGLIB 動(dòng)態(tài)代理
代碼如下:
/**
* @author csp
* @date 2021-06-03
*/
public class CglibProxyFactory implements MethodInterceptor {
// 目標(biāo)對象(被代理對象)
private Object target;
// 使用構(gòu)造方法傳遞目標(biāo)對象
public CglibProxyFactory(Object target) {
super();
this.target = target;
}
/**
* 創(chuàng)建代理對象
*
* @return
*/
public Object createProxy() {
// 1.創(chuàng)建Enhancer
Enhancer enhancer = new Enhancer();
// 2.傳遞目標(biāo)對象的class
enhancer.setSuperclass(target.getClass());
// 3.設(shè)置回調(diào)操作
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 真正執(zhí)行代理增強(qiáng)的方法
* @param o 代理對象
* @param method 要增強(qiáng)的方法
* @param objects 要增強(qiáng)方法的參數(shù)
* @param methodProxy 要增強(qiáng)的方法的代理
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib 動(dòng)態(tài)代理:登錄/登出前邏輯校驗(yàn)......");
Object invoke = method.invoke(target, objects);
System.out.println("cglib 動(dòng)態(tài)代理:登錄/登出后日志打印......");
return invoke;
}
public static void main(String[] args) {
// 1.創(chuàng)建對象
UserServiceImpl userService = new UserServiceImpl();
// 2.創(chuàng)建代理對象
CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(userService);
// 3.調(diào)用代理對象的增強(qiáng)方法,得到增強(qiáng)后的對象
UserService userServiceProxy = (UserService) cglibProxyFactory.createProxy();
userServiceProxy.login();
System.out.println("==================================");
userServiceProxy.logout();
}
}
測試結(jié)果如下:
cglib 動(dòng)態(tài)代理:登錄/登出前邏輯校驗(yàn)......
用戶登錄...
cglib 動(dòng)態(tài)代理:登錄/登出后日志打印......
==================================
cglib 動(dòng)態(tài)代理:登錄/登出前邏輯校驗(yàn)......
用戶推出登錄...
cglib 動(dòng)態(tài)代理:登錄/登出后日志打印......
面試題一:JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理區(qū)別?
① JDK 動(dòng)態(tài)代理本質(zhì)上是實(shí)現(xiàn)了被代理對象的接口,而 CGLib 本質(zhì)上是繼承了被代理對象,覆蓋其中的方法。
② JDK 動(dòng)態(tài)代理只能對實(shí)現(xiàn)了接口的類生成代理,CGLib 則沒有這個(gè)限制。但是 CGLib 因?yàn)槭褂美^承實(shí)現(xiàn),所以 CGLib 所以無法對 final 類、private 方法和 static方法進(jìn)行代理。
③ JDK 動(dòng)態(tài)代理是 JDK 里自帶的,CGLib 動(dòng)態(tài)代理需要引入第三方的 jar 包。
④ 在調(diào)用代理方法上,JDK動(dòng)態(tài)代理是通過反射機(jī)制調(diào)用,CGLib 是通過 FastClass 機(jī)制直接調(diào)用。(看過一篇文章,介紹說 FastClass 簡單的理解,就是使用一個(gè) index 下標(biāo)作為入?yún)?,可以直接定位到要調(diào)用的方法直接,并進(jìn)行調(diào)用)
在性能上,JDK1.7 之前,由于使用了 FastClass 機(jī)制,CGLib 在執(zhí)行效率上比 JDK 快,但是隨著 JDK 動(dòng)態(tài)代理的不斷優(yōu)化,從 JDK 1.7 開始,JDK 動(dòng)態(tài)代理已經(jīng)明顯比 CGLib 更快了。
面試題二:JDK 動(dòng)態(tài)代理為什么只能對實(shí)現(xiàn)了接口的類生成代理?
根本原因是通過 JDK 動(dòng)態(tài)代理生成的類已經(jīng)繼承了 Proxy 類,所以無法再使用繼承的方式去對類實(shí)現(xiàn)代理。
總結(jié)
文章會不定時(shí)更新,有時(shí)候一天多更新幾篇,如果幫助您復(fù)習(xí)鞏固了知識點(diǎn),還請三連支持一下,后續(xù)會一點(diǎn)點(diǎn)的更新!希望大家多多關(guān)注腳本之家的其他內(nèi)容!
相關(guān)文章
mybatis的ParamNameResolver參數(shù)名稱解析
這篇文章主要為大家介紹了mybatis的ParamNameResolver參數(shù)名稱解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
SpringBoot工程Docker多環(huán)境中使用同一個(gè)Jar包解決方案
在Docker多環(huán)境部署中,SpringBoot工程可以通過環(huán)境變量來動(dòng)態(tài)改變配置,無需重新打包,利用volume掛載或docker?cp命令,可以將配置文件直接傳入容器,提高部署效率,并保證安全性2024-09-09
Spring Boot中slf4j日志依賴關(guān)系示例詳解
在項(xiàng)目開發(fā)中,記錄日志是必做的一件事情。而當(dāng)我們使用Springboot框架時(shí),記錄日志就變得極其簡單了。下面這篇文章主要給大家介紹了關(guān)于Spring Boot中slf4j日志依賴關(guān)系的相關(guān)資料,需要的朋友可以參考下2018-11-11
解析Spring中@Controller@Service等線程安全問題
這篇文章主要為大家介紹解析了Spring中@Controller@Service等線程的安全問題,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
一文探究ArrayBlockQueue函數(shù)及應(yīng)用場景
這篇文章主要為大家介紹了一文探究ArrayBlockQueue函數(shù)及應(yīng)用場景,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
Spring?Boot?優(yōu)雅整合多數(shù)據(jù)源
這篇文章主要介紹了Spring?Boot?優(yōu)雅整合多數(shù)據(jù)源,多數(shù)據(jù)源就是在一個(gè)單一應(yīng)用中涉及到了兩個(gè)及以上的數(shù)據(jù)庫,更多相關(guān)內(nèi)容需要的小伙伴可以參考下面文章介紹2022-05-05

