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