系列文章:
在前面三篇,我们已经成功实现了 Spring 最核心的功能 IOC和DI 以及 MVC,从这篇开始,我们就要来实现Spring的另一核心 AOP。
1.配置封装(config包)
下面这些类 AOP 中需要用到的基本组件,是对一些基本信息的封装。
MYAopConfig
封装了配置文件信息
public class MYAopConfig {
private String pointCut;
private String aspectBefore;
private String aspectAfter;
private String aspectClass;
private String aspectAfterThrow;
private String aspectAfterThrowingName;
public String getPointCut() {
return pointCut;
}
public void setPointCut(String pointCut) {
this.pointCut = pointCut;
}
public String getAspectBefore() {
return aspectBefore;
}
public void setAspectBefore(String aspectBefore) {
this.aspectBefore = aspectBefore;
}
public String getAspectAfter() {
return aspectAfter;
}
public void setAspectAfter(String aspectAfter) {
this.aspectAfter = aspectAfter;
}
public String getAspectClass() {
return aspectClass;
}
public void setAspectClass(String aspectClass) {
this.aspectClass = aspectClass;
}
public String getAspectAfterThrow() {
return aspectAfterThrow;
}
public void setAspectAfterThrow(String aspectAfterThrow) {
this.aspectAfterThrow = aspectAfterThrow;
}
public String getAspectAfterThrowingName() {
return aspectAfterThrowingName;
}
public void setAspectAfterThrowingName(String aspectAfterThrowingName) {
this.aspectAfterThrowingName = aspectAfterThrowingName;
}
}
2.切面组件(aspect包)
MYJoinPoint
封装了 Spring Aop 中切面方法的信息,在切面方法中添加 JoinPoint 参数就可以获取到封装了该方法信息的 JoinPoint 对象,会作为使用时所有Advice的入参
public interface MYJoinPoint {
Object getThis();
Object[] getArguments();
Method getMethod();
void setUserAttribute(String key, Object value);
Object getUserAttribute(String key);
}
MYAdvice
通知类的顶层接口
public interface MYAdvice {
}
MYMethodInterceptor
拦截器链的组件的统一接口
public interface MYMethodInterceptor {
Object invoke(MYMethodInvocation invocation) throws Throwable;
}
MYMethodBeforeAdviceInterceptor
拦截器链组件,前置通知
public class MYMethodBeforeAdviceInterceptor extends MYAbstractAspectAdvice implements MYMethodInterceptor {
private MYJoinPoint joinPoint;
public MYMethodBeforeAdviceInterceptor(Method aspectMethod, Object aspectTarget) {
super(aspectMethod, aspectTarget);
}
private void before(Method method,Object[] args,Object target) throws Throwable{
// 传送了给织入参数
// method.invoke(target);
super.invokeAdviceMethod(this.joinPoint,null,null);
}
@Override
public Object invoke(MYMethodInvocation mi) throws Throwable {
// 从被织入的代码中才能拿到,JoinPoint
this.joinPoint = mi;
before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
}
MYAfterReturningAdviceInterceptor
拦截器链组件,返回通知
public class MYAfterReturningAdviceInterceptor extends MYAbstractAspectAdvice implements MYMethodInterceptor {
private MYJoinPoint joinPoint;
public MYAfterReturningAdviceInterceptor(Method aspectMethod, Object aspectTarget) {
super(aspectMethod, aspectTarget);
}
@Override
public Object invoke(MYMethodInvocation mi) throws Throwable {
Object retVal = mi.proceed();
this.joinPoint = mi;
this.afterReturning(retVal,mi.getMethod(),mi.getArguments(),mi.getThis());
return retVal;
}
private void afterReturning(Object retVal, Method method, Object[] arguments, Object aThis) throws Throwable {
super.invokeAdviceMethod(this.joinPoint,retVal,null);
}
}
MYAfterThrowingAdviceInterceptor
拦截器链组件,异常通知
public class MYAfterThrowingAdviceInterceptor extends MYAbstractAspectAdvice implements MYMethodInterceptor {
private String throwingName;
public MYAfterThrowingAdviceInterceptor(Method aspectMethod, Object aspectTarget) {
super(aspectMethod, aspectTarget);
}
public void setThrowName(String throwingName) {
this.throwingName = throwingName;
}
@Override
public Object invoke(MYMethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}catch (Throwable e){
invokeAdviceMethod(mi,null,e.getCause());
throw e;
}
}
}
3. 拦截器链(intercept包)
MYAbstractAspectAdvice
拦截器链执行通用逻辑,实现了 MYAdvice 接口
public class MYAbstractAspectAdvice implements MYAdvice{
private Method aspectMethod;
private Object aspectTarget;
public MYAbstractAspectAdvice(Method aspectMethod, Object aspectTarget) {
this.aspectMethod = aspectMethod;
this.aspectTarget = aspectTarget;
}
public Object invokeAdviceMethod(MYJoinPoint joinPoint, Object returnValue, Throwable tx) throws Throwable{
Class<?> [] paramTypes = this.aspectMethod.getParameterTypes();
if(null == paramTypes || paramTypes.length == 0){
return this.aspectMethod.invoke(aspectTarget);
}else{
Object [] args = new Object[paramTypes.length];
for (int i = 0; i < paramTypes.length; i ++) {
if(paramTypes[i] == MYJoinPoint.class){
args[i] = joinPoint;
}else if(paramTypes[i] == Throwable.class){
args[i] = tx;
}else if(paramTypes[i] == Object.class){
args[i] = returnValue;
}
}
return this.aspectMethod.invoke(aspectTarget,args);
}
}
}
MYMethodInvocation
拦截器链执行器
public class MYMethodInvocation implements MYJoinPoint {
// 代理对象
private Object proxy;
// 目标类
private Class<?> targetClass;
// 目标对象
private Object target;
// 要被处理方法(不一定是增强方法)
private Method method;
// 方法的参数们
private Object [] arguments;
// 拦截器链
private List<Object> interceptorsAndDynamicMethodMatchers;
// 定义一个索引,从-1开始来记录当前拦截器执行的位置
private int currentInterceptorIndex = -1;
private Map<String, Object> userAttributes;
public MYMethodInvocation(
Object proxy, Object target, Method method, Object[] arguments,
Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
this.proxy = proxy;
this.target = target;
this.targetClass = targetClass;
this.method = method;
this.arguments = arguments;
this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
}
@Override
public Object getThis() {
return this.target;
}
@Override
public Object[] getArguments() {
return this.arguments;
}
@Override
public Method getMethod() {
return this.method;
}
public void setUserAttribute(String key, Object value) {
if (value != null) {
if (this.userAttributes == null) {
this.userAttributes = new HashMap<String,Object>();
}
this.userAttributes.put(key, value);
}
else {
if (this.userAttributes != null) {
this.userAttributes.remove(key);
}
}
}
public Object getUserAttribute(String key) {
return (this.userAttributes != null ? this.userAttributes.get(key) : null);
}
}
proceed()
执行拦截器链
public Object proceed() throws Throwable {
// 如果Interceptor执行完了,则执行joinPoint
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return this.method.invoke(this.target,this.arguments);
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// 如果要动态匹配joinPoint
if (interceptorOrInterceptionAdvice instanceof MYMethodInterceptor) {
MYMethodInterceptor mi = (MYMethodInterceptor) interceptorOrInterceptionAdvice;
return mi.invoke(this);
} else {
// 动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor
return proceed();
}
}
4.代理封装(support包)
MYAdvisedSupport
主要封装了被代理对象信息,以及一些处理被代理对象的方法。
每个IOC容器中对象都一一对应一个AdvisedSupport对象,但只有符合pointCut的对象才是真正的target
public class MYAdvisedSupport {
// 目标类
private Class<?> targetClass;
// 目标对象,即被代理对象
private Object target;
// 配置文件信息
private MYAopConfig config;
// 被代理类特征正则表达式
private Pattern pointCutClassPattern;
// 保存当前对象切入点方法们,与拦截器链的映射关系
private transient Map<Method, List<Object>> methodCache;
public MYAdvisedSupport(MYAopConfig config) {
this.config = config;
}
public void setTarget(Object target) {
this.target = target;
}
// 将当前类放入AdvisedSupport对象中,同时初始化pointCutClassPattern(被代理类特征正则)
public void setTargetClass(Class<?> targetClass) {
this.targetClass = targetClass;
parse();
}
public Class<?> getTargetClass() {
return this.targetClass;
}
public Object getTarget() {
return this.target;
}
//.......
}
parse()
会在创建代理时 setTargetClass 方法中调用,
- 初始化目标类特征 pointCutClassPattern
- 保存切面类的所有方法(LogAspect),将它们放入 aspectMethods 的Map中
- 为当前类中符合切入点的方法构造拦截器链,并将它们放入map中
private void parse() {
String pointCut = config.getPointCut()
.replaceAll("\\.","\\\\.")
.replaceAll("\\\\.\\*",".*")
.replaceAll("\\(","\\\\(")
.replaceAll("\\)","\\\\)");
// pointCut=public .* com.xupt.yzh.demo.service..*Service..*(.*)
// 1.通过正则表达式获取被代理类特征,初始化pointCutForClassRegex
String pointCutForClassRegex = pointCut.substring(0,pointCut.lastIndexOf("\\(") - 4);
pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(
pointCutForClassRegex.lastIndexOf(" ") + 1));
try {
// 2.拿到切面类,这里是LogAspect,并保存其所有方法
Class aspectClass = Class.forName(this.config.getAspectClass());
// 保存切面类(LogAspect)的所有方法
Map<String,Method> aspectMethods = new HashMap<String,Method>();
for (Method m : aspectClass.getMethods()) {
aspectMethods.put(m.getName(),m);
}
methodCache = new HashMap<Method, List<Object>>();
// 拿到切入点正则,即切入方法特征
Pattern pattern = Pattern.compile(pointCut);
// 3.遍历当前类的所有方法,为符合切入点的方法构造拦截器链,最后放入map中
for (Method m : this.targetClass.getMethods()) {
String methodString = m.toString();
if (methodString.contains("throws")) {
methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();
}
// 判断当前方法是否是切点方法
Matcher matcher = pattern.matcher(methodString);
if(matcher.matches()){
// 若是,则构造一个执行器链,
List<Object> advices = new LinkedList<Object>();
// 把每一个方法包装成 MethodIterceptor
// before
if(!(null == config.getAspectBefore() || "".equals(config.getAspectBefore()))) {
//创建一个Advivce
advices.add(new MYMethodBeforeAdviceInterceptor(aspectMethods.get(config.getAspectBefore()),aspectClass.newInstance()));
}
// after
if(!(null == config.getAspectAfter() || "".equals(config.getAspectAfter()))) {
//创建一个Advivce
advices.add(new MYAfterReturningAdviceInterceptor(aspectMethods.get(config.getAspectAfter()),aspectClass.newInstance()));
}
// afterThrowing
if(!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow()))) {
//创建一个Advivce
MYAfterThrowingAdviceInterceptor throwingAdvice =
new MYAfterThrowingAdviceInterceptor(
aspectMethods.get(config.getAspectAfterThrow()),
aspectClass.newInstance());
throwingAdvice.setThrowName(config.getAspectAfterThrowingName());
advices.add(throwingAdvice);
}
// 最后,将当前方法与执行器链放入mthodCache
methodCache.put(m,advices);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
pointCutMatch()
判断当前AdvisedSupport是否能匹配上切点表达式的类
public boolean pointCutMatch() {
// 通过被代理类特征正则表达式来匹配当前类
return pointCutClassPattern.matcher(this.targetClass.toString()).matches();
}
getInterceptorsAndDynamicInterceptionAdvice()
获取指定方法的执行器链
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) throws Exception{
List<Object> cached = methodCache.get(method);
// 如果当前方法没有拦截器链,那么把拦截器链置为null,也封装一下,然后返回原方法
if (cached == null) {
Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
// cached 就等于原方法
cached = methodCache.get(m);
// 底层逻辑,对代理方法进行一个兼容处理
this.methodCache.put(m, cached);
}
return cached;
}
到此为止 aop 包中的代码就实现完了,但是还有一步没做。
5.代理实现(aop核心)
MYAopProxy
AOP 本质就是一个代理模式,MYAopProxy 是代理的最顶层接口,有两种实现(JDK,cglib)
public interface MYAopProxy {
Object getProxy();
Object getProxy(ClassLoader classLoader);
}
MYCglibAopProxy
基于 Cglib 生成代理对象,因为 JDK 的动态代理有局限性(必须有接口或继承关系)
public class MYCglibAopProxy implements MYAopProxy {
@Override
public Object getProxy() {
return null;
}
@Override
public Object getProxy(ClassLoader classLoader) {
return null;
}
}
cglib 代理这里先不做实现…
MYJdkDynamicAopProxy
基于 JDK 动态代理生成代理对象
public class MYJdkDynamicAopProxy implements MYAopProxy, InvocationHandler {
private MYAdvisedSupport advised;
public MYJdkDynamicAopProxy(MYAdvisedSupport config) {
this.advised = config;
}
//......
}
getProxy()
获取代理对象
// 通过该方法对外返回Proxy
@Override
public Object getProxy() {
// 调用传入ClassLoader的方法,创建Proxy
return getProxy(this.advised.getTargetClass().getClassLoader());
}
// 通过jdk动态代理具体生成代理对象
@Override
public Object getProxy(ClassLoader classLoader) {
// newProxyInstance方法会根据被代理对象ClassLoader,class对象,及增强逻辑(InvocationHanlder.invoke)生成一个代理对象
// 注:动态代理只能代理有接口对象,本质原因是单继承
return Proxy.newProxyInstance(classLoader, this.advised.getTargetClass().getInterfaces(), this);
}
invoke()
当 doDispatch 将请求分发给当前方法后执行
- 这个方法是在调用后执行,不是在初始化时执行,初始化时只是通过AdvisedSupport创建了拦截器链
- 对外是invoke原对象的方法,但实际上执行invoke代理对象的方法
- 这里传入要执行的method,可以对进行判断进行选择性增强
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在AdvisedSupport中获取方法的拦截器链
// 其中符合切点表达式的方法会有拦截器链,而不是切点的方法就只有自己
List<Object> interceptorsAndDynamicMethodMatchers = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, this.advised.getTargetClass());
// 获取拦截器链执行器
MYMethodInvocation methodInvocation = new MYMethodInvocation(proxy, this.advised.getTarget(), method, args, this.advised.getTargetClass(), interceptorsAndDynamicMethodMatchers);
// 执行拦截器链及invoke当前方法
return methodInvocation.proceed();
}
6.入口:MyApplicationContext
在 ApplicationContext#instantiteBean 创建实例对象时,我们需要将原本的对象变为代理对象放入 IOC 容器。
instantiteBean()
private MYBeanWrapper instantiteBean(String beanName, MYBeanDefinition myBeanDefinition) {
// 1.拿到要实例化的类名
String className = myBeanDefinition.getBeanClassName();
// 2.通过反射进行实例化
Object instance = null;
try {
// 默认所有对象都是单例的,即都可以通过单例IOC容器中获取Bean
if (this.factoryBeanObjectCache.containsKey(className)) {
instance = this.factoryBeanObjectCache.get(className);
} else {
Class<?> clazz = Class.forName(className);
instance = clazz.newInstance();
this.factoryBeanObjectCache.put(myBeanDefinition.getFactoryBeanName(), instance);
//-------------------------AOP部分入口代码-----------------------
// 读取配置文件中的信息,并以当前对象信息构建一个AdvisedSupport对象
MYAdvisedSupport config = instantionAopConfig(myBeanDefinition);
config.setTargetClass(clazz);
config.setTarget(instance);
// 符合PointCut的规则的话(即符合被代理的对象要求),创建将代理对象
// 然后用代理对象替换当前对象,并放入IOC容器,
// 到时 mvc 初始化IOC容器时,就会将代理对象放入,再后来创建处理请求的handler时就会将该代理对象封装进去
if(config.pointCutMatch()) {
// 这时获取到的 Proxy 持有的AdvisedSupport已经构造好了拦截器链
// 到时 mvc 分发请求过来直接 proceed 执行即可
instance = createProxy(config).getProxy();
}
//----------------------------------------------------------------
// 注:这里还要根据className(全类名)放一个
// 因为在通过类型进行getBean时,BeanDefinition只封装了接口做factoryName
this.factoryBeanObjectCache.put(className, instance);
}
} catch (Exception e) {
e.printStackTrace();
}
// 3.封装BeanWrapper
// 注:无论单例多例,都要先封装成 BeanWrapper
MYBeanWrapper beanWrapper = new MYBeanWrapper(instance);
return beanWrapper;
}
instantionAopConfig()
// 读取配置文件信息,封装成AopCofig后交给AopSupport去处理
private MYAdvisedSupport instantionAopConfig(MYBeanDefinition myBeanDefinition) {
MYAopConfig config = new MYAopConfig();
config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
return new MYAdvisedSupport(config);
}
createProxy()
// 创建代理对象
private MYAopProxy createProxy(MYAdvisedSupport config) {
Class<?> targetClass = config.getTargetClass();
// 当前类存在接口时,使用jdk的动态代理
if (targetClass.getInterfaces().length > 0) {
return new MYJdkDynamicAopProxy(config);
}
// 否则使用 cglib 代理(未实现)
return new MYCglibAopProxy();
}
到此 aop 部分实现完成,下面我们来测试一下。
测试
在配置文件中,我们设置的是
# 切点表达式
pointCut=public .* com.xupt.yzh.demo.service..*Service..*(.*)
# 切面类
aspectClass=com.xupt.yzh.demo.aspect.LogAspect
# 前置通知
aspectBefore=before
# 后置通知
aspectAfter=after
# 异常通知
aspectAfterThrow=afterThrowing
# 异常类型
aspectAfterThrowingName=java.lang.Exception
也就是说 service 包下的所有方法都会被增强,包括前置通知,后置通知和异常通知。切面如下:
@Slf4j // 借助 slf4j 打印日志
public class LogAspect {
// 在调用一个方法之前,执行before方法
public void before(MYJoinPoint joinPoint){
joinPoint.setUserAttribute("startTime_" + joinPoint.getMethod().getName(),System.currentTimeMillis());
// 这个方法中的逻辑,是由我们自己写的
log.info("Invoker Before Method!!!" // 打印 Invoker Before Method!!!
+ "\nTargetObject:" + joinPoint.getThis() // 打印请求对象
+" \nArgs:" + Arrays.toString(joinPoint.getArguments())); // 打印请求参数
}
// 在调用一个方法之后,执行after方法
public void after(MYJoinPoint joinPoint){
log.info("Invoker After Method!!!" +
"\nTargetObject:" + joinPoint.getThis() +
"\nArgs:" + Arrays.toString(joinPoint.getArguments()));
long startTime = (Long) joinPoint.getUserAttribute("startTime_" + joinPoint.getMethod().getName());
long endTime = System.currentTimeMillis();
// 打印执行方法执行时间
System.out.println("use time :" + (endTime - startTime));
}
// 异常通知
public void afterThrowing(MYJoinPoint joinPoint, Throwable ex){
log.info("出现异常" +
"\nTargetObject:" + joinPoint.getThis() +
"\nArgs:" + Arrays.toString(joinPoint.getArguments()) +
"\nThrows:" + ex.getMessage());
}
}
结果如下:
可以看到已经成功实现了 AOP 功能!
完整代码我放到 GitHub 上了,可以点击这里跳转…