在上一篇文章自己动手实现Spring中,介绍了本人自己实现的一个简单的IOC容器spring-toy。spring-toy的v0.1版本初步实现了IOC容器,但并没有实现AOP 功能。
在v0.2版本中,实现了以下功能:
- 支持通过FactoryBean注入单实例到容器中。
- 支持AOP。可以通过将目标实例,Advisor或者Advice配置到ProxyFactoryBean实例,并将ProxyFactoryBean实例注入到容器中的方式实现AOP拦截。
- 同时支持使用AspectJ的注解,声明Aspect以及通知,实现AOP拦截。
v0.2版本基本实现了SpringAOP的基本功能,在本篇文章中,将介绍笔者是如何实现的AOP功能。
FactoryBean
首先,需要介绍的是FactoryBean,这是一个接口,用来将实例注入到容器中。之所以先介绍这个接口,是因为笔者并没有实现XML的方式声明AOP切面以及切点的功能,只能通过注入Bean的方式实现AOP。以下是FactoryBean的定义:
/**
* 用于直接将Bean注入到容器中
*
* @author bdq
* @since 2019-08-01
*/
public interface FactoryBean<T> {
/**
* 返回对象实例
*
* @return T 对象实例
* @throws BeansException bean异常
*/
T getObject() throws BeansException;
/**
* 返回Bean的类型
*
* @return Class<?> Bean的类型
*/
Class<?> getObjectType();
/**
* 是否单例
*
* @return boolean
*/
default boolean isSingleton() {
return true;
}
}
将Bean注入到容器中只需要通过调用ApplicationContext的registerSingleBean(FactoryBean factoryBean)
方法,将实现了该接口的类传入即可。
ApplicationContext将会分析注入的Bean,将BeanDefinition以及factoryBean一起注入到BeanFactory中。代码如下:
public void registerSingleBean(FactoryBean factoryBean) throws BeansException {
String beanName = this.beanNameGenerator.generateBeanName(factoryBean.getObjectType());
Class<?> objectType = factoryBean.getObjectType();
BeanDefinition beanDefinition = new BeanDefinition(objectType, ScopeType.SINGLETON, beanName, false);
beanFactory.registerBeanDefinition(beanName, beanDefinition);
beanFactory.registerSingleBean(beanName, factoryBean);
}
其中,registerBeanDefinition()
和registerSingleBean()
代码实现如下:
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeansException {
if (beanDefinitions.containsKey(beanName)) {
throw new ConflictedBeanException(String.format("the entity named %s has conflicted ! ", beanName));
}
beanDefinitions.put(beanName, beanDefinition);
}
@Override
public void registerSingleBean(String beanName, FactoryBean factoryBean) throws BeansException {
instances.put(beanName, factoryBean);
}
将factoryBean注入到instances容器之后,可以通过getBean()
方法获取Bean实例,关键代码如下:
Object instance = instances.get(beanName);
if (instance instanceof FactoryBean) {
try {
FactoryBean factoryBean = (FactoryBean) instance;
return factoryBean.getObject();
} catch (Exception e) {
throw new BeansException(e);
}
}
return instance;
在了解了FactoryBean机制之后,再来介绍一下AOP具体实现。
Advisor和Advice
在Spring中,通过配置Advisor和Advice声明一个切面,从而实现AOP功能。笔者参考这个机制,实现了一个简单版本。首先看一下aop包下的定义的类:
image其中Advice的定义,与Spring相同,就不做过多介绍。Advisor接口的定义如下:
/**
* 顾问接口,通知接口的增强,可以实现更复杂的通知
*
* @author bdq
* @since 2019-07-29
*/
public interface Advisor extends Advice {
/**
* 设置切点
*
* @param pointcut 切点表达式
*/
void setPointcut(String pointcut);
/**
* 获取切点表达式
*
* @return String
*/
String getPointcut();
/**
* 设置通知
*
* @param advice 通知
*/
void setAdvice(Advice advice);
/**
* 获取通知
*
* @return Advice
*/
Advice getAdvice();
/**
* 代理方法是否匹配通知
*
* @param method 代理方法
* @param adviceType 通知类型
* @return boolean
*/
boolean isMatch(Method method, Class<?> adviceType);
}
Advisor定义了切点,以及切点对应的通知,同时定义了匹配方法,方便进行通知匹配。AbstractAdvisor是Advisor的抽象实现,主要实现了get以及set方法。
Advisor最终实现类之一为RegexpMethodAdvisor,RegexpMethodAdvisor表示通过正则表达式来定义切面,实现通知方法的匹配,主要是实现了isMatch()
方法。RegexpMethodAdvisor代码如下:
@Override
public boolean isMatch(Method method, Class<?> adviceType) {
MethodSignature methodSignature = new MethodSignature(adviceType, method);
String fullyMethodName = methodSignature.toLongString();
return adviceType.isAssignableFrom(getAdvice().getClass()) && fullyMethodName.matches(getPointcut());
}
其中MethodSignature是方法签名类,方便获取方法签名,在此不做过多介绍。
MethodInvocation是切点实现类,封装了切点信息,通过调用MethodInvocation的proceed()
方法,执行前置通知和具体的代理方法。代码如下:
@Override
public Object proceed() throws Throwable {
for (MethodBeforeAdvice methodBeforeAdvice : beforeAdvices) {
if (methodBeforeAdvice instanceof AspectAdvice) {
AspectAdvice aspectAdvice = (AspectAdvice) methodBeforeAdvice;
aspectAdvice.setJoinPoint(this);
}
methodBeforeAdvice.before(method, args, target);
}
return method.invoke(target, args);
}
代理增强
在定义了相关的Advisor和Advice之后,需要在代理中获取,并按照通知类型按顺序调用。这部分的功能,主要定义在AdvisorInvocationHandler接口中,代码如下:
/**
* 用于处理通知的执行
*
* @author bdq
* @since 2019-07-31
*/
public interface AdvisorInvocationHandler {
/**
* 设置advisors
*
* @param advisors 所有顾问
*/
void setAdvisors(List<Advisor> advisors);
/**
* 执行代理方法以及通知方法
*
* @param target 代理实例
* @param method 代理方法
* @param args 代理方法参数
* @return Object 执行结果
* @throws Throwable 异常
*/
Object invokeWithAdvice(Object target, Method method, Object[] args) throws Throwable;
}
其实现类AdvisorInvocationHandlerImpl实现了invokeWithAdvice()
用于执行通知方法逻辑,代码如下:
@Override
public Object invokeWithAdvice(Object target, Method method, Object[] args) throws Throwable {
MethodSignature methodSignature = new MethodSignature(target.getClass(), method);
MethodBeforeAdvice[] beforeAdvices = getMethodBeforeAdvices(method, methodSignature);
MethodInvocation invocation = new MethodInvocation(target, method, args, beforeAdvices);
MethodInterceptor[] aroundAdvices = getAroundAdvices(method, methodSignature);
if (aroundAdvices.length > 0) {
//执行环绕通知
for (MethodInterceptor aroundAdvice : aroundAdvices) {
try {
Object returnValue = doAround(aroundAdvice, invocation);
doAfterReturning(invocation, returnValue);
return returnValue;
} catch (Exception e) {
doThrows(invocation, e);
}
}
} else {
try {
Object returnValue = invocation.proceed();
doAfterReturning(invocation, returnValue);
return returnValue;
} catch (Exception e) {
doThrows(invocation, e);
}
}
return null;
}
从代码中可以看出,首先执行环绕通知,然后通过MethodInvocation的proceed()
方法调用前置通知以及具体的代理方法,最后依次调用后置通知和异常通知。
动态代理
动态代理主要有两种方式,一种是通过jdk进行代理,另一种是通过cglib进行代理,具体可以参考proxy包下的JdkInvocationHandler和CglibMethodInterceptor。通过在动态代理回调中,调用AdvisorInvocationHandler的invokeWithAdvice()
方法执行增强通知,由此实现了AOP功能。以JdkInvocationHandler为例,主要代码如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object targetObject = getTargetObject();
Object result = invokeObjectMethod(targetObject, method, args);
if (result == null) {
result = advisorInvocationHandler.invokeWithAdvice(targetObject, method, args);
}
return result;
}
代理工厂
ProxyFactory是用来生成代理实例的类,其封装了生成代理实例的方法,以及将通知注入到AdvisorInvocationHandler,并传入JdkInvocationHandler或者CglibMethodInterceptor中。主要代码如下:
/**
* 获取代理实例
*
* @return Object 代理实例
* @throws BeansException bean异常
*/
public Object getProxy() throws BeansException {
AdvisorInvocationHandler advisorInvocationHandler = getAdviceInvocationHandler();
return createProxyInstance(advisorInvocationHandler);
}
private Object createProxyInstance(AdvisorInvocationHandler advisorInvocationHandler) throws BeansException {
ProxyInvocationHandler invocationHandler;
//实例化代理生成类
if (interfaces != null && interfaces.length > 0) {
invocationHandler = new JdkInvocationHandler(advisorInvocationHandler);
} else {
invocationHandler = new CglibMethodInterceptor(advisorInvocationHandler);
}
invocationHandler.setTarget(target);
invocationHandler.setInterfaces(interfaces);
return invocationHandler.newProxyInstance();
}
private AdvisorInvocationHandler getAdviceInvocationHandler() {
AdvisorInvocationHandler advisorInvocationHandler = new AdvisorInvocationHandlerImpl();
if (advisors.size() > 0) {
advisorInvocationHandler.setAdvisors(advisors);
}
if (beanFactory != null) {
//从BeanFactory获取Aspect通知
AdvisorBeanFactoryImpl advisorBeanFactoryImpl = (AdvisorBeanFactoryImpl) beanFactory;
advisorInvocationHandler.setAdvisors(advisorBeanFactoryImpl.getAdvisors());
}
return advisorInvocationHandler;
}
最后,通过注入ProxyFactoryBean到容器中,实现AOP功能。ProxyFactoryBean是一个FactoryBean实现,其功能是声明目标类以及需要的增强通知。代码如下:
/**
* FactoryBean,代理实例直接注入到BeanFactory
*
* @author bdq
* @since 2019-07-30
*/
public class ProxyFactoryBean implements FactoryBean<Object> {
/**
* 目标实例
*/
private Object target;
/**
* 代理类型
*/
private Class<?>[] interfaces;
/**
* 代理工厂
*/
private ProxyFactory proxyFactory;
public ProxyFactoryBean() {
proxyFactory = new ProxyFactory();
}
public void setTarget(Object target) {
this.target = target;
}
public void setInterfaces(Class<?>... proxyInterfaces) {
this.interfaces = proxyInterfaces;
}
@Override
public Object getObject() throws BeansException {
proxyFactory.setTarget(target);
proxyFactory.setInterfaces(target.getClass().getInterfaces());
if (interfaces != null) {
if (!(interfaces.length == 1 && interfaces[0] == target.getClass())) {
proxyFactory.setInterfaces(interfaces);
}
}
return proxyFactory.getProxy();
}
@Override
public Class<?> getObjectType() {
return target.getClass();
}
/**
* 添加通知
*
* @param advice 通知
*/
public void addAdvice(Advice advice) {
proxyFactory.addAdvice(advice);
}
}
使用示例如下:
@Test
public void testProxyFactoryBean() throws ApplicationContextException {
ApplicationContext applicationContext = new AnnotationApplicationContext("test.cn.bdqfork.ioc.factorybean");
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setInterfaces(UserDao.class);
proxyFactoryBean.setTarget(new UserDao());
proxyFactoryBean.addAdvice(new Before());
applicationContext.registerSingleBean(proxyFactoryBean);
UserDao userDao = applicationContext.getBean(UserDao.class);
userDao.test();
}
Aspect注解
通过ProxyFactoryBean,只能实现外部实例的增强,且需要大量的手动注入,十分的不方便。因此,笔者参考Spring,引入Aspect注解,实现了通过注解的方式配置AOP。Aspect注解的支持,本质上是对Advice和Advisor的封装,核心实现类如下:
image代码比较简单,就不过多介绍了。然后是对Aspect注解的解析,这部分代码可以查看AspectResolver类的resolve()
方法,解析Aspect注解修饰的类,并生成AspectAdvisor以及AspectAdvice,并注入到BeanFactory中,ProxyFactory将会从BeanFactory中获取相关的Advisor生成代理实例。
Aspect注解方式的使用方法如下:
/**
* @author bdq
* @since 2019-07-28
*/
@Scope(ScopeType.PROTOTYPE)
@Component
@Aspect
public class Log {
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
System.out.println("执行前置通知方法");
}
@AfterReturning(value = "pointcut()", returning = "result")
public void after(JoinPoint joinPoint, Object result) {
System.out.println("执行后置通知方法,return : " + result);
}
@Around("execution(test.cn.bdqfork.ioc.aop.*)")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("执行环绕通知方法,目标方法执行之前");
Object result = pjp.proceed();
System.out.println("执行环绕通知方法,目标方法执行之后");
if (result != null) { //可以修改目标方法的返回结果
result = ((String) result).toUpperCase();
}
return result;
}
@AfterThrowing(value = "pointcut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
System.out.println("执行异常抛出通知方法,Exception : " + ex);
}
@Pointcut("execution(test.cn.bdqfork.ioc.aop.*)")
public void pointcut() {
}
}
/**
* @author bdq
* @since 2019-07-30
*/
public class TestProxyFactory {
@Test
public void testAspect() throws ApplicationContextException {
ApplicationContext applicationContext = new AnnotationApplicationContext("test.cn.bdqfork.ioc.aop");
UserDaoImpl userDao = applicationContext.getBean(UserDaoImpl.class);
userDao.testAop();
System.out.println("----------------------------------------");
userDao.testThrowing();
}
}
以上是笔者实现AOP的思路,具体细节,限于篇幅原因,没有一一介绍,可以通过查看源码进行了解。笔者的代码已经上传到Github中,点击 spring-toy 查看。笔者技术水平有限,如果有问题,请联系笔者,感谢大家匹配指正。
网友评论