目录
我们实现和原生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();
- }
我们使用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);
- }
这个类用来表示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;
- }
我们在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就是通过后置处理器来进行实现的,在后置处理器的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的代码下载链接
手写spring系列
[手写spring](2)初始化BeanDefinitionMap