Как реализован АОП и его принцип

задняя часть Spring исходный код Kotlin
  • суг команда
  • Автор: Леди MP
  • Группа связи QQ: 855833773
  • Добро пожаловать в нашу команду, контакт WeChat: foreverpx_cjl

Обзор:

Недавно я столкнулся с примером, который можно реализовать с помощью АОП при разработке, кстати, я изучил принцип реализации АОП и обобщил то, что узнал. В статье используется язык программирования kotlin, который можно напрямую преобразовать в java в IDEA. Эта статья будет расширена в соответствии со следующими категориями:

  • Введение в АОП
  • Пример реализации в коде
  • Принцип реализации АОП
  • Частичный анализ исходного кода

1. Введение в АОП

Я считаю, что все более-менее разбираются в АОП, и все знают, что это аспектно-ориентированное программирование, можно найти массу объяснений, поискав в Интернете. Здесь я резюмирую одним предложением: АОП позволяет нам предоставлять программное обеспечение для программного обеспечения, не затрагивая исходные функции.МасштабированиеФункции. Так как вы понимаете горизонтальное расширение?При разработке WEB проектов мы обычно соблюдаем трехуровневый принцип, в том числе слой управления (Контроллер) -> бизнес-уровень (Сервис) -> уровень данных (дао), то из этого структура вертикальна, ее особый слой — это то, что мы называем горизонтальным. Наш АОП — это все методы, которые могут воздействовать на этот один горизонтальный модуль.

Давайте посмотрим на разницу между АОП и ООП: АОП является дополнением к ООП.Когда нам нужно ввести общее поведение для нескольких объектов, таких как журналы, записи операций и т. д., нам нужно обратиться к общему поведению в каждый объект, так что программа может генерировать много повторяющегося кода, и использование АОП прекрасно решает эту проблему.

Далее я представлю точки знаний, которые необходимо понимать при обращении к АОП:

  • Аспекты: классы перехватчиков, которые определяют точки и рекомендации.
  • Pointcut: конкретная бизнес-точка, которую необходимо перехватить.
  • Уведомление: метод в аспекте, который объявляет место выполнения метода уведомления на целевом бизнес-уровне.Типы уведомлений следующие:
    1. Предварительное уведомление: @Before выполняется до выполнения целевого бизнес-метода.
    2. Опубликовать уведомление: @After выполняется после выполнения целевого бизнес-метода.
    3. Уведомление о возврате: @AfterReturning выполняется после того, как целевой бизнес-метод возвращает результат.
    4. Уведомление об исключении: @AfterThrowing после того, как целевой бизнес-метод выдает исключение
    5. Окружное уведомление: @Around является мощным средством и может заменить четыре вышеупомянутых уведомления, а также может контролировать, выполняется ли и когда выполняется целевой бизнес-метод.

2. Пример реализации в коде

Вышеприведенное примерно представило базовые знания, которые необходимо понимать в АОП, а также знает о преимуществах АОП, так как же реализовать его в коде? Приведу пример: теперь у нас есть система управления школой, в которой реализованы добавления, исключения и изменения учителей и учеников.Появилось новое требование, которое заключается в том, чтобы делать запись о каждом добавлении, исключении и изменении учителей. и студенты.В это время директор может просмотреть список записей. Так что вопрос в том, как бороться с этим является лучшим решением? Здесь я перечислил три решения, давайте рассмотрим их преимущества и недостатки.

- Самый простой - это первый метод. Мы напрямую реализуем этот метод записи в каждой функции добавления, удаления и модификации, так что повторение кода слишком велико, а связь слишком сильна, поэтому его не рекомендуется использовать.

-Второе то, что мы используем дольше всего.Извлекается метод записи,и другие добавления,удаления и изменения могут вызывать эту функцию записи.Очевидно,что повторение кода уменьшается,но такой вызов все равно не уменьшает связность .

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

Давайте посмотрим на конкретную реализацию кода:

  1. Сначала я определяю пользовательскую аннотацию как pointcut.
@Target(AnnotationTarget.FUNCTION)  //注解作用的范围,这里声明为函数
@Order(Ordered.HIGHEST_PRECEDENCE)  //声明注解的优先级为最高,假设有多个注解,先执行这个
annotation class Hanler(val handler: HandlerType)  //自定义注解类,HandlerType是一个枚举类型,里面定义的就是学生和老师的增删改操作,在这里就不展示具体内容了
  1. Следующим шагом является определение класса аспекта.
@Aspect   //该注解声明这个类为一个切面类
@Component
class HandlerAspect{

 @Autowired
 private lateinit var handlerService: HandlerService

@AfterReturning("@annotation(handler)")   //当有函数注释了注解,将会在函数正常返回后在执行我们定义的方法
fun hanler(hanler: Hanler) {
    handlerService.add(handler.operate.value)   //这里是真正执行记录的方法
}
}
  1. В конце концов, это наш оригинальный бизнес-подход.
/**
* 删除学生方法
*/
@Handler(operate= Handler.STUDENT_DELETE)   //当执行到删除学生方法时,切面类就会起作用了,当学生正常删除后就会执行记录方法,我们就可以看到记录方法生成的数据
fun delete(id:String) {
   studentService.delete(id)
}

3. Принцип реализации АОП

Теперь мы понимаем, как это реализовано в коде, так каков же принцип реализации АОП? Я уже читал блог и говорил, что когда дело доходит до АОП, все знают, что его принцип реализации — динамический прокси, Очевидно, я не знал этого раньше, ха-ха, но я верю, что вы, читавшие статью, должны это знать.

Когда дело доходит до динамического прокси, мы должны говорить о режиме прокси. Определение режима прокси: предоставление прокси для объекта, а прокси-объект управляет ссылкой на исходный объект. Режим прокси включает следующие роли: subject: роль абстрактного субъекта, представляющая собой интерфейс. Интерфейс — это интерфейс, совместно используемый объектом и его прокси; RealSubject: роль реального субъекта, представляющая собой класс, реализующий абстрактный интерфейс субъекта; Proxy: роль прокси, которая содержит ссылку на реальный объект RealSubject, так что реальным объектом можно манипулировать. Прокси-объект предоставляет тот же интерфейс, что и реальный объект, вместо реального объекта. В то же время прокси-объект может добавлять другие операции при выполнении операций над реальным объектом, что эквивалентно инкапсуляции реального объекта. Как показано ниже:

Далее прокси делится на статический прокси и динамический прокси.Здесь написаны две небольшие демки.Динамический прокси использует прокси JDK. Например, учащиеся в классе теперь должны сдать домашнюю работу, и теперь наблюдатель выступает в роли агента по передаче домашнего задания, затем наблюдатель является агентом, а учащиеся — делегированными объектами.

3.1 Статический прокси

Во-первых, мы создаем интерфейс Person. Этот интерфейс является общедоступным интерфейсом студентов (класс агентов) и мониторов (класс агентов), все они ведут себя как сдача домашнего задания. Таким образом, учащиеся могут сдать свою домашнюю работу и позволить наблюдателю выполнить ее от их имени.

/**
 * Created by Mapei on 2018/11/7
 * 创建person接口
 */
public interface Person {
    //交作业
    void giveTask();
}

Класс Student реализует интерфейс Person, а Student может реализовать поведение передачи домашнего задания.

/**
 * Created by Mapei on 2018/11/7
 */
public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }

    public void giveTask() {
        System.out.println(name + "交语文作业");
    }
}

StudentProxy, этот класс также реализует интерфейс Person, но также содержит объект класса ученика, поэтому он может проксировать объект класса ученика для выполнения поведения при сдаче домашнего задания.

/**
 * Created by Mapei on 2018/11/7
 * 学生代理类,也实现了Person接口,保存一个学生实体,这样就可以代理学生产生行为
 */
public class StudentsProxy implements Person{
    //被代理的学生
    Student stu;

    public StudentsProxy(Person stu) {
        // 只代理学生对象
        if(stu.getClass() == Student.class) {
            this.stu = (Student)stu;
        }
    }

    //代理交作业,调用被代理学生的交作业的行为
    public void giveTask() {
        stu.giveTask();
    }
}

Давайте протестируем его, чтобы увидеть, как работает режим прокси:

/**
 * Created by Mapei on 2018/11/7
 */
public class StaticProxyTest {
    public static void main(String[] args) {
        //被代理的学生林浅,他的作业上交有代理对象monitor完成
        Person linqian = new Student("林浅");

        //生成代理对象,并将林浅传给代理对象
        Person monitor = new StudentsProxy(linqian);

        //班长代理交作业
        monitor.giveTask();
    }
}

результат операции:

Здесь Линь Цянь (прокси-объект) не выполняет задание напрямую, а монитор (прокси-объект) действует как прокси. Это режим прокси. Режим прокси заключается в том, чтобы ввести определенную степень косвенности при доступе к фактическому объекту. Косвенность здесь означает, что метод фактического объекта не вызывается напрямую, поэтому мы можем добавить некоторые другие цели в процесс прокси. Например, когда наблюдатель помогает Линь Цяню с домашним заданием, он хочет сказать учителю, что Линь Цянь в последнее время добился больших успехов, поэтому он может легко сделать это через агентскую модель. Метод можно добавить перед передачей прокси-класса. Это преимущество можно использовать в АОП весной.Мы можем выполнять некоторые операции до pointcut и выполнять некоторые операции после pointcut.Этот pointcut является методом. Класс, в котором находятся эти методы, должен быть проксирован, а некоторые другие операции вырезаны в прокси-процессе.

3.2 Динамический прокси

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

Сначала определите интерфейс Person:

/**
 * Created by Mapei on 2018/11/7
 * 创建person接口
 */
public interface Person {
    //交作业
    void giveTask();
}

Далее необходимо создать фактический класс, который необходимо проксировать, студенческий класс:

/**
 * Created by Mapei on 2018/11/7
 */
public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }

    public void giveTask() {
        System.out.println(name + "交语文作业");
    }
}

Создайте класс StuInvocationHandler, реализуйте интерфейс InvocationHandler и сохраните целевой экземпляр прокси-объекта в этом классе. В InvocationHandler есть метод вызова, и все методы, выполняющие прокси-объект, будут заменены выполнением метода вызова.

/**
 * Created by Mapei on 2018/11/7
 */
public class StuInvocationHandler<T> implements InvocationHandler {
    //invocationHandler持有的被代理对象
    T target;

    public StuInvocationHandler(T target) {
        this.target = target;
    }

    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理执行" +method.getName() + "方法");
        Object result = method.invoke(target, args);
        return result;
    }
}

Затем мы можем специально создать прокси-объект.

/**
 * Created by Mapei on 2018/11/7
 * 代理类
 */
public class ProxyTest {
    public static void main(String[] args) {

        //创建一个实例对象,这个对象是被代理的对象
        Person linqian = new Student("林浅");

        //创建一个与代理对象相关联的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler<Person>(linqian);

        //创建一个代理对象stuProxy来代理linqian,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

        //代理执行交作业的方法
        stuProxy.giveTask();
    }
}

Выполняем тестовый класс прокси.Сначала создаем студента Линь Цяня, которого нужно проксировать, и передаем Линь Цяня в stuHandler.При создании прокси-объекта stuProxy мы используем stuHandler в качестве параметра, затем все методы выполнение прокси-объекта будет заменено Метод вызова выполнен успешно, то есть метод вызова в StuInvocationHandler выполняется последним. Поэтому естественно видеть следующие результаты.

Итак, возникает вопрос, почему методы, выполняемые прокси-объектом, будут выполняться через метод вызова в InvocationHandler, С этой проблемой нам нужно посмотреть исходный код динамического прокси и сделать его простой анализ.

Выше мы использовали метод newProxyInstance класса Proxy для создания динамического прокси-объекта, взгляните на его исходный код:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
  }

Затем нам нужно сосредоточиться на коде Class> cl = getProxyClass0(loader, intfs), где генерируется класс прокси, и этот класс является ключом к динамическому прокси.Поскольку это динамически сгенерированный файл класса, мы напечатает этот файл класса в файл.

        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());
        String path = "/Users/mapei/Desktop/okay/65707.class";

        try{
            FileOutputStream fos = new FileOutputStream(path);
            fos.write(classFile);
            fos.flush();
            System.out.println("代理类class文件写入成功");
        }catch (Exception e) {
            System.out.println("写文件错误");
        }

Декомпилируем этот файл класса, давайте посмотрим, какой контент генерирует для нас jdk:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;

public final class $Proxy0 extends Proxy implements Person
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
  *为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个
  *被代理对象的实例,就可以去调用真正的对象实例。
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  //这个静态块本来是在最后的,我把它拿到前面来,方便描述
   static
  {
    try
    {
      //看看这儿静态块儿里面的住giveTask通过反射得到的名字m3,其他的先不管
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.Person").getMethod("giveTask", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
 
  /**
  * 
  *这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
  *this.h.invoke(this, m3, null);我们可以对将InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
  */
  public final void giveTask()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

}

Прочитав исходный код динамического прокси, давайте взглянем на исходный код реализации АОП в Spring.

4. Частичный анализ исходного кода

Анализ исходного кода агента создания aop

  1. Взгляните на то, как bean-компонент упакован в качестве прокси.
       	protected Object createProxy(
   		Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
   		
   	if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
   		AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
   	}

       // 1.创建proxyFactory,proxy的生产主要就是在proxyFactory做的
   	ProxyFactory proxyFactory = new ProxyFactory();
   	proxyFactory.copyFrom(this);

   	if (!proxyFactory.isProxyTargetClass()) {
   		if (shouldProxyTargetClass(beanClass, beanName)) {
   			proxyFactory.setProxyTargetClass(true);
   		}
   		else {
   			evaluateProxyInterfaces(beanClass, proxyFactory);
   		}
   	}

       // 2.将当前bean适合的advice,重新封装下,封装为Advisor类,然后添加到ProxyFactory中
   	Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
   	for (Advisor advisor : advisors) {
   		proxyFactory.addAdvisor(advisor);
   	}

   	proxyFactory.setTargetSource(targetSource);
   	customizeProxyFactory(proxyFactory);

   	proxyFactory.setFrozen(this.freezeProxy);
   	if (advisorsPreFiltered()) {
   		proxyFactory.setPreFiltered(true);
   	}

       // 3.调用getProxy获取bean对应的proxy
   	return proxyFactory.getProxy(getProxyClassLoader());
   }
  1. Какой тип прокси создать? JDKProxy или CGLIBProxy?
	public Object getProxy(ClassLoader classLoader) {
		return createAopProxy().getProxy(classLoader);
	}
    // createAopProxy()方法就是决定究竟创建何种类型的proxy
	protected final synchronized AopProxy createAopProxy() {
		if (!this.active) {
			activate();
		}
        // 关键方法createAopProxy()
		return getAopProxyFactory().createAopProxy(this);
	}
	
    // createAopProxy()
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        // 1.config.isOptimize()是否使用优化的代理策略,目前使用与CGLIB
        // config.isProxyTargetClass() 是否目标类本身被代理而不是目标类的接口
        // hasNoUserSuppliedProxyInterfaces()是否存在代理接口
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
            
            // 2.如果目标类是接口类(目标对象实现了接口),则直接使用JDKproxy
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
            
            // 3.其他情况则使用CGLIBproxy
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}
  1. метод getProxy()
   final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable// JdkDynamicAopProxy类结构,由此可知,其实现了InvocationHandler,则必定有invoke方法,来被调用,也就是用户调用bean相关方法时,此invoke()被真正调用
   // getProxy()
   public Object getProxy(ClassLoader classLoader) {
   	if (logger.isDebugEnabled()) {
   		logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
   	}
   	Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
   	findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
       
       // JDK proxy 动态代理的标准用法
   	return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
   }
  1. метод вызова()
    //使用了JDK动态代理模式,真正的方法执行在invoke()方法里,看到这里在想一下上面动态代理的例子,是不是就完全明白Spring源码实现动态代理的原理了。
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		MethodInvocation invocation;
		Object oldProxy = null;
		boolean setProxyContext = false;
 
		TargetSource targetSource = this.advised.targetSource;
		Class<?> targetClass = null;
		Object target = null;
 
		try {
            // 1.以下的几个判断,主要是为了判断method是否为equals、hashCode等Object的方法
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				// The target does not implement the equals(Object) method itself.
				return equals(args[0]);
			}
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				// There is only getDecoratedClass() declared -> dispatch to proxy config.
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				// Service invocations on ProxyConfig with the proxy config...
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}
 
			Object retVal;
 
			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}
 
			// May be null. Get as late as possible to minimize the time we "own" the target,
			// in case it comes from a pool.
			target = targetSource.getTarget();
			if (target != null) {
				targetClass = target.getClass();
			}
			// 2.获取当前bean被拦截方法链表
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
 
			// 3.如果为空,则直接调用target的method
			if (chain.isEmpty()) {
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
            // 4.不为空,则逐一调用chain中的每一个拦截方法的proceed,这里的一系列执行的原因以及proceed执行的内容,我 在这里就不详细讲了,大家感兴趣可以自己去研读哈
			else {
				// We need to create a method invocation...
				invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// Proceed to the joinpoint through the interceptor chain.
				retVal = invocation.proceed();
			}
 
			...
			return retVal;
		}
	}
	}

Итак, содержание, о котором я хочу рассказать, почти подошло к концу. Если что-то не так, или если у вас есть какие-то сомнения, подскажите, пожалуйста!