在上一節從零開始實現一個簡易的Java MVC框架(四)--實現AOP中咱們實現了AOP的功能,已經能夠生成對應的代理類了,可是對於代理對象的選擇只能經過指定的類,這樣確實不方便也不合理。這一節咱們就利用aspectj
來實現功能更強大的切點。java
在spring初期的時候AOP功能使用起來也是很繁瑣麻煩的,到了後面整合了aspectj
纔有瞭如今這麼方便的AOP功能,好比下面這樣的代碼,很簡便而且直觀的定義了切點。git
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.zbw.*.service..*Impl.*(..)) && @annotation(Log)")
public void logPointcut() {
}
@Before("logPointcut()")
public void before() {System.out.println("Before");}
}
複製代碼
如今咱們也來引入aspectj來實現AOP切點的功能github
首先在pom.xml中加入aspectj
的依賴spring
<properties>
...
<aspectj.version>1.8.13</aspectj.version>
</properties>
<dependencies>
...
<!-- aspectj -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
複製代碼
接下來就能夠開始實現一個利用aspectj來斷定的切點類,這個類主要是用於判斷aspectj表達式是否匹配一個指定類或者指定方法。express
在zbw.aop包下建立一個類,起名叫ProxyPointcut
框架
package com.zbw.aop;
import ...
/** * 代理切點類 */
public class ProxyPointcut {
/** * 切點解析器 */
private PointcutParser pointcutParser;
/** * (AspectJ)表達式 */
private String expression;
/** * 表達式解析器 */
private PointcutExpression pointcutExpression;
/** * AspectJ語法集合 */
private static final Set<PointcutPrimitive> DEFAULT_SUPPORTED_PRIMITIVES = new HashSet<>();
static {
DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
}
public ProxyPointcut() {
this(DEFAULT_SUPPORTED_PRIMITIVES);
}
public ProxyPointcut(Set<PointcutPrimitive> supportedPrimitives) {
pointcutParser = PointcutParser
.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(supportedPrimitives);
}
/** * Class是否匹配切點表達式 */
public boolean matches(Class<?> targetClass) {
checkReadyToMatch();
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
}
/** * Method是否匹配切點表達式 */
public boolean matches(Method method) {
checkReadyToMatch();
ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(method);
if (shadowMatch.alwaysMatches()) {
return true;
} else if (shadowMatch.neverMatches()) {
return false;
}
return false;
}
/** * 初始化切點解析器 */
private void checkReadyToMatch() {
if (null == pointcutExpression) {
pointcutExpression = pointcutParser.parsePointcutExpression(expression);
}
}
public void setExpression(String expression) {
this.expression = expression;
}
public String getExpression() {
return expression;
}
複製代碼
這個類中有三個變量:pointcutParser
,expression
,pointcutExpression
。測試
其中expression
是String類型,用於存放咱們要設定的aspectj表達式,好比execution(* com.zbw.*.service..*Impl.*(..))
這樣的。優化
pointcutParser
和pointcutExpression
就是aspectj裏面的類了,pointcutParser
用於根據expression
中的表達式建立pointcutExpression
表達式解析器。而pointcutExpression
能夠用來判斷方法或者類是否匹配表達式。this
這個類中最主要的兩個方法就matches(Class<?> targetClass)
和matches(Method method)
,這兩個方法分別用於斷定目標的類和方法是否匹配expression
中的aspectj表達式。spa
接下來就能夠把ProxyPointcut
這個切點類加入到咱們以前實現的AOP功能中了。
首先改裝Aspect
註解,把以前target()
改爲pointcut()
來存儲aspectj表達式。
package com.zbw.aop.annotation;
import ...;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
/** * 切點表達式 */
String pointcut() default "";
}
複製代碼
而後改裝ProxyAdvisor
這個類,把切點表達式匹配器放入其中,而且使用匹配器來斷定目標類是否要被加強。
...
public class ProxyAdvisor {
...
/** * AspectJ表達式切點匹配器 */
private ProxyPointcut pointcut;
/** * 執行代理方法 */
public Object doProxy(Object target, Class<?> targetClass, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (!pointcut.matches(method)) {
return proxy.invokeSuper(target, args);
}
...
}
}
複製代碼
在doProxy()
這個方法的最前面經過pointcut.matches()
來斷定目標方法是否匹配這個表達式,若是匹配的話就往下執行以前編寫的各類通知,若是不匹配那麼就直接執行目標方法。經過這種方式來使aspectj表達式控制目標類的加強。
接下來改裝Aop
類,因爲改變了匹配目標類的規則,因此要重寫以前的doAop()
方法。
...
public class Aop {
...
public void doAop() {
beanContainer.getClassesBySuper(Advice.class)
.stream()
.filter(clz -> clz.isAnnotationPresent(Aspect.class))
.map(this::createProxyAdvisor)
.forEach(proxyAdvisor -> beanContainer.getClasses()
.stream()
.filter(target -> !Advice.class.isAssignableFrom(target))
.filter(target -> !target.isAnnotationPresent(Aspect.class))
.forEach(target -> {
if (proxyAdvisor.getPointcut().matches(target)) {
Object proxyBean = ProxyCreator.createProxy(target, proxyAdvisor);
beanContainer.addBean(target, proxyBean);
}
}));
}
/** * 經過Aspect切面類建立代理通知類 */
private ProxyAdvisor createProxyAdvisor(Class<?> aspectClass) {
String expression = aspectClass.getAnnotation(Aspect.class).pointcut();
ProxyPointcut proxyPointcut = new ProxyPointcut();
proxyPointcut.setExpression(expression);
Advice advice = (Advice) beanContainer.getBean(aspectClass);
return new ProxyAdvisor(advice, proxyPointcut);
}
}
複製代碼
雖然重寫了doAop()
方法,可是實現原理依舊是相同的。只不過如今把建立ProxyAdvisor
的過程分離出來單獨寫了一個方法createProxyAdvisor()
, 而後再遍歷Bean容器中的除了切面類的全部Bean,若是這個Bean匹配ProxyAdvisor
中的切點表達式,那麼就會生成對應的代理類。
引入aspectj實現AOP切點完成了,又到測試用例來測試功能是否成功的時候了。
在上一篇文章從零開始實現一個簡易的Java MVC框架(四)--實現AOP中的測試用例的基礎上修改測試用例。
先修改切面類DoodleAspect
上的Aspect
註解
package com.zbw.bean;
import ...
@Slf4j
@Aspect(pointcut = "execution(* com.zbw.bean.DoodleController.helloForAspect(..))")
public class DoodleAspect implements AroundAdvice {
...
}
複製代碼
這個Aspect@pointcut()
中的值會讓其只匹配DoodleController
中的helloForAspect()
方法。
接下來在DoodleController
添加helloForAspect()
方法
...
public class DoodleController {
...
public void helloForAspect() {
log.info("Hello Aspectj");
}
}
複製代碼
最後再從新編寫AopTest
的測試用例。
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();
controller.helloForAspect();
}
}
複製代碼
從結果的圖中能夠看到在DoodleController
的hello()
先後沒有打印多餘的日誌,而在helloForAspect()
方法的前面和後面都打印了DoodleAspect
中的通知方法裏的內容,說明咱們的AOP已經精準的匹配到了想要的目標。
- 從零開始實現一個簡易的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