title: 08.解析切面类并实现代理类逻辑
tag: 笔记 手写SSM AOP
通能够生成代理对象后,我们就需要实现代理对象的调用逻辑了。
分析
在上一节我们实现了创建代理对象的工厂ProxyFactory
,我们需要下面两个参数即可拿到原始Bean的代理对象。
- 原始
Bean
对象 - 一个处理代理对象方法逻辑的
InvocationHandler
获取Beans
对于第一个条件,我们需要拿到IOC容器中所有Bean的实例,但我们AOP模块如何拿到IOC容器中的所有Bean呢?
这里参照Spring中的一系列Aware接口,当一个Bean注册到容器中,若这个Bean实现了某个Aware接口,那么在Bean实例化或者初始化时Spring会将与接口对应的信息注入到这个Bean中。
例如下面这个例子:
public class MyBean implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void doSomething() {
// 获取其他的Bean实例或其他的组件
OtherBean otherBean = applicationContext.getBean("otherBean", OtherBean.class);
// ...
}
}
这个MyBean注册到IOC容器后,因为它实现了ApplicationContextAware
接口,因此Spring会在初始化或者实例化时会调用接口的setApplicationContext
方法将容器注入到这个类中,我们就可以在这个类中使用applicationContext
。
在我们现在的这个场景下,我们只需要拿到所有Bean,因此我们自定义一个接口,在IOC容器实例化时检测是否有Bean实现该接口,若实现则将Beans注入在这个Bean中。如此我们即可拿到容器中的全部Bean信息。
解析Aspect
拿到Bean之后,我们还需要找到Aspect的Bean,解析Aspect切面类的拦截规则。因为可能存在一个PointCut
规则对应着多个Advice
的情况,并且我们的PointCut只支持注解。我们可以使用一个Map结构来表达这个拦截规则,键是PointCut注解的名字,值是Advice的集合。
比如下面这个切面类:
@Aspect
@Component
public class MyAspect {
/**
* 拦截所有方法上携带 MyAopAnnotation 注解的方法
* @param joinPoint
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@Around(targetAnno = target.class)
public Object testAspect(ProceedingJoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {
long startTime = System.currentTimeMillis();
//方法放行
Object proceed = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("总共用时:"+(endTime - startTime));
return proceed;
}
/**
* 拦截所有方法上携带 MyAopAnnotation 注解的方法
* @param joinPoint
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@Around(targetAnno = target.class)
public Object testAspect2(ProceedingJoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {
long startTime = System.currentTimeMillis();
//方法放行
Object proceed = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("总共用时:"+(endTime - startTime));
return proceed;
}
@Around(targetAnno = DAO.class)
public Object DAOAdvice(ProceedingJoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {
System.out.println("DAO层调用开始");
Object proceed = joinPoint.proceed();
System.out.println("DAO层调用开始");
return proceed;
}
}
解析这个切面类,我们可以得到两条拦截规则:
- @DAO: DAOAdvice
- @target: testAspect,testAspect2.
代理类的方法调用逻辑
在解析了Aspect定义的拦截之后,我们就可以拦截需要生成代理的Bean为其生成代理对象了。这个时候我们就需要编写InvocationHandler
来决定代理对象的方法调用逻辑了。
我们先来回顾一下Spring中如何编写一个通知:
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
Object result = point.proceed();
long executeTime = System.currentTimeMillis() - beginTime;
recordLog(point, executeTime,result);
return result;
}
在进行环绕通知时,Spring需要我们在通知的签名中加入一个ProceedingJoinPoint
来存储一些表示原始对象的一些信息,其中最重要的方法就是proceed
方法,这个方法表示执行原始方法。因此我们需要将代理方法的执行转发到这个Advice通知中执行。
大致流程应当如下:
- 根据解析的拦截规则解析该方法存在的
Advice
链。若为0则说明不需要代理。 - 构建
ProceedingJoinPoint
参数并将参数传入Advice并递归调用Advice链。(重点)
其中第二步使用递归较为复杂。
使用BeanPostProcessor替换原始对象
在完成了如何创建代理之后,我们就可以使用BeanPostProcessor
去替换IOC容器中原始的Bean了。
我们可以创建一个类,这个类实现两个接口:
- BeanPostProcessor
创建Bean实例时创建代理对象替换原始的Bean,并且在注入时注入到原始的Bean
- BeansAware
注入Beans时同时解析Aspect的拦截规则
实现
在上面的分析后,我们有了创建IOC容器中需要增强的Bean的代理类的思路了,下面我们可以使用代码来进行实现。
创建Around通知注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Around {
/**
* Invocation handler bean name.
*/
Class<? extends Annotation> targetAnno();
}
Around注解只能标注到方法上,并且需要传入一个注解的参数。
创建表示Advice和ProceedingJoinPoint的类
/**
* @author 白日
* @create 2024/1/4 18:24
* @description 通知方法
*/
public class Advice {
/**
* 切点的方法
*/
private Method method;
/**
* 切点的对象
*/
private Object target;
/**
* 切点的参数
*/
private Object[] args;
public Advice(Method method, Object target) {
this.method = method;
this.target = target;
}
public Object invoker() throws InvocationTargetException, IllegalAccessException {
method.setAccessible(true);
return method.invoke(target, args);
}
}
这个类代表了用户定义的切点,有一个invoker
方法调用这个切点方法,并且需要传入一个ProceedingJoinPoint
作为参数。
/**
* @author 白日
* @create 2024/1/4 18:45
* @description 代表了正在执行的方法的连接点(join point)
*/
public class ProceedingJoinPoint {
/**
* 目标方法的参数
*/
private Object[] args;
/**
* 目标对象
*/
private Object target;
/**
* 目标方法
*/
private Method method;
/**
* 存在的调用链
*/
private List<Advice> proxyChains = new ArrayList<>(8);
/**
* 当前调用链的指针位置
*/
private int chainsIndex = 0;
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target,args);
}
}
这个类代表了正在执行的方法的连接点(join point),其中proceed
方法会执行连接点的方法,并且这个类会作为一个Around坏绕通知的参数传入。
实现代理方法调用逻辑
这一步我们需要定义我们代理方法的执行逻辑:
- 根据解析的拦截规则解析该方法存在的
Advice
链。若为0则说明不需要代理。 - 构建
ProceedingJoinPoint
参数并将参数传入Advice并递归调用Advice链。(重点)
public class DynamicAopProxy implements InvocationHandler {
Object target;//原始对象
public Map<Class<? extends Annotation>, List<Advice>> proxyRule = new ConcurrentHashMap<>();//拦截规则
List<Advice> proxyChains = new ArrayList<>(8); //当前Advice调用链
int chainsIndex = 0; //调用链指针
public DynamicAopProxy(Map<Class<? extends Annotation>, List<Advice>> proxyRule, Object target){
this.proxyRule = proxyRule;
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(chainsIndex == 0){
getCurProxyChains(method);
}
//递归中止条件
if(proxyChains.size() == chainsIndex){
chainsIndex = 0; //清空调用链
proxyChains = new ArrayList<>(8); //清空调用链
return method.invoke(target, args);//调用链执行完毕,调用原始方法。
}
//构造当前的Advice并调用
Advice advice = getAdvice(proxy, method, args);
return advice.invoker();//递归入口,在advice中调用method.invoke(proxy,args)仍然会转发到到invoke方法InvocationHandler的invoke方法。
}
private Advice getAdvice(Object proxy, Method method, Object[] args) {
//构建ProceedingJoinPoint
ProceedingJoinPoint proceedingJoinPoint = new ProceedingJoinPoint();
proceedingJoinPoint.setArgs(args);
proceedingJoinPoint.setMethod(method);
proceedingJoinPoint.setProxyChains(proxyChains);
proceedingJoinPoint.setTarget(proxy);//我们传入proxy代理对象
//构建Advice
Advice advice = proxyChains.get(chainsIndex++);//指针指向下一个Advice
proceedingJoinPoint.setChainsIndex(chainsIndex);
advice.setArgs(new Object[]{proceedingJoinPoint});
return advice;
}
private void getCurProxyChains(Method method) {
List<Advice> curProxyChains = new ArrayList<>(8);
proxyRule.forEach((key, value) -> {
if (target.getClass().isAnnotationPresent(key) ||
method.isAnnotationPresent(key)) {
curProxyChains.addAll(value);
}
});
this.proxyChains = curProxyChains;
}
}
实现代理的BeanPostProcessor
@Component
public class AOPProxyCreator implements BeanPostProcessor, BeansAware {
Map<String, Object> originBeans = new HashMap<>();
public Map<String, BeanDefinition> beans;
public final Map<Class<? extends Annotation>, List<Advice>> proxyRule = new ConcurrentHashMap<>();
public List<Object> aspectInstance = new ArrayList<>(8);
ProxyFactory proxyResolver = new ProxyFactory();
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
Class<?> beanClass = bean.getClass();
if(checkJoinPoint(beanClass)){
originBeans.put(beanName, bean);
return proxyResolver.createProxy(bean, new DynamicAopProxy(proxyRule, bean));
}
return bean;
}
private boolean checkJoinPoint(Class<?> beanClass) {
for (Annotation annotation : beanClass.getAnnotations()) {
if(proxyRule.containsKey(annotation.annotationType())){
return true;
}
}
for (Method method : beanClass.getMethods()) {
for (Annotation annotation : method.getAnnotations()) {
if(proxyRule.containsKey(annotation.annotationType())){
return true;
}
}
}
return false;
}
@Override
public Object postProcessOnSetProperty(Object bean, String beanName) {
Object origin = this.originBeans.get(beanName);
return origin != null ? origin : bean;
}
@Override
public void setApplicationContext(Map<String, BeanDefinition> beans) {
this.beans = beans;
aspectInstance = getAspectInstance();
parseAspectjClass();
logger.debug("解析后的拦截规则为:{}", proxyRule);
}
private void parseAspectjClass(){
for (Object aspect : aspectInstance) {
for (Method method : aspect.getClass().getMethods()) {
Around around = method.getAnnotation(Around.class);
if(around != null){
Advice advice = new Advice(method, aspect);
Class<? extends Annotation> targetAnno = around.targetAnno();
if(proxyRule.containsKey(targetAnno)){
proxyRule.get(targetAnno).add(advice);
}else {
List<Advice> proxyChains = new ArrayList<>();
proxyChains.add(advice);
proxyRule.put(targetAnno, proxyChains);
}
}
}
}
}
public List<Object> getAspectInstance() {
List<BeanDefinition> aspectDef = beans.values().
stream()
.filter(definition -> definition.getBeanClass().isAnnotationPresent(Aspect.class))
.toList();
return aspectDef.stream().map(BeanDefinition::getInstance).toList();
}
}
}
}
}
}
public List<Object> getAspectInstance() {
List<BeanDefinition> aspectDef = beans.values().
stream()
.filter(definition -> definition.getBeanClass().isAnnotationPresent(Aspect.class))
.toList();
return aspectDef.stream().map(BeanDefinition::getInstance).toList();
}
}