AOP全稱是Aspect Oriented Programming,叫作面向切面編程,和麪向對象編程(OOP)同樣也是一種編程思想,也是spring中一個重要的部分。java
其實現基於代理模式,對原來的業務進行加強。好比說原來的功能是增刪改查,想要不修改源代碼的狀況下加強原來的功能,那麼就能夠對原來的業務類生成一個代理的對象,在代理對象中實現方法對原來的業務加強。git
而代理又分靜態代理和動態代理,一般咱們都是用動態代理,由於靜態代理都是硬編碼,不適合拿來用在實現框架這種需求裏。在java中一般有兩種代理方式,一個是jdk自帶的代理,另外一個是cglib實現的代理方式,這兩個代理各有特色,不大瞭解的話能夠自行查找資料看看。github
在spring的底層這兩種代理方式都支持,在默認的狀況下,若是bean實現了一個接口,spring會使用jdk代理,不然就用cglib代理。spring
在doodle框架裏用了cglib代理的方式,由於這種方式代理的類不用實現接口,實現更靈活編程
在具體實現AOP功能前,先作一些準備。框架
由於cglib代理不是jdk自帶的,因此先在pom.xml引入cglib。ide
<properties>
...
<cglib.version>3.2.6</cglib.version>
</properties>
<dependencies>
...
<!-- cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${cglib.version}</version>
</dependency>
</dependencies>
複製代碼
而後在zbw.aop包下建立一個annotation包,而後再建立一個Aspect
註解。這個註解是用於標記在''切面''中,即實現代理功能的類上面。函數
package com.zbw.aop.annotation;
import ...;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
/** * 目標代理類的範圍 */
Class<? extends Annotation> target();
}
複製代碼
接着在zbw.aop包下建立一個advice包,這個包下放一系列的通知接口(Advice)。其中包括:測試
Advice
,全部通知接口都要繼承這個接口MethodBeforeAdvice
,繼承這個通知接口並實現其前置方法,能夠前置加強目標類,即目標方法執行前會先執行這個前置方法。AfterReturningAdvice
,繼承這個通知接口並實現其返回後方法,能夠後置加強目標類,即目標方法執後並放回結果時,會執行這個返回方法。ThrowsAdvice
,繼承這個通知接口並實現其異常方法,能夠加強目標類的異常,即目標方法發生異常時,會執行這個異常方法。AroundAdvice
,這個接口繼承了MethodBeforeAdvice
,AfterReturningAdvice
,ThrowsAdvice
這三個接口,至關於這三個接口的合集。在spring中還有其餘幾種的通知,這裏暫時就不一一實現,咱們就實現這幾種相對來講最經常使用的。優化
/** * 通知接口 */
public interface Advice {
}
/** * 前置通知接口 */
public interface MethodBeforeAdvice extends Advice {
/** * 前置方法 */
void before(Class<?> clz, Method method, Object[] args) throws Throwable;
}
/** * 返回通知接口 */
public interface AfterReturningAdvice extends Advice {
/** * 返回後方法 */
void afterReturning(Class<?> clz, Object returnValue, Method method, Object[] args) throws Throwable;
}
/** * 異常通知接口 */
public interface ThrowsAdvice extends Advice {
/** * 異常方法 */
void afterThrowing(Class<?> clz, Method method, Object[] args, Throwable e);
}
/** * 環繞通知接口 */
public interface AroundAdvice extends MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {
}
複製代碼
剛纔實現了幾種通知接口,咱們先將這些通知接口使用起來,實現代理類。
package com.zbw.aop;
import ...
/** * 代理通知類 */
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ProxyAdvisor {
/** * 通知 */
private Advice advice;
/** * 執行代理方法 */
public Object doProxy(Object target, Class<?> targetClass, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object result = null;
if (advice instanceof MethodBeforeAdvice) {
((MethodBeforeAdvice) advice).before(targetClass, method, args);
}
try {
//執行目標類的方法
result = proxy.invokeSuper(target, args);
if (advice instanceof AfterReturningAdvice) {
((AfterReturningAdvice) advice).afterReturning(targetClass, result, method, args);
}
} catch (Exception e) {
if (advice instanceof ThrowsAdvice) {
((ThrowsAdvice) advice).afterThrowing(targetClass, method, args, e);
} else {
throw new Throwable(e);
}
}
return result;
}
}
複製代碼
這個類就是代理類ProxyAdvisor
,即到時候咱們的目標類執行的時候,實際上就是執行咱們這個代理類。在ProxyAdvisor
中有屬性Advice
即是剛纔編寫的通知接口,而後在目標方法執行的時候,就會執行doProxy()
方法,經過斷定Advice
接口的類型來執行在接口中實現的方法。
執行的順序就是 MethodBeforeAdvice@before() -> MethodProxy@invokeSuper() -> AfterReturningAdvice@afterReturning(),若是目標方法出現異常則會執行ThrowsAdvice@afterThrowing()方法。
接下來就是實現AOP的執行器
package com.zbw.aop;
import ...
/** * Aop執行器 */
@Slf4j
public class Aop {
/** * Bean容器 */
private BeanContainer beanContainer;
public Aop() {
beanContainer = BeanContainer.getInstance();
}
public void doAop() {
beanContainer.getClassesBySuper(Advice.class)
.stream()
.filter(clz -> clz.isAnnotationPresent(Aspect.class))
.forEach(clz -> {
final Advice advice = (Advice) beanContainer.getBean(clz);
Aspect aspect = clz.getAnnotation(Aspect.class);
beanContainer.getClassesByAnnotation(aspect.target())
.stream()
.filter(target -> !Advice.class.isAssignableFrom(target))
.filter(target -> !target.isAnnotationPresent(Aspect.class))
.forEach(target -> {
ProxyAdvisor advisor = new ProxyAdvisor(advice);
Object proxyBean = ProxyCreator.createProxy(target, advisor);
beanContainer.addBean(target, proxyBean);
});
});
}
}
複製代碼
和上一節實現IOC的執行器的時候相似,先在AOP執行器的構造函數獲取到單例化得BeanContainer容器。
而後在doAop()
方法中實現AOP功能。
Aspect
註解的Bean,並找到實現了Advice
接口的類,這些類即是切面Aspect
的target()
的值,這個值就是要被代理的類的註解。好比說有個切面的註解爲@Aspect(target = Controller.class)
,那麼這個切面會做用在被Controller
註解的類上。aspect.target()的值
註解的Bean,找到目標代理類ProxyAdvisor
代理類並經過cglib建立出這個代理類的實例,並把這個類實例放回到BeanContainer容器中。在方法中有一個代理類創造器ProxyCreator
,他就是經過cglib來建立代理類的,最後實現一下這個創造器。
package com.zbw.aop;
import ...
/** * 代理類建立器 */
public final class ProxyCreator {
/** * 建立代理類 */
public static Object createProxy(Class<?> targetClass, ProxyAdvisor proxyAdvisor) {
return Enhancer.create(targetClass,
(MethodInterceptor) (target, method, args, proxy) ->
proxyAdvisor.doProxy(target, targetClass, method, args, proxy));
}
}
複製代碼
以上咱們最基本的AOP功能就實現了,可是目前來講,咱們的Advice實現類是不會被Bean容器BeanContainer
加載的,全部要在Bean容器的BEAN_ANNOTATION
屬性添加@Aspect註解
(感謝無邪丶的指正)
//BeanContainer
...
/** * 加載bean的註解列表 */
private static final List<Class<? extends Annotation>> BEAN_ANNOTATION
= Arrays.asList(Component.class, Controller.class, Service.class, Repository.class,Aspect.class);
...
複製代碼
在上一篇文章從零開始實現一個簡易的Java MVC框架(三)--實現IOC中的測試用例的基礎上,在實現一個DoodleAspect
切面,這切面實現了AroundAdvice
的通知接口並實現其中的三個方法。
package com.zbw.bean;
import ...
@Slf4j
@Aspect(target = Controller.class)
public class DoodleAspect implements AroundAdvice {
@Override
public void before(Class<?> clz, Method method, Object[] args) throws Throwable {
log.info("Before DoodleAspect ----> class: {}, method: {}", clz.getName(), method.getName());
}
@Override
public void afterReturning(Class<?> clz, Object returnValue, Method method, Object[] args) throws Throwable {
log.info("After DoodleAspect ----> class: {}, method: {}", clz, method.getName());
}
@Override
public void afterThrowing(Class<?> clz, Method method, Object[] args, Throwable e) {
log.error("Error DoodleAspect ----> class: {}, method: {}, exception: {}", clz, method.getName(), e.getMessage());
}
}
複製代碼
而後再編寫AopTest
的測試用例,這裏要注意,Aop執行器必需要在Ioc執行器以前執行,否則注入到Bean中的實例將可能不是代理類。
package com.zbw.aop;
import ...
@Slf4j
public class AopTest {
@Test
public void doAop() {
BeanContainer beanContainer = BeanContainer.getInstance();
beanContainer.loadBeans("com.zbw");
new Aop().doAop();
new Ioc().doIoc();
DoodleController controller = (DoodleController) beanContainer.getBean(DoodleController.class);
controller.hello();
}
}
複製代碼
能夠看到在執行DoodleController@hello()
方法的先後分別執行了DoodleAspect@before()
和DoodleAspect@afterReturning()
方法。說明AOP的功能已經完成了。
雖然完成了AOP功能,可是仍是有幾個比較嚴重的缺陷的
Aspect.target()
的值,來篩選出被這個值註解的類,這樣太籠統了。假如Aspect.target()=Controller.class
,那麼全部被Controller
註解的controller裏的左右方法都要被代理。咱們但願可以像spring那樣如execution(* com.zbw.*.service..*Impl.*(..))
,用一些表達式來篩選目標類。DoodleAspect1
和DoodleAspect2
兩個切面,都做用於DoodleController
上,只有一個切面能生效,這也不合理。因此在後面的章節會完善實現這兩個問題。
- 從零開始實現一個簡易的Java MVC框架(一)--前言
- 從零開始實現一個簡易的Java MVC框架(二)--實現Bean容器
- 從零開始實現一個簡易的Java MVC框架(三)--實現IOC
- 從零開始實現一個簡易的Java MVC框架(四)--實現AOP
- 從零開始實現一個簡易的Java MVC框架(五)--引入aspectj實現AOP切點
- 從零開始實現一個簡易的Java MVC框架(六)--增強AOP功能
- 從零開始實現一個簡易的Java MVC框架(七)--實現MVC
- 從零開始實現一個簡易的Java MVC框架(八)--製做Starter
- 從零開始實現一個簡易的Java MVC框架(九)--優化MVC代碼
源碼地址:doodle