Простая реализация АОП в простой версии фреймворка Spring (непростая для меня)

задняя часть GitHub Spring регулярное выражение

Упрощенная версия фреймворка Spring.

Функции

  • Поддерживает bean-компоненты типа singleton, включая инициализацию, внедрение свойств и внедрение bean-компонентов зависимостей.
  • Конфигурацию можно прочитать из xml.
  • АОП может быть написан наподобие Aspectj, который поддерживает интерфейс и прокси класса.

иллюстрировать

Если вам посчастливится увидеть

  • 1. Справочник по Simple Spring Framework в этой статьеGithub, ссылка на комментарий к кодуGithub.
  • 2. Право собственности принадлежит оригинальному автору, здесь я просто копирую, обратитесь к процессу восстановления аннотации. Если не понятно, можете посмотреть авторское видео.
  • 3. Давайте работать вместе и учиться вместе.Если вам интересно, вы также можете посмотреть моиGithub. Загрузил исходный код Spring. Вы можете увидеть большого парня.
  • 4. Я надеюсь, что у вас есть исходный код Spring до прочтения этой статьи. Сравните, чтобы найти нужный интерфейс и класс. углубить впечатление.
  • 5. Эта статья предназначена только для моего собственного обзора в будущем, пожалуйста, простите меня, если это неправильно.

tiny-springОн разработан для изучения Spring и может рассматриваться как упрощенная версия Spring. В Spring много кода, сложных слоев и его трудно читать. Я пытаюсь использовать функцию с точки зрения, обратиться к реализации Spring, построить ее шаг за шагом и, наконец, завершить упрощенную версию Spring. Некоторые сравнивают программистов с художниками.У художников есть базовый навык, называемый копированием.Tiny-spring можно рассматривать как копирующую версию программы - отталкиваясь от собственных нужд, дизайна программы, и в то же время ссылаясь на известные проекты.

Часть II: АОП и реализация

АОП делится на две части: конфигурация (Pointcut, Advice), плетение (Weave) и, конечно же, другая часть — интегрировать АОП во весь жизненный цикл контейнера.

7.step7-Используйте динамический прокси JDK для реализации AOP-ткачества

Плетение относительно простое, поэтому начнем с него. Ключевым элементом Spring AOP является AopProxy, который содержит метод Object getProxy() для получения прокси-объекта.

Я думаю, что в Spring AOP две наиболее важные роли — это хорошо знакомые нам MethodInterceptor и MethodInvocation (оба являются стандартами альянса AOP), которые соответствуют двум основным ролям в AOP: Advice и Joinpoint. Advice определяет логику, указанную в pointcut, а Joinpoint представляет собой pointcut.

Интерфейсы, связанные с Pointcut Notifier

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}

АОП Spring поддерживает только вызов на уровне метода, поэтому фактически в AopProxy нам нужно только поместить MethodInterceptor в вызов метода объекта.

Использовать динамический прокси jdk

public class JdkDynamicAopProxy extends AbstractAopProxy implements InvocationHandler {

    public JdkDynamicAopProxy(AdvisedSupport advised ) {
        super(advised);
    }

    @Override
    public Object getProxy() {
        return Proxy.newProxyInstance(getClass().getClassLoader(),advised.getTargetSource().getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //提取拦截的方法
        MethodInterceptor methodInterceptor = (MethodInterceptor) advised.getMethodInterceptor();
        //比较传入的方法和对象的方法是否一致,如果一致则调用传入的方法,
        if (advised.getMethodMatcher() != null
                && advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
            //这里应该是先调用拦截的方法,然后调用原始对象的方法。但是一般括号里的东西不是优先吗?括号里面好像就只有赋值操作而已。
            return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(),method, args));
        } else {
            return method.invoke(advised.getTargetSource().getTarget(), args);
        }
    }

Мы называем прокси-объект TargetSource, а AdvisedSupport — это объект метаданных, который содержит TargetSource и MethodInterceptor. На этом этапе мы сначала реализуем JdkDynamicAopProxy на основе динамического прокси-сервера JDK, который может проксировать интерфейс. Итак, у нас есть основная функция плетения.

@Test
public void testInterceptor() throws Exception {
  // --------- helloWorldService without AOP
  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
  HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
  helloWorldService.helloWorld();

  // --------- helloWorldService with AOP
  // 1. 设置被代理对象(Joinpoint)
  AdvisedSupport advisedSupport = new AdvisedSupport();
  TargetSource targetSource = new TargetSource(helloWorldService, HelloWorldServiceImpl.class,
      HelloWorldService.class);
  advisedSupport.setTargetSource(targetSource);

  // 2. 设置拦截器(Advice)
  TimerInterceptor timerInterceptor = new TimerInterceptor();
  advisedSupport.setMethodInterceptor(timerInterceptor);

  // 3. 创建代理(Proxy)
  JdkDynamicAopProxy jdkDynamicAopProxy = new JdkDynamicAopProxy(advisedSupport);
  HelloWorldService helloWorldServiceProxy = (HelloWorldService) jdkDynamicAopProxy.getProxy();

  // 4. 基于AOP的调用
  helloWorldServiceProxy.helloWorld();

}

8.step8- Используйте AspectJ для управления аспектами

git checkout step-8-invite-pointcut-and-aspectj

Закончив плетение, мы должны рассмотреть еще один вопрос: на каком классе и каким методом выполнять АОП? Для определения «где все» мы также называем его «Pointcut». В Spring есть две роли для Pointcut:ClassFilterа такжеMethodMatcher, которые соответствуют классам и методам соответственно. Pointcut имеет много методов определения, таких как сопоставление имени класса, обычное сопоставление и т. д., но более широко используемыми должны быть иAspectJспособ выражения.

AspectJявляется «расширением АОП для Java». Сначала это был язык, мы писали его как Java-код, а после статической компиляции он получил функцию АОП. Вот кусок кода AspectJ:

aspect PointObserving {
    private Vector Point.observers = new Vector();

    public static void addObserver(Point p, Screen s) {
        p.observers.add(s);
    }
    public static void removeObserver(Point p, Screen s) {
        p.observers.remove(s);
    }
    ...
}

Этот метод, несомненно, слишком тяжелый для АОП, но и для адаптации к языку? Так что сейчас он мало используется, ноPointcutВыражения заимствованы из Spring. Таким образом, мы достиглиAspectJExpressionPointcut:

    @Test
    public void testMethodInterceptor() throws Exception {
        String expression = "execution(* us.codecraft.tinyioc.*.*(..))";
        AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
        aspectJExpressionPointcut.setExpression(expression);
        boolean matches = aspectJExpressionPointcut.getMethodMatcher().matches(HelloWorldServiceImpl.class.getDeclaredMethod("helloWorld"),HelloWorldServiceImpl.class);
        Assert.assertTrue(matches);
    }
public class AspectJExpressionPointcutTest {

    @Test
    public void testClassFilter() throws Exception {
        String expression = "execution(* us.codecraft.tinyioc.*.*(..))";
        AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
        aspectJExpressionPointcut.setExpression(expression);
        boolean matches = aspectJExpressionPointcut.getClassFilter().matches(HelloWorldService.class);
        Assert.assertTrue(matches);
    }

    @Test
    public void testMethodInterceptor() throws Exception {
        String expression = "execution(* us.codecraft.tinyioc.*.*(..))";
        AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
        aspectJExpressionPointcut.setExpression(expression);
        boolean matches = aspectJExpressionPointcut.getMethodMatcher().matches(HelloWorldServiceImpl.class.getDeclaredMethod("helloWorld"),HelloWorldServiceImpl.class);
        Assert.assertTrue(matches);
    }

9.step9-Включите АОП в процесс создания бина

git checkout step-9-auto-create-aop-proxy

Все готово, кроме возможности! Теперь, когда у нас есть технологии Pointcut и Weave, АОП завершен, но он еще не интегрирован в Spring. Как это совместить? Spring имеет аккуратный ответ: используйтеBeanPostProcessor.

BeanPostProcessor — это интерфейс, предоставляемый BeanFactory, который расширяется во время инициализации Bean. Пока ваш компонент реализуетBeanPostProcessorинтерфейс, то Spring сначала найдет их при инициализации и вызовет этот интерфейс во время процесса инициализации Bean, чтобы реализовать ненавязчивое расширение ядра BeanFactory.

Так как же работает наш АОП? Мы знаем, что в XML-конфигурации АОП мы напишем это предложение:

<aop:aspectj-autoproxy/>

На самом деле это эквивалентно:

<bean id="autoProxyCreator" class="org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator"></bean>

AspectJAwareAdvisorAutoProxyCreatorЭто ядро ​​реализации ткачества в стиле AspectJ. На самом деле это BeanPostProcessor. Здесь он сканирует все Pointcuts и сплетает бобы.

Чтобы упростить настройку xml, я использую метод Bean непосредственно в tiny-spring вместо того, чтобы настраивать его с помощью префикса aop:

    <bean id="autoProxyCreator" class="us.codecraft.tinyioc.aop.AspectJAwareAdvisorAutoProxyCreator"></bean>

    <bean id="timeInterceptor" class="us.codecraft.tinyioc.aop.TimerInterceptor"></bean>

    <bean id="aspectjAspect" class="us.codecraft.tinyioc.aop.AspectJExpressionPointcutAdvisor">
        <property name="advice" ref="timeInterceptor"></property>
        <property name="expression" value="execution(* us.codecraft.tinyioc.*.*(..))"></property>
    </bean>

TimerInterceptorДостигнутоMethodInterceptor(На самом деле Весна тоже имеетAdviceТакую роль для простоты используйте непосредственно MethodInterceptor).

На данный момент АОП в основном завершен.

10.step10- используйте CGLib для плетения классов

git checkout step-10-invite-cglib-and-aopproxy-factory

Предыдущий динамический прокси JDK может проксировать только интерфейс, но ничего не может сделать для класса. Здесь нам понадобятся некоторые приемы манипулирования байт-кодом. Для этого, вероятно, есть несколько вариантов:ASM,CGLibа такжеjavassist, последние два верныASMупаковка. CGLib используется в Spring.

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

Кроме того, мы достиглиCglib2AopProxy, как использовать иJdkDynamicAopProxyточно такие же.

public class Cglib2AopProxy extends AbstractAopProxy {

	public Cglib2AopProxy(AdvisedSupport advised) {
		super(advised);
	}

	//通过cglib类库创建了一个代理类的实例
	@Override
	public Object getProxy() {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(advised.getTargetSource().getTargetClass());
		enhancer.setInterfaces(advised.getTargetSource().getInterfaces());
		//设置代理类的通知方法,相当于设置拦截器方法
		enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
		Object enhanced = enhancer.create();
		return enhanced;
	}

	//方法拦截器
	private static class DynamicAdvisedInterceptor implements MethodInterceptor {

		private AdvisedSupport advised;

		private org.aopalliance.intercept.MethodInterceptor delegateMethodInterceptor;

		private DynamicAdvisedInterceptor(AdvisedSupport advised) {
			this.advised = advised;
			this.delegateMethodInterceptor = advised.getMethodInterceptor();
		}

		//调用代理类的方法(代理类与原始类是父子关系,还有一种是兄弟关系,调用实质是调用原始类的方法)
		@Override
		public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
			if (advised.getMethodMatcher() == null
					|| advised.getMethodMatcher().matches(method, advised.getTargetSource().getTargetClass())) {
				//这里也应该是先调用拦截方法,然后调用原始对象的方法
				return delegateMethodInterceptor.invoke(new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy));
			}
			return new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy).proceed();
		}
	}

	private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

		private final MethodProxy methodProxy;

		public CglibMethodInvocation(Object target, Method method, Object[] args, MethodProxy methodProxy) {
			super(target, method, args);
			this.methodProxy = methodProxy;
		}

		@Override
		public Object proceed() throws Throwable {
			return this.methodProxy.invoke(this.target, this.arguments);
		}
	}

Одна деталь заключается в том, что прокси, созданный CGLib, не имеет внедренных свойств. Решение Spring таково: CGLib используется только как прокси, все свойства хранятся в TargetSource, а метод вызывается методом MethodInterceptor=>TargetSource.

На этом часть АОП завершена.

1. Обработка основных классов и отношений АОП через выражения AspectJ

2. Используйте динамический прокси cglib

3. Два динамических агента

Некоторые резюме:

  • Послойная инкапсуляция, рациональное использование комбинации, класса наследования или интерфейса для придания и расширения соответствующих функций класса

  • Использование интерфейса:

    • Ненавязчивое встраивание путем раскрытия методов интерфейса (например: раскрытие интерфейса BeanPostProcessor, класс, реализующий этот интерфейс, будет иметь приоритет над созданием экземпляра обычного компонента и может выполнять некоторые операции инициализации компонента до создания экземпляра компонента, например: aop ткачество)
    • Интерфейс BeanFactoryAware предоставляет возможность получения beanFactory.Класс, наследующий этот интерфейс, имеет возможность управлять beanFactory, а также может специально управлять bean-компонентом.
  • Шаблон метода шаблона и применение метода крючка:   Например: процесс загрузки, инстанцирования, инициализации и получения бина стандартизирован в AbstractBeanFactory. Метод ловушки (метод applyPropertyValues) реализован в AutowireCapableBeanFactory, который вызывается в методе AbstractBeanFactory#initializeBean.В AbstractBeanFactory есть пустая реализация метода ловушки по умолчанию.

  • Применение шаблона фабричного метода: Например: BeanFactory#getBean, подкласс должен решить, как получить компонент и выполнить соответствующие операции при его получении. Фабричные методы откладывают создание экземпляров до подклассов.

  • Использование режима внешнего вида (фасада): ClassPathXmlApplicationContext для Resouce, BeanFactory, BeanDefinition Функция инкапсулирована для решения проблемы получения ресурсов по адресу, регистрации определений bean-компонентов и создания их экземпляров через IoC-контейнер, а также инициализации bean-компонентов и предоставления простого метода их использования.

  • Использование агентской модели.:

    • Через динамический прокси-сервер jdk: динамический прокси-сервер JDK основан на интерфейсе и должен реализовать один или несколько произвольных интерфейсов, прежде чем его можно будет проксировать, и будут проксироваться только методы в этих интерфейсах.
    • Динамическое проксирование через cglib: cglib реализует проксирование для классов. Его принцип заключается в создании подкласса для указанного целевого класса и перезаписи методов в нем для достижения улучшения. Однако, поскольку используется наследование, его нельзя использовать для окончательно измененных классов. как прокси.
  • Использование одноэлементного шаблона:

    • Tiny-spring по умолчанию является одноэлементным bean-компонентом. Зарегистрируйте определение bean-компонента в AbstractApplicationContext#refresh.После инициализации bean-компонент создается в форме singleton по умолчанию: имя beanDefinition получается в методе preInstantiateSingletons, а затем bean-компонент создается с помощью метода getBean(name).В следующий раз, когда getBean(name), он сначала проверит, был ли создан экземпляр bean-компонента в beanDefinition имени.Если он был создан, он вернет ссылку на этот bean-компонент вместо создание нового bean-компонента.
    • Общий метод реализации в стандартном одноэлементном шаблоне состоит в том, чтобы создать экземпляр объекта этого класса через getInstance (двойная проверка) в первый раз и сохранить его, а затем вернуть объект в следующий раз, когда вы getInstance.
  • режим стратегии: Вот идея, посмотрите на метод построения ClassPathXMLApplicationContext, вы можете знать, что стратегия автосборки используется по умолчанию.Вот вы можете написать еще один класс для наследования AbstractBeanFactory, переписать метод applyPropertyValues ​​для реализации стратегии сборки, и вы может выбирать различные стратегии сборки во время инициализации.