昨天有個(gè)大牛說我啰嗦,眼光比較細(xì)碎,看不到重點(diǎn)。太他爺爺?shù)挠械览砹耍∫f看人品,還是女孩子強(qiáng)一些。
原來記得看到一個(gè)男孩子的抱怨,說怎么兩人剛剛開始在一起,女孩子在心里就已經(jīng)和他過完了一輩子。哥哥們,不想這么遠(yuǎn)行嗎?看看何潔,看看帶著倆娃跳樓的媽媽。
所以現(xiàn)在的女孩子是很明白的,有些男孩子個(gè)子不高,其貌不揚(yáng),但是一看那人品氣質(zhì)就知道能找個(gè)不錯(cuò)的女盆友。不過要說看人的技術(shù)能力,男孩子確實(shí)更勝一籌,咱得努力了。
總結(jié)一下要形成的習(xí)慣:
- 有空時(shí)隔一段時(shí)間要做幾道算法題,C語言和JAVA都可以,主要是訓(xùn)練思維。
- 定期閱讀spring的源碼。因?yàn)閟pring是框架,重設(shè)計(jì),能夠培養(yǎng)大局觀
- 閱讀底層的書籍,如linux方面,虛擬機(jī)方面,這是內(nèi)功。越高級(jí)的語言只是招式。
- 不要忘記做了一半的東西,如搜索引擎方面,redis方面,可以過一段時(shí)間再做,因?yàn)榈綍r(shí)候自己的境界有提升,深入程度也會(huì)有所增加。
下面是今天的正題。我也很菜,看源碼也很費(fèi)力,所以都會(huì)從最容易的入手。先了解其原理,再去看源碼。
看源碼看熟了,以后再遇到問題,就可以通過源碼去了解原理了。
spring的AOP,原理懂了,代碼相當(dāng)簡單。這也是為什么我記得我還是個(gè)菜鳥的時(shí)候,面試人家經(jīng)常問我這個(gè)。
先有個(gè)大局觀,畫張整體的spring結(jié)構(gòu)圖。以下是備受吐槽的手繪時(shí)間:
如果你覺得我左手字寫的實(shí)在是不能再難看了的話,我有空可以展示一下右手字
天生做不好的兩件事:寫不好字,梳不整齊頭發(fā)。自我感覺最近梳頭技術(shù)有所改觀。
AOP面向切面編程是面向?qū)ο蟮难a(bǔ)充。它利用一種橫切技術(shù),將一些公共行為封裝成叫做“方面”的可重用模塊,解耦,增加可維護(hù)性。
AOP將系統(tǒng)分為核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)兩部分。核心關(guān)注點(diǎn)就是主業(yè)務(wù)流程,橫切關(guān)注點(diǎn)就是上面提到的“方面”。
那么看AOP的源碼就是要看橫切關(guān)注點(diǎn)是怎樣和核心關(guān)注點(diǎn)整合來發(fā)揮作用的。
主業(yè)務(wù)流程歸根到底是一個(gè)java方法,而且是對(duì)象的方法。在AOP中被稱為被通知或被代理對(duì)象POJO。
AOP的作用就是將核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)組合起來,術(shù)語叫做“增強(qiáng)”。最后實(shí)際用的是增強(qiáng)后的代理對(duì)象。
對(duì)核心關(guān)注點(diǎn)進(jìn)行增強(qiáng)就涉及到在哪些地方增強(qiáng)的問題。如方法調(diào)用或者異常拋出時(shí)做增強(qiáng)這些時(shí)機(jī)叫做連接點(diǎn)Joinpoint。一個(gè)通知將被引發(fā)的連接點(diǎn)集合叫做切入點(diǎn),理解時(shí)就可以想正則表達(dá)式,通配符來指定多個(gè),而不是單單一個(gè)連接點(diǎn)。
在連接點(diǎn)都做了哪些增強(qiáng)呢?增強(qiáng)的內(nèi)容AOP術(shù)語叫“通知”Advice。
Spring里定義了四種Advice:BeforeAdvice,AfterAdvice,ThrowAdvice,DynamicIntroducationAdvice。
許多AOP框架包括spring都是以攔截器作為通知模型。維護(hù)一個(gè)圍繞連接點(diǎn)的攔截器鏈。其中DynamicIntroducationAdvice是可以引入方法或者字段到核心關(guān)注點(diǎn)。
這里有個(gè)Introduction,AOP術(shù)語叫引入。將增強(qiáng)后的AOP代理組裝到系統(tǒng)叫做織入。
上面就是AOP的核心概念了。總結(jié)一下:
AOP要做的事情就是:生成代理對(duì)象,然后織入。
生成代理對(duì)象是經(jīng)常會(huì)被問到的一個(gè)問題:Spring提供了兩種方式來生成代理對(duì)象,JDKProxy和Cglib。
具體使用哪種方式由AopProxyFactory根據(jù)AdvisedSupport對(duì)象的配置來決定。
默認(rèn)的策略是如果目標(biāo)類是接口,則使用JDK動(dòng)態(tài)代理技術(shù),否則使用Cglib來生成代理。
Cglib是基于字節(jié)碼技術(shù)的,使用的是ASM。asm是一個(gè)java字節(jié)碼操縱框架,它能被用來動(dòng)態(tài)生成類或者增強(qiáng)既有類的功能。
ASM可以直接產(chǎn)生二進(jìn)制class文件,也可以在類被加載入JVM之前動(dòng)態(tài)改變類行為。
下面重點(diǎn)來看看JDK動(dòng)態(tài)代理技術(shù)。這是我還是個(gè)很菜很菜的菜鳥時(shí)為數(shù)不多能看懂的源碼。因?yàn)橹翱催^Java設(shè)計(jì)模式,寫過類似的例子,所以會(huì)比較順暢。今天先講這一部分。
下面是調(diào)用測試類:
package dynamic.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 實(shí)現(xiàn)自己的InvocationHandler
* @author zyb
* @since 2012-8-9
*
*/
public class MyInvocationHandler implements InvocationHandler {
// 目標(biāo)對(duì)象
private Object target;
/**
* 構(gòu)造方法
* @param target 目標(biāo)對(duì)象
*/
public MyInvocationHandler(Object target) {
super();
this.target = target;
}
/**
* 執(zhí)行目標(biāo)對(duì)象的方法
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在目標(biāo)對(duì)象的方法執(zhí)行之前簡單的打印一下
System.out.println("------------------before------------------");
// 執(zhí)行目標(biāo)對(duì)象的方法
Object result = method.invoke(target, args);
// 在目標(biāo)對(duì)象的方法執(zhí)行之后簡單的打印一下
System.out.println("-------------------after------------------");
return result;
}
/**
* 獲取目標(biāo)對(duì)象的代理對(duì)象
* @return 代理對(duì)象
*/
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(), this);
}
}
package dynamic.proxy;
/**
* 目標(biāo)對(duì)象實(shí)現(xiàn)的接口,用JDK來生成代理對(duì)象一定要實(shí)現(xiàn)一個(gè)接口
* @author zyb
* @since 2012-8-9
*
*/
public interface UserService {
/**
* 目標(biāo)方法
*/
public abstract void add();
}
package dynamic.proxy;
/**
* 目標(biāo)對(duì)象
* @author zyb
* @since 2012-8-9
*
*/
public class UserServiceImpl implements UserService {
/* (non-Javadoc)
* @see dynamic.proxy.UserService#add()
*/
public void add() {
System.out.println("--------------------add---------------");
}
}
package dynamic.proxy;
import org.junit.Test;
/**
* 動(dòng)態(tài)代理測試類
* @author zyb
* @since 2012-8-9
*
*/
public class ProxyTest {
@Test
public void testProxy() throws Throwable {
// 實(shí)例化目標(biāo)對(duì)象
UserService userService = new UserServiceImpl();
// 實(shí)例化InvocationHandler
MyInvocationHandler invocationHandler = new MyInvocationHandler(userService);
// 根據(jù)目標(biāo)對(duì)象生成代理對(duì)象
UserService proxy = (UserService) invocationHandler.getProxy();
// 調(diào)用代理對(duì)象的方法
proxy.add();
}
}
執(zhí)行結(jié)果如下:
------------------before---------------
--------------------add---------------
-------------------after-----------------
很簡單,核心就是 invocationHandler.getProxy();這個(gè)方法調(diào)用的Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),target.getClass().getInterfaces(), this); 怎么生成對(duì)象的。
/**
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler.
*
*
{@code Proxy.newProxyInstance} throws
* {@code IllegalArgumentException} for the same reasons that
* {@code Proxy.getProxyClass} does.
*
* @param loader the class loader to define the proxy class
* @param interfaces the list of interfaces for the proxy class
* to implement
* @param h the invocation handler to dispatch method invocations to
* @return a proxy instance with the specified invocation handler of a
* proxy class that is defined by the specified class loader
* and that implements the specified interfaces
* @throws IllegalArgumentException if any of the restrictions on the
* parameters that may be passed to {@code getProxyClass}
* are violated
* @throws SecurityException if a security manager, s, is present
* and any of the following conditions is met:
*
*
the given {@code loader} is {@code null} and
* the caller's class loader is not {@code null} and the
* invocation of {@link SecurityManager#checkPermission
* s.checkPermission} with
* {@code RuntimePermission("getClassLoader")} permission
* denies access;
*
for each proxy interface, {@code intf},
* the caller's class loader is not the same as or an
* ancestor of the class loader for {@code intf} and
* invocation of {@link SecurityManager#checkPackageAccess
* s.checkPackageAccess()} denies access to {@code intf};
*
any of the given proxy interfaces is non-public and the
* caller class is not in the same {@linkplain Package runtime package}
* as the non-public interface and the invocation of
* {@link SecurityManager#checkPermission s.checkPermission} with
* {@code ReflectPermission("newProxyInPackage.{package name}")}
* permission denies access.
*
* @throws NullPointerException if the {@code interfaces} array
* argument or any of its elements are {@code null}, or
* if the invocation handler, {@code h}, is
* {@code null}
*/
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
最后附上這四個(gè)項(xiàng)目的百度網(wǎng)盤地址:鏈接:https://pan.baidu.com/s/1Q1R9I7IkQJ78sJUaokD04這個(gè)代碼是JDK1.8中的,里面用到了1.8的一些語法,如果不太了解,建議先看看這本書。
代碼看著不少,實(shí)際上都在進(jìn)行一些安全校驗(yàn),包裝之類的,真正有用的就兩句:
Class cl = getProxyClass0(loader, intfs);這句話查找或者生成代理類。跟進(jìn)去:
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class getProxyClass0(ClassLoader loader,
Class... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
對(duì),就是從緩存里把接口拿將出來。然后用return cons.newInstance(new Object[]{h}) 這一句將接口用invocationHandler進(jìn)行包裝。
具體源碼可以跟進(jìn)去看,不詳述。想必看到這里,JDK動(dòng)態(tài)代理的原理都已經(jīng)很明白了。
這里要說一點(diǎn)理論性的東西:
- AOP解決的問題往往可以用代理模式來解決。Java開發(fā)中常說動(dòng)態(tài)代理和靜態(tài)代理,而AOP就是動(dòng)態(tài)代理,因?yàn)榇淼念愂窃谶\(yùn)行時(shí)才生成的。
- 而一般說的代理模式寫成的代碼是編譯期就已經(jīng)生成的,叫靜態(tài)代理。
最后
針對(duì)于上面的面試題我總結(jié)出了互聯(lián)網(wǎng)公司java程序員面試涉及到的絕大部分面試題及答案做成了文檔和架構(gòu)視頻資料免費(fèi)分享給大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并發(fā)等架構(gòu)技術(shù)資料),希望能幫助到您面試前的復(fù)習(xí)且找到一個(gè)好的工作,也節(jié)省大家在網(wǎng)上搜索資料的時(shí)間來學(xué)習(xí).
資料領(lǐng)取方式:
加群:219571750,領(lǐng)取往期Java高級(jí)架構(gòu)資料、源碼、筆記、視頻
Dubbo、Redis、設(shè)計(jì)模式、Netty、zookeeper、Spring cloud、分布式、
高并發(fā)等架構(gòu)技術(shù)