我們都知道SpringAOP是用代理模式實現,那麼到底是如何實現的呢?我們來探一探究竟,並手寫部分代碼。
定義
代理模式(Proxy Pattern):是指爲其他對象提供一種代理、來控制這個對象的訪問。
代理對象在客戶端和目標對象之間起到一種中介作用。
目的:
1.保護目標對象:
我們不直接訪問被代理對象,而是通過中間媒介去訪問。
2.增強目標對象:
參考SpringAop的功能。
場景:
生活中場景:
租房中介、售票黃牛、婚介、經紀人、快遞、事務代理、非侵入式日誌監聽等,這些都是代理模式的實際體現。
如何理解:
租房,房屋擁有者將想出租自己多餘的房子,將房子託付給房屋中介,房屋中介就代理了這個房子,我們如果想要租房子就直接去找中介即可。
代理模式主要就是增加一箇中間層,來實現代理與被代理之間的組合。
我們來看看類圖結構:
Subject是頂層接口,RealSubject是真實的對象(被代理對象),Proxy是代理對象,代理對象擁有被代理對象的引用,客戶端調用代理對象方法,同時也調用了被代理對象方法,但是在代理對象前後增加一些處理。在代碼編寫中,我們想到代理就會理解爲是代碼增強,其實就是在原本邏輯上增加一些前後邏輯,而調用者感知不到。代理模式屬性結構型代理,有靜態代理和動態代理
靜態代理
這裏使用業務場景來幫助理解:
在分佈式業務場景中,我們通常會對數據庫進行分庫分表,分庫分表之後使用Java操作時,就可能需要配置多個數據源,我們通過設置數據源路由來動態切換數據源。
1.創建訂單實體Order:
@Data
public class Order {
//訂單信息
private Object orderInfo;
//訂單創建時間
private Long createTime;
private String id;
}
2.創建OrderDao
//持久層,這裏就直接寫實現類了。
public class OrderDao {
public int insert(Order order){
System.out.println("OrderDao 創建Order成功");
return 1;
}
}
3.創建IOrderService接口
public interface IOrderService {
int createOrder(Order order);
}
4.創建OrderService實現類
public class OrderService implements IOrderService{
private OrderDao orderDao;
//模擬spring的注入 直接創建對象
public OrderService(){
orderDao = new OrderDao();
}
@Override
public int createOrder(Order order) {
System.out.println("調用OrderDao 創建訂單");
return orderDao.insert(order);
}
}
咱們使用靜態代理來進行切換數據源,依據開閉原則,寫好的代理邏輯不去改變,通過代理對象來完成。先創建數據源路由對象,我們使用ThreadLocal的單例實現,DynamicDataSourceEntry類
5.DynamicDataSourceEntry
//動態切換數據源
public class DynamicDataSourceEntry {
// 默認數據源
public final static String DEFAULT_SOURCE = null;
private final static ThreadLocal<String> local = new ThreadLocal<String>();
private DynamicDataSourceEntry(){}
//清空數據源
public static void clear() {
local.remove();
}
//獲取當前正在使用的數據源名字
public static String get() { return local.get(); }
//還原當前切面的數據源
public static void restore() { local.set(DEFAULT_SOURCE); }
//設置已知名字的數據源
public static void set(String source) { local.set(source); }
//根據年份動態設置數據源
public static void set(int year) { local.set("DB_" + year); }
}
6.創建代理對象OrderServiceStaticProx 實現被代理的對象。
public class OrderServiceStaticProx implements IOrderService {
//日期格式化
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
//代理對象
private IOrderService orderService;
//模擬spring注入
public OrderServiceStaticProx(IOrderService orderService) {
this.orderService = orderService;
}
public int createOrder(Order order) {
before();
Long time = order.getCreateTime();
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("靜態代理類自動分配到【DB_" + dbRouter + "】數據源處理數據。");
//設置數據源
DynamicDataSourceEntry.set(dbRouter);
orderService.createOrder(order);
after();
return 0;
}
private void before() {
System.out.println("Proxy before method.");
}
private void after() {
System.out.println("Proxy after method.");
}
}
測試代碼:
public static void main(String[] args) throws ParseException {
Order order = new Order();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date date = sdf.parse("2020/03/11");
order.setCreateTime(date.getTime());
IOrderService orderService = new OrderServiceStaticProx(new OrderService());
orderService.createOrder(order);
}
測試結果:
Proxy before method.
靜態代理類自動分配到【DB_2020】數據源處理數據。
調用OrderDao 創建訂單
OrderDao 創建Order成功
Proxy after method.
符合我們預期的效果,通過代理來達到切換數據源的效果,並增加了before和after方法。
我們看一看類圖是否符合我們的上文中寫到的:
動態代理
動態代理實際上思路是與靜態代理一致的,只不過動態代理的功能更強大,擴展性更強:
我們來看一看JDK動態代理的實現原理:
1、拿到被代理類的引用,並且獲取它的所有的接口(反射獲取)。
2、JDK Proxy類重新生成一個新的類,實現了被代理類所有 接口的方法。
3、動態生成Java代碼,把增強邏輯加入到新生成代碼中。
4、編譯生成新的Java代碼的class文件。
5、加載並重新運行新的class,得到類就是全新類。
我們按照以上思路來對上文靜態代理做一些改進:修改OrderServiceStaticProx類
public class OrderServiceDynamicProx implements InvocationHandler {
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
private Object target;
public Object getInstance(Object target) {
this.target = target;
//獲得對象
Class<?> clazz = target.getClass();
//獲得所有接口和方法
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//執行前邏輯
before(args[0]);
//被代理對象方法
Object object = method.invoke(target, args);
//執行後邏輯
after();
return object;
}
private void before(Object target) {
try {
System.out.println("Proxy before method.");
Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("動態代理類自動分配到【DB_" + dbRouter + "】數據源處理數據。");
DynamicDataSourceEntry.set(dbRouter);
} catch (Exception e) {
e.printStackTrace();
}
}
private void after() {
System.out.println("Proxy after method.");
}
}
測試代碼:
public static void main(String[] args) {
try {
Order order = new Order();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date date = sdf.parse("2020/03/31");
order.setCreateTime(date.getTime());
IOrderService orderService = (IOrderService) new OrderServiceDynamicProx().getInstance(new OrderService());
orderService.createOrder(order);
} catch (Exception e) {
e.printStackTrace();
}
}
測試結果
Proxy before method.
動態代理類自動分配到【DB_2020】數據源處理數據。
調用OrderDao 創建訂單
OrderDao 創建Order成功
Proxy after method.
達到了與靜態代理一樣的功能。但是,動態代理實現之後,我們不僅能實現Order的數據源動態路由,還可以實現其他任何類的數據源路由。
代理模式與Spring
Spring中代理核心類ProxyFactoryBean,核心方法getObject:我們首先來看看源碼:
@Override
@Nullable
public Object getObject() throws BeansException {
initializeAdvisorChain();
if (isSingleton()) {
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
return newPrototypeInstance();
}
}
在getObject()方法中,主要調用getSingletonInstance()和newPrototypeInstance(),在Spring的配置中,如果不做任何設置,那麼Spring代理生成的Bean都是單例對象。如果修改scope則每次創建一個新的原型對象.
Spring利用動態代理實現AOP有兩個非常重要的類,一個是JdkDynamicAopProxy類和CglibAopProxy類,來看一下類圖:
Spring中的代理選擇原則
1、當Bean有實現接口時,Spring就會用JDK的動態代理
2、當Bean沒有實現接口時,Spring選擇CGLib。
3、Spring可以通過配置強制使用CGLib,只需在Spring的配置文件中加入如下代碼:
<aop:aspectj-autoproxy proxy-target-class="true"/>
關與JDK的代理和CGLib我會另外整理單獨記錄,到時候會及時補上,還請見諒。
代理模式優缺點:
優點:
1、代理模式能將代理對象與真實被調用的目標對象分離。
2、一定程度上降低了系統的耦合度,擴展性好。
3、可以起到保護目標對象的作用。
4、可以對目標對象的功能增強。
缺點:
1、代理模式會造成系統設計中類的數量增加。
2、在客戶端和目標對象增加一個代理對象,會造成請求處理速度變慢。
3、增加了系統的複雜度。
望各位看過的夥伴們如果發現了問題能夠及時批評指正,在此感謝。