Упрощенная версия фреймворка 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 для реализации стратегии сборки, и вы может выбирать различные стратегии сборки во время инициализации.