前言
前面经过了十章的漫长练习,我们终于把Spring的IOC部分完善的差不多了。那么从这一章开始,我们就要步入Spring的另外半壁江山:AOP了。AOP,也就是面向切面编程。它可以实现对业务逻辑的各个部分进行隔离,从而降低各个模块之间的耦合度,提高代码可复用性。
我们平时对Spring AOP的应用,最常见的情况可能就是对方法执行的监控了。比如在方法执行前后输出一些日志之类的。那么Spring是如何实现这个功能的呢?答案就是动态代理。我们最终使用的类,实际上已经不是我们最原始的类了,而是被Spring代理过的类。而一旦这个类被代理了,那就意味着Spring可以在执行前后做任何自己想做的事情了。就像租房子时候的黑中介一样,给你加上各种条款还有高额的中介费(泣。
工程结构
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─akitsuki
│ │ │ └─springframework
│ │ │ ├─aop
│ │ │ │ │ AdvisedSupport.java
│ │ │ │ │ ClassFilter.java
│ │ │ │ │ MethodMatcher.java
│ │ │ │ │ PointCut.java
│ │ │ │ │ TargetSource.java
│ │ │ │ │
│ │ │ │ ├─aspect
│ │ │ │ │ AspectJExpressionPointcut.java
│ │ │ │ │
│ │ │ │ └─framework
│ │ │ │ AopProxy.java
│ │ │ │ Cglib2AopProxy.java
│ │ │ │ JdkDynamicAopProxy.java
│ │ │ │ ReflectiveMethodInvocation.java
│ │ │ │
│ │ │ ├─beans
│ │ │ │ ├─exception
│ │ │ │ │ BeanException.java
│ │ │ │ │
│ │ │ │ └─factory
│ │ │ │ │ Aware.java
│ │ │ │ │ BeanClassLoaderAware.java
│ │ │ │ │ BeanFactory.java
│ │ │ │ │ BeanFactoryAware.java
│ │ │ │ │ BeanNameAware.java
│ │ │ │ │ ConfigurableListableBeanFactory.java
│ │ │ │ │ DisposableBean.java
│ │ │ │ │ FactoryBean.java
│ │ │ │ │ HierarchicalBeanFactory.java
│ │ │ │ │ InitializingBean.java
│ │ │ │ │ ListableBeanFactory.java
│ │ │ │ │
│ │ │ │ ├─config
│ │ │ │ │ AutowireCapableBeanFactory.java
│ │ │ │ │ BeanDefinition.java
│ │ │ │ │ BeanDefinitionRegistryPostProcessor.java
│ │ │ │ │ BeanFactoryPostProcessor.java
│ │ │ │ │ BeanPostProcessor.java
│ │ │ │ │ BeanReference.java
│ │ │ │ │ ConfigurableBeanFactory.java
│ │ │ │ │ DefaultSingletonBeanRegistry.java
│ │ │ │ │ PropertyValue.java
│ │ │ │ │ PropertyValues.java
│ │ │ │ │ SingletonBeanRegistry.java
│ │ │ │ │
│ │ │ │ ├─support
│ │ │ │ │ AbstractAutowireCapableBeanFactory.java
│ │ │ │ │ AbstractBeanDefinitionReader.java
│ │ │ │ │ AbstractBeanFactory.java
│ │ │ │ │ BeanDefinitionReader.java
│ │ │ │ │ BeanDefinitionRegistry.java
│ │ │ │ │ CglibSubclassingInstantiationStrategy.java
│ │ │ │ │ DefaultListableBeanFactory.java
│ │ │ │ │ DisposableBeanAdapter.java
│ │ │ │ │ FactoryBeanRegistrySupport.java
│ │ │ │ │ InstantiationStrategy.java
│ │ │ │ │ SimpleInstantiationStrategy.java
│ │ │ │ │
│ │ │ │ └─xml
│ │ │ │ XmlBeanDefinitionReader.java
│ │ │ │
│ │ │ ├─context
│ │ │ │ │ ApplicationContext.java
│ │ │ │ │ ApplicationContextAware.java
│ │ │ │ │ ApplicationEvent.java
│ │ │ │ │ ApplicationEventPublisher.java
│ │ │ │ │ ApplicationListener.java
│ │ │ │ │ ConfigurableApplicationContext.java
│ │ │ │ │
│ │ │ │ ├─event
│ │ │ │ │ AbstractApplicationEventMulticaster.java
│ │ │ │ │ ApplicationContextEvent.java
│ │ │ │ │ ApplicationEventMulticaster.java
│ │ │ │ │ ContextClosedEvent.java
│ │ │ │ │ ContextRefreshEvent.java
│ │ │ │ │ SimpleApplicationEventMulticaster.java
│ │ │ │ │
│ │ │ │ └─support
│ │ │ │ AbstractApplicationContext.java
│ │ │ │ AbstractRefreshableApplicationContext.java
│ │ │ │ AbstractXmlApplicationContext.java
│ │ │ │ ApplicationContextAwareProcessor.java
│ │ │ │ ClasspathXmlApplicationContext.java
│ │ │ │
│ │ │ ├─core
│ │ │ │ └─io
│ │ │ │ ClasspathResource.java
│ │ │ │ DefaultResourceLoader.java
│ │ │ │ FileSystemResource.java
│ │ │ │ Resource.java
│ │ │ │ ResourceLoader.java
│ │ │ │ UrlResource.java
│ │ │ │
│ │ │ └─util
│ │ │ ClassUtils.java
│ │ │
│ │ └─resources
│ └─test
│ └─java
│ └─com
│ └─akitsuki
│ └─springframework
│ │ ApiTest.java
│ │
│ ├─aop
│ │ └─aspect
│ │ AspectJExpressionPointcutTest.java
│ │
│ └─bean
│ IUserDao.java
│ UserDao.java
│ UserDaoInterceptor.java
这次的内容,主要新增了一个aop包,经历了前面的练习,这些已经是小场面了。让我们开始这一次的内容吧。
事前准备
这次我们需要引入两个新的依赖,来帮助我们解析表达式,以及实现代理
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
切点表达式
回忆一下,我们在Spring中使用AOP的时候,一般都是用一个表达式来表示我们需要切入的方法或者类。那么我们首先就要来实现这一功能。
首先,我们需要一个切入点
package com.akitsuki.springframework.aop;
/**
* 切入点接口
*
* @author ziling.wang@hand-china.com
* @date 2022/12/5 9:51
*/
public interface PointCut {
/**
* 获取classFilter
* @return
*/
ClassFilter getClassFilter();
/**
* 获取method匹配器
* @return
*/
MethodMatcher getMethodMatcher();
}
这里的 ClassFilter
和 MethodMatcher
,我们会在下面进行介绍。这两个类,分别是用来匹配类与方法的。
我们来看看ClassFilter
package com.akitsuki.springframework.aop;
/**
* 类匹配接口,用于切点找到给定的接口和目标类
*
* @author ziling.wang@hand-china.com
* @date 2022/12/5 9:54
*/
public interface ClassFilter {
/**
* 是否匹配
*
* @param clazz 要匹配的类
* @return result
*/
boolean matches(Class<?> clazz);
}
简单易懂的一个方法,就是用来判断传入的类是否匹配的
我们再来看MethodMatcher
package com.akitsuki.springframework.aop;
import java.lang.reflect.Method;
/**
* 方法匹配接口,找到表达式范围内匹配下的目标类和方法
*
* @author ziling.wang@hand-china.com
* @date 2022/12/5 9:55
*/
public interface MethodMatcher {
/**
* 是否匹配
*
* @param method 要匹配的方法
* @param clazz 要匹配的类
* @return result
*/
boolean matches(Method method, Class<?> clazz);
}
和上面类似,这次是判断方法是否匹配。
有了这些接口作为框架,我们就可以实现我们的表达式匹配类了
package com.akitsuki.springframework.aop.aspect;
import com.akitsuki.springframework.aop.ClassFilter;
import com.akitsuki.springframework.aop.MethodMatcher;
import com.akitsuki.springframework.aop.PointCut;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.PointcutPrimitive;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
/**
* @author ziling.wang@hand-china.com
* @date 2022/12/5 9:58
*/
public class AspectJExpressionPointcut implements PointCut, ClassFilter, MethodMatcher {
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
static {
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
}
private final PointcutExpression pointcutExpression;
public AspectJExpressionPointcut(String expression) {
PointcutParser pointcutParser = PointcutParser
.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
pointcutExpression = pointcutParser.parsePointcutExpression(expression);
}
@Override
public boolean matches(Class<?> clazz) {
return pointcutExpression.couldMatchJoinPointsInType(clazz);
}
@Override
public boolean matches(Method method, Class<?> clazz) {
return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
}
@Override
public ClassFilter getClassFilter() {
return this;
}
@Override
public MethodMatcher getMethodMatcher() {
return this;
}
}
我们来分析一下。首先,我们维护了一个Set,用来表示我们支持的匹配方式。然后在静态代码块中,加入了execution表达式的支持。然后我们看构造方法,主要内容是通过传入的表达式字符串,初始化我们的表达式解析器。最后我们来看看对方法内容的实现。可以看到主要都是通过表达式解析器来进行判断传入的类或者方法,是否满足当前的表达式。
对切面信息进行包装
我们既然要对一个对象进行代理,首先我们需要一个类来表示它。怎么理解呢,有些像我们前面的Bean定义一样,需要一个类来存放一些被代理类的信息。
package com.akitsuki.springframework.aop;
/**
* 被代理的目标对象
*
* @author ziling.wang@hand-china.com
* @date 2022/12/5 11:11
*/
public class TargetSource {
private final Object target;
public TargetSource(Object target) {
this.target = target;
}
public Class<?>[] getTargetClass() {
return target.getClass().getInterfaces();
}
public Object getTarget() {
return target;
}
}
可以看到,我们用TargetSource类,来表示被代理的对象。类中维护了一个Object属性来存储被代理对象,同时提供了getTargetClass方法,来获取对象所实现的接口。这一点很重要,并不是获取对象本身,而是获取对象的接口。因为我们用JDK的动态代理来进行实现的话,被代理的对象必须要实现接口,所以我们也需要一个方法来提供这些接口。
接下来,我们还需要一个类,用来包装上面的这些信息。它的作用有些类似于我们平时使用的DTO,主要是对需要用到的信息起到一个包装汇总作用。
package com.akitsuki.springframework.aop;
import lombok.Getter;
import lombok.Setter;
import org.aopalliance.intercept.MethodInterceptor;
/**
* 切面通知属性包装类,主要是方便管理
*
* @author ziling.wang@hand-china.com
* @date 2022/12/5 11:08
*/
@Getter
@Setter
public class AdvisedSupport {
/**
* 被代理的目标对象
*/
private TargetSource targetSource;
/**
* 方法拦截器
*/
private MethodInterceptor methodInterceptor;
/**
* 方法匹配器
*/
private MethodMatcher methodMatcher;
}
可以看到,它包含了被代理的对象、方法拦截器和方法匹配器三个内容。
开始代理!
上面铺垫了这么多,我们终于要开始今天的重头戏了。我们知道常用的代理,有JDK的动态代理,还有Cglib提供的代理。所以我们首先需要一个接口,用来抽象我们的代理方式。
package com.akitsuki.springframework.aop.framework;
/**
* 获取代理类,因为代理的方式可能有多种实现
*
* @author ziling.wang@hand-china.com
* @date 2022/12/5 11:29
*/
public interface AopProxy {
/**
* 获取代理类
*
* @return resuLt
*/
Object getProxy();
}
我们这次准备提供JDK和Cglib两种代理的实现,首先来看JDK的实现方式
package com.akitsuki.springframework.aop.framework;
import com.akitsuki.springframework.aop.AdvisedSupport;
import org.aopalliance.intercept.MethodInterceptor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author ziling.wang@hand-china.com
* @date 2022/12/5 11:30
*/
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
private final AdvisedSupport advisedSupport;
public JdkDynamicAopProxy(AdvisedSupport advisedSupport) {
this.advisedSupport = advisedSupport;
}
@Override
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
advisedSupport.getTargetSource().getTargetClass(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (advisedSupport.getMethodMatcher().matches(method, advisedSupport.getTargetSource().getTarget().getClass())) {
MethodInterceptor methodInterceptor = advisedSupport.getMethodInterceptor();
return methodInterceptor.invoke(new ReflectiveMethodInvocation(advisedSupport.getTargetSource().getTarget(), method, args));
}
return method.invoke(advisedSupport.getTargetSource().getTarget(), args);
}
}
可以看到,首先这个类实现了 AopProxy
和 InvocationHandler
接口。实现了 InvocationHandler
接口,就意味着当前这个类可以作为JDK动态代理类来使用了。getProxy
方法中,通过 Proxy.newProxyInstance
方法,来动态生成代理对象。接下来我们看 invoke
方法。这个方法是代理类在调用方法时,会被拦截执行的一个方法。可以看到这里实现的内容是,通过方法匹配器来判断当前方法是否需要被拦截,如果需要被拦截,则通过方法拦截器来进行调用。否则,直接通过反射进行调用方法。
这里还可以看到有一个 ReflectiveMethodInvocation
,它其实也是起到一个包装作用,实现了 MethodInvocation
接口。主要内容的实现 proceed
方法,实际上还是对方法进行反射调用。
package com.akitsuki.springframework.aop.framework;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
/**
* @author ziling.wang@hand-china.com
* @date 2022/12/5 15:39
*/
public class ReflectiveMethodInvocation implements MethodInvocation {
protected final Object target;
protected final Method method;
protected final Object[] arguments;
public ReflectiveMethodInvocation(Object target, Method method, Object[] arguments) {
this.target = target;
this.method = method;
this.arguments = arguments;
}
@Override
public Method getMethod() {
return method;
}
@Override
public Object[] getArguments() {
return arguments;
}
@Override
public Object proceed() throws Throwable {
return method.invoke(target, arguments);
}
@Override
public Object getThis() {
return target;
}
@Override
public AccessibleObject getStaticPart() {
return method;
}
}
接下来我们看Cglib的实现方式
package com.akitsuki.springframework.aop.framework;
import com.akitsuki.springframework.aop.AdvisedSupport;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author ziling.wang@hand-china.com
* @date 2022/12/5 15:45
*/
public class Cglib2AopProxy implements AopProxy {
private final AdvisedSupport advisedSupport;
public Cglib2AopProxy(AdvisedSupport advisedSupport) {
this.advisedSupport = advisedSupport;
}
@Override
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(advisedSupport.getTargetSource().getTarget().getClass());
enhancer.setInterfaces(advisedSupport.getTargetSource().getTargetClass());
enhancer.setCallback(new DynamicAdvisedInterceptor(advisedSupport));
return enhancer.create();
}
private static class DynamicAdvisedInterceptor implements MethodInterceptor {
private final AdvisedSupport advisedSupport;
public DynamicAdvisedInterceptor(AdvisedSupport advisedSupport) {
this.advisedSupport = advisedSupport;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
CglibMethodInvocation methodInvocation =
new CglibMethodInvocation(advisedSupport.getTargetSource().getTarget(), method, objects, methodProxy);
if (advisedSupport.getMethodMatcher().matches(method, advisedSupport.getTargetSource().getTarget().getClass())) {
return advisedSupport.getMethodInterceptor().invoke(methodInvocation);
}
return methodInvocation.proceed();
}
}
private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
private final MethodProxy methodProxy;
public CglibMethodInvocation(Object target, Method method, Object[] arguments, MethodProxy methodProxy) {
super(target, method, arguments);
this.methodProxy = methodProxy;
}
@Override
public Object proceed() throws Throwable {
return this.methodProxy.invoke(this.target, this.arguments);
}
}
}
看起来很复杂,还有两个静态的内部类。但其实仔细分析一下的话,会发现也不是很难。我们先来看 getProxy
的内容,可以看到,这里是通过Enhancer创建了代理对象,重点在于Callback的内容。这里的Callback,实际上和上面JDK动态代理中的invoke功能是一样的,都是代理后拦截执行的逻辑。这里需要传入一个Callback类型的对象,Callback实际上是一个标记接口,我们之前也接触过很多次标记接口了,有很多类都实现了Callback接口,我们一般常用的是 MethodInterceptor
,也就是方法拦截器。往下看,我们的第一个静态内部类,实际上就是一个方法拦截器,然后我们看具体内容,实际上还是通过方法匹配器来进行判断,如果匹配就调用方法拦截器来执行,和JDK动态代理是一样的玩法,如果不匹配,则调用 methodInvocation
来执行。这里的 MethodInvocation
,就是我们下面的静态内部类,可以看到它继承了我们上面提到的 ReflectiveMethodInvocation
,所以其实本质上它们没什么区别。主要就是这里的执行,是通过方法代理 MethodProxy
来进行的,而 ReflectiveMethodInvocation
则是通过反射直接执行的,就这么一点区别。
测试!
好了,终于又到了测试环节。总觉得这次虽然新加了不少类,但是实际上内容却不是很多。这次的测试,我们和前面的Spring IOC内容决裂了!已经没有什么Context了!也没有什么Bean定义了!回归最初的美好吧!
不过我们还是得有个bean(笑)
由于JDK动态代理要求类必须实现接口,所以我们先得给整一个接口
package com.akitsuki.springframework.bean;
/**
* @author ziling.wang@hand-china.com
* @date 2022/12/5 17:26
*/
public interface IUserDao {
String queryUserName(Long id);
}
嗯,简单易懂,接下来是我们永远跑不了的bean
package com.akitsuki.springframework.bean;
import java.util.HashMap;
import java.util.Map;
/**
* @author ziling.wang@hand-china.com
* @date 2022/11/8 14:42
*/
public class UserDao implements IUserDao {
private static final Map<Long, String> userMap = new HashMap<>();
static {
userMap.put(1L, "akitsuki");
userMap.put(2L, "toyosaki");
userMap.put(3L, "kugimiya");
userMap.put(4L, "hanazawa");
userMap.put(5L, "momonogi");
}
@Override
public String queryUserName(Long id) {
return userMap.get(id);
}
}
没啥大变化,就是实现了上面的接口
下面是重点了,我们要实现自定义的方法拦截器,也就是我们切面的具体内容,需要实现 MethodInerceptor
接口。
package com.akitsuki.springframework.bean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* @author ziling.wang@hand-china.com
* @date 2022/12/5 16:51
*/
public class UserDaoInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return methodInvocation.proceed();
} finally {
System.out.println("+++++ AOP 方法执行监控 +++++");
System.out.println("方法名称:" + methodInvocation.getMethod().getName());
System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("----- AOP 方法监控结束 -----");
}
}
}
打印一下方法执行的信息,很简单。
下面,是真正的测试类
package com.akitsuki.springframework;
import com.akitsuki.springframework.aop.AdvisedSupport;
import com.akitsuki.springframework.aop.TargetSource;
import com.akitsuki.springframework.aop.aspect.AspectJExpressionPointcut;
import com.akitsuki.springframework.aop.framework.Cglib2AopProxy;
import com.akitsuki.springframework.aop.framework.JdkDynamicAopProxy;
import com.akitsuki.springframework.bean.IUserDao;
import com.akitsuki.springframework.bean.UserDao;
import com.akitsuki.springframework.bean.UserDaoInterceptor;
import org.junit.Test;
/**
* @author ziling.wang@hand-china.com
* @date 2022/12/5 16:46
*/
public class ApiTest {
@Test
public void test() {
IUserDao userDao = new UserDao();
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTargetSource(new TargetSource(userDao));
advisedSupport.setMethodInterceptor(new UserDaoInterceptor());
advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* com.akitsuki.springframework.bean.IUserDao.*(..))"));
IUserDao proxyJdk = (IUserDao) new JdkDynamicAopProxy(advisedSupport).getProxy();
assert proxyJdk != null;
System.out.println("测试结果:" + proxyJdk.queryUserName(1L));
IUserDao proxyCglib = (IUserDao) new Cglib2AopProxy(advisedSupport).getProxy();
System.out.println("测试结果:" + proxyCglib.queryUserName(2L));
}
}
测试结果
+++++ AOP 方法执行监控 +++++
方法名称:queryUserName
方法耗时:0ms
----- AOP 方法监控结束 -----
测试结果:akitsuki
+++++ AOP 方法执行监控 +++++
方法名称:queryUserName
方法耗时:12ms
----- AOP 方法监控结束 -----
测试结果:toyosaki
Process finished with exit code 0
可以看到,我们切面中的内容已经正常打印出来了。这次我们的表达式拦截的是IUserDao接口的所有方法,分别创建了JDK动态代理和Cglib动态代理两种方式来进行测试,都能够正常进行工作。
不过我们也可以看到,这种方式终究还是有些繁琐,而且没有和Spring很好的结合起来,显得有些割裂。那么就留到下一章再进行完善吧。
相关源码可以参考我的gitee:https://gitee.com/akitsuki-kouzou/mini-spring
,这里对应的代码是mini-spring-11