前言
AOP全称是Aspect Oriented Programming,叫做面向切面编程,和面向对象编程(OOP)一样也是一种编程思想,也是spring中一个重要的部分。
其实现基于代理模式,对原来的业务进行增强。比如说原来的功能是增删改查,想要不修改源代码的情况下增强原来的功能,那么就可以对原来的业务类生成一个代理的对象,在代理对象中实现方法对原来的业务增强。
而代理又分静态代理和动态代理,通常我们都是用动态代理,因为静态代理都是硬编码,不适合拿来用在实现框架这种需求里。在java中通常有两种代理方式,一个是jdk自带的代理,另一个是cglib实现的代理方式,这两个代理各有特点,不大了解的话可以自行查找资料看看。
在spring的底层这两种代理方式都支持,在默认的情况下,如果bean实现了一个接口,spring会使用jdk代理,否则就用cglib代理。
在doodle框架里用了cglib代理的方式,因为这种方式代理的类不用实现接口,实现更灵活
实现准备
在具体实现AOP功能前,先做一些准备。
因为cglib代理不是jdk自带的,所以先在pom.xml引入cglib。
<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 {
}
实现AOP
刚才实现了几种通知接口,我们先将这些通知接口使用起来,实现代理类。
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功能。
- 遍历在BeanContainer容器被
Aspect
注解的Bean,并找到实现了Advice
接口的类,这些类便是切面 - 获取切面上的注解
Aspect
的target()
的值,这个值就是要被代理的类的注解。比如说有个切面的注解为@Aspect(target = Controller.class)
,那么这个切面会作用在被Controller
注解的类上。 - 遍历BeanContainer容器被
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。