最近在開發中遇到了一個恰好能夠用AOP實現的例子,就順便研究了AOP的實現原理,把學習到的東西進行一個總結。文章中用到的編程語言爲kotlin,須要的能夠在IDEA中直接轉爲java。 這篇文章將會按照以下目錄展開:java
相信你們或多或少的瞭解過AOP,都知道它是面向切面編程,在網上搜索能夠找到不少的解釋。這裏我用一句話來總結:AOP是可以讓咱們在不影響原有功能的前提下,爲軟件橫向擴展功能。 那麼橫向擴展怎麼理解呢,咱們在WEB項目開發中,一般都遵照三層原則,包括控制層(Controller)->業務層(Service)->數據層(dao),那麼從這個結構下來的爲縱向,它具體的某一層就是咱們所說的橫向。咱們的AOP就是能夠做用於這某一個橫向模塊當中的全部方法。spring
咱們在來看一下AOP和OOP的區別:AOP是OOP的補充,當咱們須要爲多個對象引入一個公共行爲,好比日誌,操做記錄等,就須要在每一個對象中引用公共行爲,這樣程序就產生了大量的重複代碼,使用AOP能夠完美解決這個問題。編程
接下來介紹一下提到AOP就必需要了解的知識點:編程語言
上面已經大概的介紹了AOP中須要瞭解的基本知識,也知道了AOP的好處,那怎麼在代碼中實現呢?給你們舉個例子:咱們如今有個學校管理系統,已經實現了對老師和學生的增刪改,又新來個需求,說是對老師和學生的每次增刪改作一個記錄,到時候校長能夠查看記錄的列表。那麼問題來了,怎麼樣處理是最好的解決辦法呢?這裏我羅列了三種解決辦法,咱們來看下他的優缺點。函數
-最簡單的就是第一種方法,咱們直接在每次的增刪改的函數當中直接實現這個記錄的方法,這樣代碼的重複度過高,耦合性太強,不建議使用。-其次就是咱們最長使用的,將記錄這個方法抽離出來,其餘的增刪改調用這個記錄函數便可,顯然代碼重複度下降,可是這樣的調用仍是沒有下降耦合性。源碼分析
-這個時候咱們想一下AOP的定義,再想一想咱們的場景,其實咱們就是要在不改變原來增刪改的方法,給這個系統增長記錄的方法,並且做用的也是一個層面的方法。這個時候咱們就能夠採用AOP來實現了。學習
咱們來看下代碼的具體實現:測試
@Target(AnnotationTarget.FUNCTION) //註解做用的範圍,這裏聲明爲函數
@Order(Ordered.HIGHEST_PRECEDENCE) //聲明註解的優先級爲最高,假設有多個註解,先執行這個
annotation class Hanler(val handler: HandlerType) //自定義註解類,HandlerType是一個枚舉類型,裏面定義的就是學生和老師的增刪改操做,在這裏就不展現具體內容了
複製代碼
@Aspect //該註解聲明這個類爲一個切面類
@Component
class HandlerAspect{
@Autowired
private lateinit var handlerService: HandlerService
@AfterReturning("@annotation(handler)") //當有函數註釋了註解,將會在函數正常返回後在執行咱們定義的方法
fun hanler(hanler: Hanler) {
handlerService.add(handler.operate.value) //這裏是真正執行記錄的方法
}
}
複製代碼
/** * 刪除學生方法 */
@Handler(operate= Handler.STUDENT_DELETE) //當執行到刪除學生方法時,切面類就會起做用了,當學生正常刪除後就會執行記錄方法,咱們就能夠看到記錄方法生成的數據
fun delete(id:String) {
studentService.delete(id)
}
複製代碼
咱們如今瞭解了代碼中如何實現,那麼AOP實現的原理是什麼呢?以前看了一個博客說到,提到AOP你們都知道他的實現原理是動態代理,顯然我以前就是不知道的,哈哈,可是相信閱讀文章的大家必定是知道的。優化
講到動態代理就不得不說代理模式了, 代理模式的定義:給某一個對象提供一個代理,並由代理對象控制對原對象的引用。代理模式包含以下角色:subject:抽象主題角色,是一個接口。該接口是對象和它的代理共用的接口; RealSubject:真實主題角色,是實現抽象主題接口的類; Proxy:代理角色,內部含有對真實對象RealSubject的引用,從而能夠操做真實對象。代理對象提供與真實對象相同的接口,以便代替真實對象。同時,代理對象能夠在執行真實對象操做時,附加其餘的操做,至關於對真實對象進行封裝。以下圖所示: ui
那麼代理又分爲靜態代理和動態代理,這裏寫兩個小的demo,動態代理採用的就是JDK代理。舉個例子就是如今一個班上的學生須要交做業,如今由班長代理交做業,那麼班長就是代理,學生就是被代理的對象。
首先,咱們建立一個Person接口。這個接口就是學生(被代理類),和班長(代理類)的公共接口,他們都有交做業的行爲。這樣,學生交做業就可讓班長來代理執行。
/** * Created by Mapei on 2018/11/7 * 建立person接口 */
public interface Person {
//交做業
void giveTask();
}
複製代碼
Student類實現Person接口,Student能夠具體實施交做業這個行爲。
/** * Created by Mapei on 2018/11/7 */
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
public void giveTask() {
System.out.println(name + "交語文做業");
}
}
複製代碼
StudentsProxy類,這個類也實現了Person接口,可是還另外持有一個學生類對象,那麼他能夠代理學生類對象執行交做業的行爲。
/** * Created by Mapei on 2018/11/7 * 學生代理類,也實現了Person接口,保存一個學生實體,這樣就能夠代理學生產生行爲 */
public class StudentsProxy implements Person{
//被代理的學生
Student stu;
public StudentsProxy(Person stu) {
// 只代理學生對象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理交做業,調用被代理學生的交做業的行爲
public void giveTask() {
stu.giveTask();
}
}
複製代碼
下面測試一下,看代理模式如何使用:
/** * Created by Mapei on 2018/11/7 */
public class StaticProxyTest {
public static void main(String[] args) {
//被代理的學生林淺,他的做業上交有代理對象monitor完成
Person linqian = new Student("林淺");
//生成代理對象,並將林淺傳給代理對象
Person monitor = new StudentsProxy(linqian);
//班長代理交做業
monitor.giveTask();
}
}
複製代碼
運行結果:
這裏並無直接經過林淺(被代理對象)來執行交做業的行爲,而是經過班長(代理對象)來代理執行了。這就是代理模式。代理模式就是在訪問實際對象時引入必定程度的間接性,這裏的間接性就是指不直接調用實際對象的方法,那麼咱們在代理過程當中就能夠加上一些其餘用途。好比班長在幫林淺交做業的時候想告訴老師最近林淺的進步很大,就能夠輕鬆的經過代理模式辦到。在代理類的交做業以前加入方法便可。這個優勢就能夠運用在spring中的AOP,咱們能在一個切點以前執行一些操做,在一個切點以後執行一些操做,這個切點就是一個個方法。這些方法所在類確定就是被代理了,在代理過程當中切入了一些其餘操做。
動態代理和靜態代理的區別是,靜態代理的的代理類是咱們本身定義好的,在程序運行以前就已經變異完成,可是動態代理的代理類是在程序運行時建立的。相比於靜態代理,動態代理的優點在於能夠很方便的對代理類的函數進行統一的處理,而不用修改每一個代理類中的方法。好比咱們想在每一個代理方法以前都加一個處理方法,咱們上面的例子中只有一個代理方法,若是還有不少的代理方法,就太麻煩了,咱們來看下動態代理是怎麼去實現的。
首先仍是定義一個Person接口:
/** * Created by Mapei on 2018/11/7 * 建立person接口 */
public interface Person {
//交做業
void giveTask();
}
複製代碼
接下來是建立須要被代理的實際類,也就是學生類:
/** * Created by Mapei on 2018/11/7 */
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
public void giveTask() {
System.out.println(name + "交語文做業");
}
}
複製代碼
建立StuInvocationHandler類,實現InvocationHandler接口,這個類中持有一個被代理對象的實例target。InvocationHandler中有一個invoke方法,全部執行代理對象的方法都會被替換成執行invoke方法。
/** * Created by Mapei on 2018/11/7 */
public class StuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理對象
T target;
public StuInvocationHandler(T target) {
this.target = target;
}
/** * proxy:表明動態代理對象 * method:表明正在執行的方法 * args:表明調用目標方法時傳入的實參 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理執行" +method.getName() + "方法");
Object result = method.invoke(target, args);
return result;
}
}
複製代碼
那麼接下來咱們就能夠具體的建立代理對象了。
/** * Created by Mapei on 2018/11/7 * 代理類 */
public class ProxyTest {
public static void main(String[] args) {
//建立一個實例對象,這個對象是被代理的對象
Person linqian = new Student("林淺");
//建立一個與代理對象相關聯的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(linqian);
//建立一個代理對象stuProxy來代理linqian,代理對象的每一個執行方法都會替換執行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
//代理執行交做業的方法
stuProxy.giveTask();
}
}
複製代碼
咱們執行代理測試類,首先咱們建立了一個須要被代理的學生林淺,將林淺傳入stuHandler中,咱們在建立代理對象stuProxy時,將stuHandler做爲參數,那麼全部執行代理對象的方法都會被替換成執行invoke方法,也就是說,最後執行的是StuInvocationHandler中的invoke方法。因此在看到下面的運行結果也就理所固然了。
那麼到這裏問題就來了,爲何代理對象執行的方法都會經過InvocationHandler中的invoke方法來執行,帶着這個問題,咱們須要看一下動態代理的源碼,對他進行簡單的分析。
上面咱們使用Proxy類的newProxyInstance方法建立了一個動態代理對象,看一下他的源碼:
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<Void>() {
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);
}
}
複製代碼
而後,咱們須要重點關注Class<?> cl = getProxyClass0(loader, intfs)這句代碼,這裏產生了代理類,這個類就是動態代理的關鍵,因爲是動態生成的類文件,咱們將這個類文件打印到文件中。
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());
String path = "/Users/mapei/Desktop/okay/65707.class";
try{
FileOutputStream fos = new FileOutputStream(path);
fos.write(classFile);
fos.flush();
System.out.println("代理類class文件寫入成功");
}catch (Exception e) {
System.out.println("寫文件錯誤");
}
複製代碼
對這個class文件進行反編譯,咱們看看jdk爲咱們生成了什麼樣的內容:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;
public final class $Proxy0 extends Proxy implements Person
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/** *注意這裏是生成代理類的構造方法,方法參數爲InvocationHandler類型,看到這,是否是就有點明白 *爲什麼代理對象調用方法都是執行InvocationHandler中的invoke方法,而InvocationHandler又持有一個 *被代理對象的實例,就能夠去調用真正的對象實例。 */
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//這個靜態塊原本是在最後的,我把它拿到前面來,方便描述
static
{
try
{
//看看這兒靜態塊兒裏面的住giveTask經過反射獲得的名字m3,其餘的先無論
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("proxy.Person").getMethod("giveTask", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
/** * *這裏調用代理對象的giveMoney方法,直接就調用了InvocationHandler中的invoke方法,並把m3傳了進去。 *this.h.invoke(this, m3, null);咱們能夠對將InvocationHandler看作一箇中介類,中介類持有一個被代理對象,在invoke方法中調用了被代理對象的相應方法。經過聚合方式持有被代理對象的引用,把外部對invoke的調用最終都轉爲對被代理對象的調用。 */
public final void giveTask()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
}
複製代碼
看完了動態代理的源碼,咱們接下來就要看一下Spring中AOP實現的源碼是怎樣的?
protected Object createProxy(
Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
// 1.建立proxyFactory,proxy的生產主要就是在proxyFactory作的
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 2.將當前bean適合的advice,從新封裝下,封裝爲Advisor類,而後添加到ProxyFactory中
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
for (Advisor advisor : advisors) {
proxyFactory.addAdvisor(advisor);
}
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// 3.調用getProxy獲取bean對應的proxy
return proxyFactory.getProxy(getProxyClassLoader());
}
複製代碼
public Object getProxy(ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
// createAopProxy()方法就是決定究竟建立何種類型的proxy
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
// 關鍵方法createAopProxy()
return getAopProxyFactory().createAopProxy(this);
}
// createAopProxy()
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 1.config.isOptimize()是否使用優化的代理策略,目前使用與CGLIB
// config.isProxyTargetClass() 是否目標類自己被代理而不是目標類的接口
// hasNoUserSuppliedProxyInterfaces()是否存在代理接口
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 2.若是目標類是接口類(目標對象實現了接口),則直接使用JDKproxy
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 3.其餘狀況則使用CGLIBproxy
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
複製代碼
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable// JdkDynamicAopProxy類結構,由此可知,其實現了InvocationHandler,則一定有invoke方法,來被調用,也就是用戶調用bean相關方法時,此invoke()被真正調用
// getProxy()
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
// JDK proxy 動態代理的標準用法
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
複製代碼
//使用了JDK動態代理模式,真正的方法執行在invoke()方法裏,看到這裏在想一下上面動態代理的例子,是否是就徹底明白Spring源碼實現動態代理的原理了。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target = null;
try {
// 1.如下的幾個判斷,主要是爲了判斷method是否爲equals、hashCode等Object的方法
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// May be null. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
// 2.獲取當前bean被攔截方法鏈表
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 3.若是爲空,則直接調用target的method
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
// 4.不爲空,則逐一調用chain中的每個攔截方法的proceed,這裏的一系列執行的緣由以及proceed執行的內容,我 在這裏就不詳細講了,你們感興趣能夠本身去研讀哈
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
...
return retVal;
}
}
}
複製代碼
那麼到了這裏,我要講的內容就差很少結束了,若是有什麼不對的,或者有什麼疑惑,歡迎你們指點!