目标
我们实现和原生spring相类似的AOP机制,即通过在切面类中的方法上面通过注解指定要切入的位置。当然,使用方法肯定也要和原生的一样,实现主要的功能。
自定义注解
我们首先需要定义@Aspect注解,这个注解用在类上面,表示是一个切面类
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Aspect {
String value() default "";
}
然后定义一个@Order注解,用于指定多个切面的顺序问题
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface Order {
int value() default Integer.MAX_VALUE;
}
定义一个@Before和@After注解,用来指定切入的位置,@AfterReturning,@AfterThrowing相类似的,这里就不写了。感兴趣可以自己扩展,基本改个名字就行
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Before {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface After {
String value();
}
自定义JoinPoint
我们使用spring的AOP的时候,都知道有一个JoinPoint,通过JoinPoint可以获取到各种信息,我们这里也定义一个JoinPoint,当然,我们自定义的就封装一点简单信息。
public class JoinPoint {
private Method method;
private Object[] agrs;
public String getName(){
return method.getName();
}
public Object[] getArgs(){
return agrs;
}
public JoinPoint(Method method, Object[] agrs) {
this.method = method;
this.agrs = agrs;
}
}
我们通过JoinPoint可以获取到切面方法的名称已有经方法中的参数
定义存储切面类的集合
我们定义一个Map来存放切面类,然后再定义一个List来存储切面类的名称。
private List<String> aspectClassNames;
private Map<String, Integer> aspectClass;
//代码块初始化集合
{
aspectClassNames = new ArrayList<>();
aspectClass = new HashMap<>();
}
这里我们再定义一个Map来对private,protectd,public进行映射
private Map<String, Integer> map;
{
map = new HashMap<>();
map.put("public", 1);
map.put("private", 2);
map.put("protect", 4);
}
定义MethodInfo内部类
这个类用来表示method的各种信息,如方法修饰符,返回值,参数等等
/**
* 一个内部类,用于存储方法的信息
*/
private class MethodInfo {
public int modify;
public String returnType;
public String methodName;
public Object[] args;
public String fullClassName;
public MethodInfo(int modify, String returnType, String methodName, Object[] args, String fullClassName) {
this.modify = modify;
this.returnType = returnType;
this.methodName = methodName;
this.args = args;
this.fullClassName = fullClassName;
}
}
解析切面表达式
我们知道,使用AOP,那么肯定就有切面表达式,切面表达式往往比较复杂,并且还支持正则表达式,这里我们就简化一下,简化为 exection(修饰符 返回类型 方法全路径(参数))的形式,我们这里提供一个解析切面表达式的方法,传入一个切面表达式,返回一个MethodInfo内部类对象。
protected MethodInfo getMethodInfo(String value) {
int modify = 0;
String returnType = null;
String fullClassName = null;
String methodName = null;
String[] methodArgs = new String[0];
try {
String s = value.substring(value.indexOf("(") + 1, value.lastIndexOf(")")).trim();
//将多个空格替换为单个空格
s = s.replaceAll(" +", " ");
//按照空格分割
String[] strings = s.split(" ");
//得到修饰符
modify = map.get(strings[0]);
//得到返回类型
returnType = strings[1];
//得到方法全路径
fullClassName = strings[2].substring(0, strings[2].lastIndexOf("."));
//得到方法名称
methodName = strings[2].substring(strings[2].lastIndexOf(".") + 1, strings[2].lastIndexOf("("));
//得到方法参数
String substring = s.substring(s.lastIndexOf(methodName) + methodName.length() + 1, s.lastIndexOf(")"));
methodArgs = substring.split(" *, *");
} catch (Exception e) {
throw new RuntimeException(value + "解析有误,请查看切面路径是否正确");
}
return new MethodInfo(modify, returnType, methodName, methodArgs, fullClassName);
}
判断是否为目标方法
我们通过MethodInfo对象和一个method,判断该method是否为目标方法
protected boolean isTargetMethod(int modify, String returnType, String name, Object[] paramsType, Method method) {
//判断方法名是否相等
if (!method.getName().equals(name)) return false;
//判断方法修饰符是否一样
if (method.getModifiers() != modify) return false;
//判断方法返回值是否相等
if (!method.getReturnType().getName().equals(returnType)) return false;
//获取该方法的所有参数
Class<?>[] parameterTypes = method.getParameterTypes();
//判断方法参数长度是否相等
if (parameterTypes.length != paramsType.length) return false;
//判断顺序和类型是否相同
for (int i = 0; i < paramsType.length; i++) {
if (!parameterTypes[i].getName().equals(paramsType[i])) return false;
}
return true;
}
初始化aspectClass容器
我们在initSingletonObjects方法中对beanDefinitionMap进行遍历的时候,我们也需要初始化aspectClass,如果是切面类就加入Map
//将切面类名字和order存入map中
if (bean.getClass().isAnnotationPresent(Aspect.class)) {
int order = Integer.MAX_VALUE;
if (bean.getClass().isAnnotationPresent(Order.class)) {
order = bean.getClass().getAnnotation(Order.class).value();
}
aspectClass.put(name, order);
}
对切面类进行排序
我们按照切面类的优先级来对切面类进行排序,将优先级高的放入前面,这个是在aspectClass的Map初始化完成后进行的
//将切面类按照order进行排序,存储进list
List<Map.Entry<String, Integer>> list = new ArrayList<>(aspectClass.entrySet());
list.sort((o1, o2) -> -o1.getValue().compareTo(o2.getValue()));
for (Map.Entry<String, Integer> t : list) {
aspectClassNames.add(t.getKey());
}
实现AOP
经过上面的准备,现在我们已经可以实现AOP机制了,AOP就是通过后置处理器来进行实现的,在后置处理器的postProcessAfterInitialization执行完成后我们就可以进行切面,也就是在我们写的processorAfterMethod中进行。
protected Object processorAfterMethod(Object o, String beanName) {
for (String postProcessorName : beanPostProcessorNames) {
BeanPostProcessor postProcessor = (BeanPostProcessor) singletonObjects.get(postProcessorName);
Object current = null;
try {
current = postProcessor.postProcessAfterInitialization(o, beanName);
} catch (Exception e) {
e.printStackTrace();
}
if (current != null) {
o = current;
}
}
//进行切面
//对该对象的所有方法进行遍历
String targetMethodFullName = o.getClass().getCanonicalName();
Method[] declaredMethods = o.getClass().getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
//对所有切面类进行遍历
for (String aspectClassName : aspectClassNames) {
//对切面类的所有方法进行遍历
for (Method method : singletonObjects.get(aspectClassName).getClass().getDeclaredMethods()) {
// System.out.println(o.getClass().getName());
//判断切面类方法是否有@before或者@After注解
if (method.isAnnotationPresent(Before.class)) {
//获取注解配置的value
String value = method.getAnnotation(Before.class).value();
//得到要进行切面的方法信息
MethodInfo methodInfo = getMethodInfo(value);
//判断现在的方法是否就是要进行切面
if (isTargetMethod(methodInfo.modify, methodInfo.returnType,
methodInfo.methodName, methodInfo.args, declaredMethod) &&
methodInfo.fullClassName.equals(targetMethodFullName)) {
//临时变量
Object proxyObject = o;
//返回的代理对象
//更新对象
o = Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method targetMethod, Object[] args) throws Throwable {
if (targetMethod.getName().equals(declaredMethod.getName())) {
//先执行我们定义的@Before的方法
method.invoke(singletonObjects.get(aspectClassName), new JoinPoint(targetMethod, args));
}
//执行目标方法,返回代理对象
return targetMethod.invoke(proxyObject, args);
}
});
}
} else if (method.isAnnotationPresent(After.class)) {
//获取注解配置的value
String value = method.getAnnotation(After.class).value();
//得到要进行切面的方法信息
MethodInfo methodInfo = getMethodInfo(value);
//判断现在的方法是否就是要进行切面
if (isTargetMethod(methodInfo.modify, methodInfo.returnType,
methodInfo.methodName, methodInfo.args, declaredMethod) &&
methodInfo.fullClassName.equals(targetMethodFullName)) {
//临时变量
Object proxyObject = o;
//返回的代理对象
//更新对象
o = Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method targetMethod, Object[] args) throws Throwable {
//执行目标方法,返回代理对象
Object result = targetMethod.invoke(proxyObject, args);
if (targetMethod.getName().equals(declaredMethod.getName())) {
//然后我们定义的@After的方法
method.invoke(singletonObjects.get(aspectClassName), new JoinPoint(targetMethod, args));
}
return result;
}
});
}
}
}
}
}
return o;
}
我这里比较暴力,就是直接对当前类的所有方法进行判断是否为切面方法,当然,原生spring肯定不是这样的,但是这里我们就不要陷入算法的泥潭,我们重点是理解spring的机制。
一个注意点,如果我们的类是切面类,那么就不会执行后置构造器,所有我们需要在执行后置处理器之前加入以下代码,其实以前也加了,只不过只考虑了后置处理器
//如果是自身就是后置处理器或者是一个切面类,跳过
if (o instanceof BeanPostProcessor || o.getClass().isAnnotationPresent(Aspect.class))
continue;
测试
创建一个utils包,里面定义一个CalUtils接口,然后再写一个实现类
public interface CalUtils {
public int add(int a, int b);
public int sub(int a, int b);
}
@Component("utils")
public class MyCalUtils implements CalUtils{
@Override
public int add(int a, int b) {
System.out.println(a + " + " + b + " = " + (a + b));
return a + b;
}
@Override
public int sub(int a, int b) {
System.out.println(a + " - " + b + " = " + (a - b));
return a - b;
}
}
创建一个aspect包,里面写3个切面类,用于测试,内容如下
@Aspect
@Component
@Order(300)
public class MyAspect {
@Before(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.add(int, int))")
public void first(JoinPoint joinPoint) {
System.out.println("切面方法---->before,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
}
@After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.add(int, int))")
public void second(JoinPoint joinPoint) {
System.out.println("切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
}
@After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.sub(int, int))")
public void thread(JoinPoint joinPoint) {
System.out.println("[MyAspect1]切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
}
}
@Aspect
@Component
@Order(100)
public class MyAspect2 {
@After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.sub(int, int))")
public void thread(JoinPoint joinPoint) {
System.out.println("[MyAspect2]切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
}
}
@Aspect
@Component
@Order(200)
public class MyAspect3 {
@After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.sub(int, int))")
public void thread(JoinPoint joinPoint) {
System.out.println("[MyAspect3]切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
}
}
在上面几个类中,我们分配了不同的优先级,并且对方法进行了切入。
由于在上一篇文章中我们将包扫描路径改了,所以我们要把路径改回来,并且由于我们创建了一些后置处理器,输出特别多,影响查看,我们直接删掉,所以现在的扫描路径和项目结构如下
@ComponentScan(path = "com.ttpfx.use")
public class ComponentScanPathConfig {
}
测试类中的代码如下
public class MySpringTest {
public static void main(String[] args) {
ApplicationContext ioc = new ApplicationContext(ComponentScanPathConfig.class);
CalUtils utils = ioc.getBean("utils", CalUtils.class);
utils.sub(1, 2);
System.out.println("-----------------------");
utils.add(1, 2);
}
}
控制台输出如下,说明代码没有问题,我们成功实现了AOP
总结
到这里,我们spring的核心机制,IOC/DI,AOP,都成功实现了,虽然代码不是特别完善,但是我们的基本功能都是没有问题的,其他就是一些细节性的问题,感兴趣可以自己扩展,最后给出我们自己写的spring的代码下载链接
项目github地址连接
手写spring系列
[手写spring](1)构建框架,实现包扫描
[手写spring](2)初始化BeanDefinitionMap
[手写spring](3)初始化singletonObjects,实现依赖注入
[手写spring](4)实现后置处理器
[手写spring](5)实现AOP机制(完结)