30 классов написанных от руки основных принципов АОП-кода Spring (5)

Java Spring

Эта статья взята из «Основных принципов Spring 5».

Ранее мы завершили функции трех основных модулей Spring IoC, DI и MVC и обеспечили их доступность. Следующим шагом будет завершение еще одного основного модуля Spring — АОП, что также является самой сложной частью.

1 Базовая конфигурация

Сначала добавьте следующую пользовательскую конфигурацию в application.properties в качестве базовой конфигурации Spring AOP:


#多切面配置可以在key前面加前缀
#例如 aspect.logAspect.

#切面表达式#
pointCut=public .* com.tom.spring.demo.service..*Service..*(.*)
#切面类#
aspectClass=com.tom.spring.demo.aspect.LogAspect
#切面前置通知#
aspectBefore=before
#切面后置通知#
aspectAfter=after
#切面异常通知#
aspectAfterThrow=afterThrowing
#切面异常类型#
aspectAfterThrowingName=java.lang.Exception

Для лучшего понимания давайте сравним нативную конфигурацию Spring AOP:


<bean id="xmlAspect" class="com.gupaoedu.aop.aspect.XmlAspect"></bean>

<!-- AOP配置 -->
<aop:config>

   <!-- 声明一个切面,并注入切面Bean,相当于@Aspect -->
   <aop:aspect ref="xmlAspect">
      <!-- 配置一个切入点,相当于@Pointcut -->
      <aop:pointcut expression="execution(* com.gupaoedu.aop.service..*(..))" id="simplePointcut"/>
      <!-- 配置通知,相当于@Before、@After、@AfterReturn、@Around、@AfterThrowing -->
      <aop:before pointcut-ref="simplePointcut" method="before"/>
      <aop:after pointcut-ref="simplePointcut" method="after"/>
      <aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/>
      <aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex"/>
   </aop:aspect>

</aop:config>

Для удобства мы используем файл свойств вместо XML, чтобы упростить работу.

2 Основной принцип АОП, версия V1.0

Основной принцип реализации АОП заключается в использовании динамического прокси-механизма для создания нового прокси-класса для завершения переплетения кода, чтобы достичь цели улучшения функций кода. Если вы мало что знаете о принципе динамического прокси, вы можете вернуться к специальной статье о режиме динамического прокси из серии «Вот как следует изучать шаблоны проектирования», которую я обновил некоторое время назад. Так как же Spring AOP работает с динамическими прокси? Фактически, основная функция Spring состоит в том, чтобы завершить развязку, отделить логику кода, которую нам нужно улучшить, и поместить ее в специальный класс, а затем связать эти отдельные логики, объявив файлы конфигурации, и, наконец, объединить их вместе для запуска. Чтобы сохранить это отношение в контейнере Spring, мы можем просто понять, что Spring использует карту для сохранения и сохранения этого отношения. Ключ Map — это целевой метод, который мы хотим вызвать, а значение Map — это метод, который мы хотим сплести. Просто методы плетения имеют последовательный порядок, поэтому нам нужно отметить позицию метода плетения. Логика, вплетенная перед целевым методом, называется предуведомлением, логика, вплетенная в целевом методе, называется постсоветом, а логика, которая должна быть вплетена, когда в целевом методе возникает исключение, называется уведомлением об исключении. Конкретный дизайн карты выглядит следующим образом:


private Map<Method,Map<String, Method>> methodAdvices = new HashMap<Method, Map<String, Method>>();

Ниже я пишу полный простой ApplicationContext, на который друзья могут ссылаться:


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;



public class GPApplicationContext {
    private Properties contextConfig = new Properties();
    private Map<String,Object> ioc = new HashMap<String,Object>();
    //用来保存配置文件中对应的Method和Advice的对应关系
    private Map<Method,Map<String, Method>> methodAdvices = new HashMap<Method, Map<String, Method>>();


    public GPApplicationContext(){
		
		   //为了演示,手动初始化一个Bean
			 
        ioc.put("memberService", new MemberService());

        doLoadConfig("application.properties");

        doInitAopConfig();

    }

    public Object getBean(String name){
        return createProxy(ioc.get(name));
    }


    private Object createProxy(Object instance){
        return new GPJdkDynamicAopProxy(instance).getProxy();
    }

    //加载配置文件
    private void doLoadConfig(String contextConfigLocation) {
        //直接从类路径下找到Spring主配置文件所在的路径
        //并且将其读取出来放到Properties对象中
        //相对于scanPackage=com.gupaoedu.demo 从文件中保存到了内存中
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null != is){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doInitAopConfig() {

        try {
            Class apectClass = Class.forName(contextConfig.getProperty("aspectClass"));
            Map<String,Method> aspectMethods = new HashMap<String,Method>();
            for (Method method : apectClass.getMethods()) {
                aspectMethods.put(method.getName(),method);
            }

            //PonintCut  表达式解析为正则表达式
            String pointCut = contextConfig.getProperty("pointCut")
                    .replaceAll("\\.","\\\\.")
                    .replaceAll("\\\\.\\*",".*")
                    .replaceAll("\\(","\\\\(")
                    .replaceAll("\\)","\\\\)");
            Pattern pointCutPattern = Pattern.compile(pointCut);

            for (Map.Entry<String,Object> entry : ioc.entrySet()) {
                Class<?> clazz = entry.getValue().getClass();
                //循环找到所有的方法
                for (Method method : clazz.getMethods()) {
                    //保存方法名
                    String methodString = method.toString();
                    if(methodString.contains("throws")){
                        methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim();
                    }
                    Matcher matcher = pointCutPattern.matcher(methodString);
                    if(matcher.matches()){
                        Map<String,Method> advices = new HashMap<String,Method>();
                        if(!(null == contextConfig.getProperty("aspectBefore") || "".equals( contextConfig.getProperty("aspectBefore")))){
                            advices.put("before",aspectMethods.get(contextConfig.getProperty("aspectBefore")));
                        }
                        if(!(null ==  contextConfig.getProperty("aspectAfter") || "".equals( contextConfig.getProperty("aspectAfter")))){
                            advices.put("after",aspectMethods.get(contextConfig.getProperty("aspectAfter")));
                        }
                        if(!(null == contextConfig.getProperty("aspectAfterThrow") || "".equals( contextConfig.getProperty("aspectAfterThrow")))){
                            advices.put("afterThrow",aspectMethods.get(contextConfig.getProperty("aspectAfterThrow")));
                        }
                        methodAdvices.put(method,advices);
                    }
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    class GPJdkDynamicAopProxy implements GPInvocationHandler {
        private Object instance;
        public GPJdkDynamicAopProxy(Object instance) {
            this.instance = instance;
        }

        public Object getProxy() {
            return Proxy.newProxyInstance(instance.getClass().getClassLoader(),instance.getClass().getInterfaces(),this);
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object aspectObject = Class.forName(contextConfig.getProperty("aspectClass")).newInstance();
            Map<String,Method> advices = methodAdvices.get(instance.getClass().getMethod(method.getName(),method.getParameterTypes()));
            Object returnValue = null;
            advices.get("before").invoke(aspectObject);
            try {
                returnValue = method.invoke(instance, args);
            }catch (Exception e){
                advices.get("afterThrow").invoke(aspectObject);
                e.printStackTrace();
                throw e;
            }
            advices.get("after").invoke(aspectObject);
            return returnValue;
        }
    }

}

Тестовый код:


public class MemberServiceTest {


    public static void main(String[] args) {
        GPApplicationContext applicationContext = new GPApplicationContext();
        IMemberService memberService = (IMemberService)applicationContext.getBean("memberService");

        try {

            memberService.get("1");
            memberService.save(new Member());

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

Мы можем полностью продемонстрировать основные принципы Spring AOP с помощью нескольких сотен строк кода, разве это не просто? Конечно, друзьям еще предстоит испытать это на себе, чтобы они были впечатлены. Далее мы продолжим улучшать и обновлять Spring AOP 1.0 до 2.0. Затем я полностью написал версию 2.0, имитируя оригинальный дизайн Spring. Я надеюсь принести вам другой опыт рукописного ввода, чтобы иметь более глубокое понимание принципов весеннего АОП. .

3 Завершите проектирование верхнего уровня АОП.

3.1 GPJoinPoint

Абстракция, определяющая pointcut, который является основным строительным блоком АОП. Мы можем понять, что это дополнительная информация для бизнес-метода. Вполне возможно, что pointcut должен включать сам бизнес-метод, фактический список параметров и экземпляр объекта, которому принадлежит метод.Вы также можете добавить пользовательские атрибуты в GPJoinPoint, см. следующий код:


package com.tom.spring.formework.aop.aspect;

import java.lang.reflect.Method;

/**
 * 回调连接点,通过它可以获得被代理的业务方法的所有信息
 */
public interface GPJoinPoint {

    Method getMethod(); //业务方法本身

    Object[] getArguments();  //该方法的实参列表

    Object getThis(); //该方法所属的实例对象

    //在JoinPoint中添加自定义属性
    void setUserAttribute(String key, Object value);
    //从已添加的自定义属性中获取一个属性值
    Object getUserAttribute(String key);

}

3.2 GPMethodInterceptor

Перехватчик метода является базовой единицей улучшения кода АОП, и его подклассы в основном включают GPMethodBeforeAdvice, GPAfterReturningAdvice и GPAfterThrowingAdvice.


package com.tom.spring.formework.aop.intercept;

/**
 * 方法拦截器顶层接口
 */ 
public interface GPMethodInterceptor{
    Object invoke(GPMethodInvocation mi) throws Throwable;
}

3.3 GPAopConfig

Объект инкапсуляции, определяющий информацию о конфигурации АОП для облегчения взаимной передачи в последующих кодах.


package com.tom.spring.formework.aop;

import lombok.Data;

/**
 * AOP配置封装
 */
@Data
public class GPAopConfig {
//以下配置与properties文件中的属性一一对应
    private String pointCut;  //切面表达式
    private String aspectBefore;  //前置通知方法名
    private String aspectAfter;  //后置通知方法名
    private String aspectClass;  //要织入的切面类
    private String aspectAfterThrow;  //异常通知方法名
    private String aspectAfterThrowingName;  //需要通知的异常类型
}

3.4 GPAdvisedSupport

GPAdvisedSupport в основном завершает анализ конфигурации AOP. Метод pointCutMatch() используется для оценки того, соответствует ли целевой класс правилам аспекта, чтобы решить, следует ли создавать прокси-класс для улучшения целевого метода. Метод getInterceptorsAndDynamicInterceptionAdvice() в основном инкапсулирует метод, который необходимо вызвать обратно, в цепочку перехватчиков в соответствии с конфигурацией AOP, и возвращает его для внешнего получения.


package com.tom.spring.formework.aop.support;

import com.tom.spring.formework.aop.GPAopConfig;
import com.tom.spring.formework.aop.aspect.GPAfterReturningAdvice;
import com.tom.spring.formework.aop.aspect.GPAfterThrowingAdvice;
import com.tom.spring.formework.aop.aspect.GPMethodBeforeAdvice;

import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 主要用来解析和封装AOP配置
 */
public class GPAdvisedSupport {
    private Class targetClass;
    private Object target;
    private Pattern pointCutClassPattern;

    private transient Map<Method, List<Object>> methodCache;

    private GPAopConfig config;

    public GPAdvisedSupport(GPAopConfig config){
        this.config = config;
    }

    public Class getTargetClass() {
        return targetClass;
    }

    public void setTargetClass(Class targetClass) {
        this.targetClass = targetClass;
        parse();
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) throws Exception {
        List<Object> cached = methodCache.get(method);

        //缓存未命中,则进行下一步处理
        if (cached == null) {
           Method m = targetClass.getMethod(method.getName(),method.getParameterTypes());
           cached = methodCache.get(m);
            //存入缓存
            this.methodCache.put(m, cached);
        }
        return cached;
    }

    public boolean pointCutMatch(){
        return pointCutClassPattern.matcher(this.targetClass.toString()).matches();
    }

    private void parse(){
        //pointCut表达式
        String pointCut = config.getPointCut()
                .replaceAll("\\.","\\\\.")
                .replaceAll("\\\\.\\*",".*")
                .replaceAll("\\(","\\\\(")
                .replaceAll("\\)","\\\\)");

        String pointCutForClass = pointCut.substring(0,pointCut.lastIndexOf("\\(") - 4);
        pointCutClassPattern = Pattern.compile("class " + pointCutForClass.substring (pointCutForClass.lastIndexOf(" ")+1));

        methodCache = new HashMap<Method, List<Object>>();
        Pattern pattern = Pattern.compile(pointCut);

        try {
            Class aspectClass = Class.forName(config.getAspectClass());
            Map<String,Method> aspectMethods = new HashMap<String,Method>();
            for (Method m : aspectClass.getMethods()){
                aspectMethods.put(m.getName(),m);
            }

            //在这里得到的方法都是原生方法
            for (Method m : 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()){
                    //能满足切面规则的类,添加到AOP配置中
                    List<Object> advices = new LinkedList<Object>();
                    //前置通知
                    if(!(null == config.getAspectBefore() || "".equals(config.getAspectBefore().trim()))) {
                        advices.add(new GPMethodBeforeAdvice(aspectMethods.get (config.getAspectBefore()), aspectClass.newInstance()));
                    }
                    //后置通知
                    if(!(null == config.getAspectAfter() || "".equals(config.getAspectAfter(). trim()))) {
                        advices.add(new GPAfterReturningAdvice(aspectMethods.get (config.getAspectAfter()), aspectClass.newInstance()));
                    }
                    //异常通知
                    if(!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow().trim()))) {
                        GPAfterThrowingAdvice afterThrowingAdvice = new GPAfterThrowingAdvice (aspectMethods.get(config.getAspectAfterThrow()), aspectClass.newInstance());
                        afterThrowingAdvice.setThrowingName(config.getAspectAfterThrowingName());
                        advices.add(afterThrowingAdvice);
                    }
                    methodCache.put(m,advices);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

3.5 GPAopProxy

GPAopProxy — это интерфейс верхнего уровня фабрики прокси.Есть два основных подкласса: GPCglibAopProxy и GPJdkDynamicAopProxy, которые реализуют прокси-сервер CGlib и прокси-сервер JDK соответственно.


package com.tom.spring.formework.aop;
/**
 * 代理工厂的顶层接口,提供获取代理对象的顶层入口 
 */
//默认就用JDK动态代理
public interface GPAopProxy {
//获得一个代理对象
    Object getProxy();
//通过自定义类加载器获得一个代理对象
    Object getProxy(ClassLoader classLoader);
}

3.6 GPCglibAopProxy

Эта статья не реализует CglibAopProxy, и заинтересованные "партнеры" могут попробовать его самостоятельно.


package com.tom.spring.formework.aop;

import com.tom.spring.formework.aop.support.GPAdvisedSupport;

/**
 * 使用CGlib API生成代理类,在此不举例
 * 感兴趣的“小伙伴”可以自行实现
 */
public class GPCglibAopProxy implements GPAopProxy {
    private GPAdvisedSupport config;

    public GPCglibAopProxy(GPAdvisedSupport config){
        this.config = config;
    }

    @Override
    public Object getProxy() {
        return null;
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return null;
    }
}

3.7 GPJdkDynamicAopProxy

Давайте посмотрим на реализацию GPJdkDynamicAopProxy, основная функция находится в методе invoke(). Что касается количества кода, то его не так много, в основном это вызов метода getInterceptorsAndDynamicInterceptionAdvice() GPAdvisedSupport для получения цепочки перехватчиков. В целевом классе каждый расширенный целевой метод соответствует цепочке перехватчиков.


package com.tom.spring.formework.aop;

import com.tom.spring.formework.aop.intercept.GPMethodInvocation;
import com.tom.spring.formework.aop.support.GPAdvisedSupport;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

/**
 * 使用JDK Proxy API生成代理类
 */
public class GPJdkDynamicAopProxy implements GPAopProxy,InvocationHandler {
    private GPAdvisedSupport config;

    public GPJdkDynamicAopProxy(GPAdvisedSupport config){
        this.config = config;
    }

    //把原生的对象传进来
    public Object getProxy(){
        return getProxy(this.config.getTargetClass().getClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return Proxy.newProxyInstance(classLoader,this.config.getTargetClass().getInterfaces(),this);
    }

    //invoke()方法是执行代理的关键入口
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//将每一个JoinPoint也就是被代理的业务方法(Method)封装成一个拦截器,组合成一个拦截器链
        List<Object> interceptorsAndDynamicMethodMatchers = config.getInterceptorsAndDynamicInterceptionAdvice(method,this.config.getTargetClass());
//交给拦截器链MethodInvocation的proceed()方法执行
        GPMethodInvocation invocation = new GPMethodInvocation(proxy,this.config.getTarget(), method,args,this.config.getTargetClass(),interceptorsAndDynamicMethodMatchers);
        return invocation.proceed();
    }
}

Как видно из кода, цепочка перехватчиков, полученная от GPAdvisedSupport, передается в конструктор GPMethodInvocation в качестве параметра. Итак, что именно делается с цепочкой методов в GPMethodInvocation?

3.8 GPMethodInvocation

Код для GPMethodInvocation выглядит следующим образом:


package com.tom.spring.formework.aop.intercept;

import com.tom.spring.formework.aop.aspect.GPJoinPoint;

import java.lang.reflect.Method;
import java.util.List;

/**
 * 执行拦截器链,相当于Spring中ReflectiveMethodInvocation的功能
 */
public class GPMethodInvocation implements GPJoinPoint {

    private Object proxy; //代理对象
    private Method method; //代理的目标方法
    private Object target; //代理的目标对象
    private Class<?> targetClass; //代理的目标类
    private Object[] arguments; //代理的方法的实参列表
    private List<Object> interceptorsAndDynamicMethodMatchers; //回调方法链

//保存自定义属性
private Map<String, Object> userAttributes;


    private int currentInterceptorIndex = -1;

    public GPMethodInvocation(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;
    }

    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 GPMethodInterceptor) {
            GPMethodInterceptor mi = (GPMethodInterceptor) interceptorOrInterceptionAdvice;
            return mi.invoke(this);
        } else {
//执行当前Intercetpor

            return proceed();
        }

    }

    @Override
    public Method getMethod() {
        return this.method;
    }

    @Override
    public Object[] getArguments() {
        return this.arguments;
    }

    @Override
    public Object getThis() {
        return this.target;
    }

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);
  }

}

Как видно из кода, метод continue() является ключевым для MethodInvocation. В методе continue() решение выносится первым.Если цепочка перехватчиков пуста, это означает, что целевой метод не нуждается в улучшении, и целевой метод вызывается и возвращается напрямую. Если цепочка перехватчиков не пуста, методы в цепочке перехватчиков выполняются по порядку, пока не будут выполнены все методы в цепочке перехватчиков.

4 Разработка базовой реализации АОП

4.1 GPAdvice

GPAdvice разработан как интерфейс верхнего уровня для всех уведомлений об обратном вызове, в версии Mini, чтобы максимально соответствовать нативному Spring, он разработан только как спецификация и не реализует никаких функций.


/**
 * 回调通知顶层接口
 */
public interface GPAdvice {

}

4.2 GPAbstractAspectJAdvice

Используйте шаблон шаблона для разработки класса GPAbstractAspectJAdvice, инкапсулируйте общую логику обратного вызова перехватчика, в основном инкапсулируйте метод динамического вызова отражения, а его подклассы должны управлять только последовательностью вызова.


package com.tom.spring.formework.aop.aspect;

import java.lang.reflect.Method;

/**
 * 封装拦截器回调的通用逻辑,在Mini版本中主要封装了反射动态调用方法
 */
public abstract class GPAbstractAspectJAdvice implements GPAdvice {

    private Method aspectMethod;
    private Object aspectTarget;

    public GPAbstractAspectJAdvice(
            Method aspectMethod, Object aspectTarget) {
            this.aspectMethod = aspectMethod;
            this.aspectTarget = aspectTarget;
    }

    //反射动态调用方法
    protected Object invokeAdviceMethod(GPJoinPoint joinPoint,Object returnValue,Throwable ex)
            throws Throwable {
        Class<?> [] paramsTypes = this.aspectMethod.getParameterTypes();
        if(null == paramsTypes || paramsTypes.length == 0) {
            return this.aspectMethod.invoke(aspectTarget);
        }else {
            Object[] args = new Object[paramsTypes.length];
            for (int i = 0; i < paramsTypes.length; i++) {
                if(paramsTypes[i] == GPJoinPoint.class){
                    args[i] = joinPoint;
                }else if(paramsTypes[i] == Throwable.class){
                    args[i] = ex;
                }else if(paramsTypes[i] == Object.class){
                    args[i] = returnValue;
                }
            }
            return this.aspectMethod.invoke(aspectTarget,args);
        }
    }
}

4.3 GPMethodBeforeAdvice

GPMethodBeforeAdvice наследует GPAbstractAspectJAdvice, реализует интерфейсы GPAdvice и GPMethodInterceptor и управляет последовательностью вызова предварительных рекомендаций в invoke().


package com.tom.spring.formework.aop.aspect;

import com.tom.spring.formework.aop.intercept.GPMethodInterceptor;
import com.tom.spring.formework.aop.intercept.GPMethodInvocation;

import java.lang.reflect.Method;

/**
 * 前置通知具体实现
 */
public class GPMethodBeforeAdvice extends GPAbstractAspectJAdvice implements GPAdvice, GPMethodInterceptor {

    private GPJoinPoint joinPoint;

    public GPMethodBeforeAdvice(Method aspectMethod, Object target) {
        super(aspectMethod, target);
    }

    public void before(Method method, Object[] args, Object target) throws Throwable {
        invokeAdviceMethod(this.joinPoint,null,null);
    }

    public Object invoke(GPMethodInvocation mi) throws Throwable {
        this.joinPoint = mi;
        this.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        return mi.proceed();
    }
}

4.4 GPAfterReturningAdvice

GPAfterReturningAdvice наследует GPAbstractAspectJAdvice, реализует интерфейсы GPAdvice и GPMethodInterceptor и управляет последовательностью вызова post-advice в invoke().


package com.tom.spring.formework.aop.aspect;

import com.tom.spring.formework.aop.intercept.GPMethodInterceptor;
import com.tom.spring.formework.aop.intercept.GPMethodInvocation;

import java.lang.reflect.Method;

/**
 * 后置通知具体实现
 */
public class GPAfterReturningAdvice extends GPAbstractAspectJAdvice implements GPAdvice, GPMethodInterceptor {

    private GPJoinPoint joinPoint;
    public GPAfterReturningAdvice(Method aspectMethod, Object target) {
        super(aspectMethod, target);
    }

    @Override
    public Object invoke(GPMethodInvocation mi) throws Throwable {
        Object retVal = mi.proceed();
        this.joinPoint = mi;
        this.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
        return retVal;
    }

    public void afterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable{
        invokeAdviceMethod(joinPoint,returnValue,null);
    }

}

4.5 GPAfterThrowingAdvice

GPAfterThrowingAdvice наследует GPAbstractAspectJAdvice, реализует интерфейсы GPAdvice и GPMethodInterceptor и управляет последовательностью вызова уведомлений об исключениях в invoke().


package com.tom.spring.formework.aop.aspect;

import com.tom.spring.formework.aop.intercept.GPMethodInterceptor;
import com.tom.spring.formework.aop.intercept.GPMethodInvocation;

import java.lang.reflect.Method;

/**
 * 异常通知具体实现
 */
public class GPAfterThrowingAdvice extends GPAbstractAspectJAdvice implements GPAdvice, GPMethodInterceptor {

    private String throwingName;
    private GPMethodInvocation mi;

    public GPAfterThrowingAdvice(Method aspectMethod, Object target) {
        super(aspectMethod, target);
    }

    public void setThrowingName(String name) {
        this.throwingName = name;
    }

    @Override
    public Object invoke(GPMethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        }catch (Throwable ex) {
            invokeAdviceMethod(mi,null,ex.getCause());
            throw ex;
        }
    }
}

Заинтересованные «партнеры» могут обратиться к исходному коду Spring и самостоятельно реализовать логику вызова вокруг уведомления.

4.6 Доступ к методу getBean()

В приведенном выше коде мы выполнили основные функции модуля Spring AOP, так как же интегрировать его в контейнер IoC? Найдите метод getBean() GPApplicationContext, мы знаем, что метод, отвечающий за инициализацию Bean в getBean(), на самом деле является instanceiateBean(), и вы можете определить, следует ли возвращать родной Bean или Proxy Bean во время инициализации. Код реализован следующим образом:


//传一个BeanDefinition,返回一个实例Bean
private Object instantiateBean(GPBeanDefinition beanDefinition){
    Object instance = null;
    String className = beanDefinition.getBeanClassName();
    try{

        //因为根据Class才能确定一个类是否有实例
        if(this.singletonBeanCacheMap.containsKey(className)){
            instance = this.singletonBeanCacheMap.get(className);
        }else{
            Class<?> clazz = Class.forName(className);
            instance = clazz.newInstance();

            GPAdvisedSupport config = instantionAopConfig(beanDefinition);
            config.setTargetClass(clazz);
            config.setTarget(instance);

            if(config.pointCutMatch()) {
                instance = createProxy(config).getProxy();
            }
		  this.factoryBeanObjectCache.put(className,instance);
            this.singletonBeanCacheMap.put(beanDefinition.getFactoryBeanName(),instance);
        }

        return instance;
    }catch (Exception e){
        e.printStackTrace();
    }

    return null;
}

private GPAdvisedSupport instantionAopConfig(GPBeanDefinition beanDefinition) throws  Exception{

    GPAopConfig config = new GPAopConfig();
    config.setPointCut(reader.getConfig().getProperty("pointCut"));
    config.setAspectClass(reader.getConfig().getProperty("aspectClass"));
    config.setAspectBefore(reader.getConfig().getProperty("aspectBefore"));
    config.setAspectAfter(reader.getConfig().getProperty("aspectAfter"));
    config.setAspectAfterThrow(reader.getConfig().getProperty("aspectAfterThrow"));
    config.setAspectAfterThrowingName(reader.getConfig().getProperty("aspectAfterThrowingName"));

    return new GPAdvisedSupport(config);
}

private GPAopProxy createProxy(GPAdvisedSupport config) {
    Class targetClass = config.getTargetClass();
    if (targetClass.getInterfaces().length > 0) {
        return new GPJdkDynamicAopProxy(config);
    }
    return new GPCglibAopProxy(config);
}

Как видно из приведенного выше кода, вызовите createProxy() в методе instanceiateBean(), чтобы определить стратегию вызова прокси-фабрики, а затем вызовите метод proxy() прокси-фабрики, чтобы создать прокси-объект. Конечный прокси-объект будет инкапсулирован в BeanWrapper и сохранен в контейнере IoC.

5 Кодекс ткачества

Благодаря предыдущему написанию кода все основные модули и базовая логика были реализованы.«Все готово, осталось только восточный ветер».Настало время «засвидетельствовать чудо». Давайте добавим бизнес-код и проведем тест. Создайте класс LogAspect для мониторинга бизнес-методов. В основном он записывает журнал вызовов целевого метода и получает имя целевого метода, список фактических параметров и время, затраченное на каждый вызов.

5.1 LogAspect

Код для LogAspect выглядит следующим образом:


package com.tom.spring.demo.aspect;

import com.tom.spring.formework.aop.aspect.GPJoinPoint;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;

/**
 * 定义一个织入的切面逻辑,也就是要针对目标代理对象增强的逻辑
 * 本类主要完成对方法调用的监控,监听目标方法每次执行所消耗的时间
 */
@Slf4j
public class LogAspect {

    //在调用一个方法之前,执行before()方法
    public void before(GPJoinPoint joinPoint){
        joinPoint.setUserAttribute("startTime_" + joinPoint.getMethod().getName(),System.currentTimeMillis());
        //这个方法中的逻辑是由我们自己写的
        log.info("Invoker Before Method!!!" +
                "\nTargetObject:" +  joinPoint.getThis() +
                "\nArgs:" + Arrays.toString(joinPoint.getArguments()));
    }

    //在调用一个方法之后,执行after()方法
    public void after(GPJoinPoint 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(GPJoinPoint joinPoint, Throwable ex){
        log.info("出现异常" +
                "\nTargetObject:" +  joinPoint.getThis() +
                "\nArgs:" + Arrays.toString(joinPoint.getArguments()) +
                "\nThrows:" + ex.getMessage());
    }
}

Из приведенного выше кода видно, что каждый метод обратного вызова добавляет параметр GPJoinPoint, помните, что такое GPJoinPoint? По сути, GPMethodInvocation — это класс реализации GPJoinPoint. GPMethodInvocation создается в методе invoke() класса GPJdkDynamicAopPorxy, то есть бизнес-метод каждого прокси-объекта соответствует экземпляру GPMethodInvocation. Другими словами, жизненный цикл MethodInvocation соответствует жизненному циклу бизнес-метода в прокси-объекте. Как мы видели ранее, вызов метода setUserAttribute() объекта GPJoinPoint может настроить атрибуты в GPJoinPoint, а вызов метода getUserAttribute() может получить значение пользовательского атрибута. В методе before() LogAspect startTime устанавливается в GPJoinPoint и назначается системным временем, то есть контекстом времени вызова метода записи для MethodInvocation. Начальное время получается в методе after() LogAspect, а системное время, полученное снова, сохраняется в endTime. В обратном вызове цепочки перехватчиков АОП метод before() должен вызываться перед методом after(), поэтому системное время, полученное дважды, образует разницу во времени, то есть время, затраченное на выполнение бизнес-метода. По этой разнице во времени можно судить о потреблении производительности бизнес-методом в единицу времени. На самом деле, почти все фреймворки системного мониторинга на рынке реализованы на основе такой идеи, которая может быть сильно отделена и уменьшить вмешательство в код.

5.2 IModifyService

Чтобы продемонстрировать уведомление обратного вызова исключения, мы добавляем функцию выбрасывания исключений в метод add() интерфейса IModifyService, определенный ранее, см. следующую реализацию кода:


package com.tom.spring.demo.service;

/**
 * 增、删、改业务
  */
public interface IModifyService {

   /**
    * 增加
    */
   String add(String name, String addr) throws Exception;
   
   /**
    * 修改
    */
   String edit(Integer id, String name);
   
   /**
    * 删除
    */
   String remove(Integer id);
   
}

5.3 ModifyService

Код для ModifyService выглядит следующим образом:


package com.tom.spring.demo.service.impl;

import com.tom.spring.demo.service.IModifyService;
import com.tom.spring.formework.annotation.GPService;

/**
 * 增、删、改业务
 */
@GPService
public class ModifyService implements IModifyService {

   /**
    * 增加
    */
   public String add(String name,String addr) throws Exception {
      throw new Exception("故意抛出异常,测试切面通知是否生效");
//    return "modifyService add,name=" + name + ",addr=" + addr;
   }

   /**
    * 修改
    */
   public String edit(Integer id,String name) {
      return "modifyService edit,id=" + id + ",name=" + name;
   }

   /**
    * 删除
    */
   public String remove(Integer id) {
      return "modifyService id=" + id;
   }
}

6 демонстрация эффекта бега

Введите в браузереhttp://localhost/web/add.json?name=Tom&addr=HunanChangsha, вы можете интуитивно и четко увидеть информацию об исключении, создаваемом уровнем службы, как показано на следующем рисунке.

file

Вывод консоли показан ниже.

file

Через вывод консоли вы можете видеть, что уведомление об исключении успешно захватывает информацию об исключении, запускает GPMethodBeforeAdvice и GPAfterThrowingAdvice, но не запускает GPAfterReturningAdvice, что соответствует нашим ожиданиям. Затем выполните еще один тест, введитеhttp://localhost/web/query.json?name=Tom, результат показан на следующем рисунке:

file

Вывод консоли показан ниже:

file

Как видно из вывода консоли, предварительное и постуведомление захватываются соответственно, и соответствующая информация печатается, что соответствует нашим ожиданиям.

На данный момент модуль АОП завершен, есть ли небольшое чувство достижения, не терпится попробовать? При реализации всей Мини-версии некоторые детали не были особо учтены, и предполагается, что большее их количество даст «друзьям» представление об изучении исходного кода. Рукописный исходник не для того, чтобы повторять колесо, и не для того, чтобы изображать из себя «высокого», на самом деле это просто метод обучения, который мы рекомендуем всем.

Эта статья является оригиналом "Архитектуры бомбы Тома", пожалуйста, указывайте источник при перепечатке. Технология заключается в обмене, я разделяю свое счастье! Если у вас есть какие-либо предложения, вы также можете оставить комментарий или личное сообщение, Ваша поддержка является движущей силой для меня, чтобы упорствовать в создании. Обратите внимание на «архитектуру бомбы Тома», чтобы получить больше технической галантереи!

Нелегко быть оригинальным, и круто настаивать. Вы все это видели здесь. Не забудьте поставить лайк, добавить в закладки, посмотреть и подписаться одним щелчком мыши! Если вы считаете, что контент слишком сух, вы можете поделиться им и отправить своим друзьям, чтобы они питались и питались!