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