Можете ли вы назвать все шаблоны проектирования, используемые в Spring? [начальство]

Spring

Это второй в серии вопросов весеннего интервью, тема этой статьи: что вовлечено веснойШаблоны проектирования, Как отвечать на собеседовании максимально полно, точно и подробно.

Эта статья отвечает только на один вопрос:

Какие шаблоны проектирования используются в Spring? Как достигается каждый?

Начнем с обзора. Всего в SpringFramework используется 11 шаблонов проектирования:

  • Шаблон прототипа синглтона +
  • заводской узор
  • прокси-режим
  • режим стратегии
  • Шаблон метода шаблона
  • Шаблон наблюдателя
  • режим адаптера
  • шаблон декоратора
  • Внешний вид Режим
  • Режим делегата(Не часть GoF23)

Конечно, если вы просто так ответите, то, что подумает интервьюер: вы. . . не будет вподдержите ответБар! Если вы просто выберете один и подробно расспросите, вы можете быть поставлены в тупик ~ ~ Таким образом, нам нужно не только знать, что использовать, но и как это использовать и где это использовать, чтобы мы могли использовать наши реальные технические резервы для покорить интервьюера.

Ниже мы подробно расскажем о сценариях проектирования и принципах 11 шаблонов проектирования.

Поскольку все 11 шаблонов проектирования слишком длинные, они будут разделены на две колонки.

Шаблон Singleton + шаблон прототипа

В IOC-контейнере SpringFramework находится очень много Bean-компонентов.По умолчанию область Bean-компонентов ( Scope ) равнаsingleton, является единственным экземпляром; если вы явно объявите область действия какprototype, область действия Bean-компонента будет становиться новой каждый раз при его приобретении, то есть прототипом Bean-компонента. Этот пункт знаний должен был быть известен на самом базовом этапе Spring Framework, и мы не будем слишком многословны. Ключевой вопрос заключается в том, что если Bean, который я определяю, объявляет прототип, тогда SpringFramework должен знать, что я хочу создать прототип Bean; но когда я определяю bean-компонент без объявления Scope, почему по умолчанию он дает мне один экземпляр?

Начнем с наиболее знакомого сценария регистрации Бина. (Шаблон-прототип относительно прост, а содержание перемежается поясняемым шаблоном-одиночкой.)

Регистрация бина

XML Регистрация Bean
<bean class="com.example.demo.bean.Person" scope="singleton"/>

Это самая простая регистрация Бина, если она здесь явно объявленаscope="singleton"После этого IDE сообщит желтым цветом (предупреждение):

Очевидно, это предполагает, что значение по умолчанию равноsingleton, нам не нужно проявлять инициативу, чтобы объявить. Эта подсказка интеллектуально распознается IDEA, нам трудно найти место, но мы можем нажать на эту область и посмотреть комментарии в xsd:

<xsd:attribute name="scope" type="xsd:string">
    <xsd:annotation>
        <xsd:documentation><![CDATA[
	The scope of this bean: typically "singleton" (one shared instance,
	which will be returned by all calls to getBean with the given id), ......

Очевидно, первое предложение комментария к документации гласит:Обычно этоsingletonиз.

Компонент регистрации, управляемый аннотациями

Подход, основанный на аннотациях, использует@ScopeАннотация для объявления области:

@Scope
@Component
public class Person {
    
}

нажмите на@ScopeАннотация Глядя на исходный код, вы можете обнаружить, что сделаны только аннотации@ScopeКомментарий, не высказывание охвата, значение по умолчанию является пустой строкой (неsingleton):

public @interface Scope {

	@AliasFor("scopeName")
	String value() default "";

В этом месте может быть путаница, объявляет пустую строку, а в xml настраиваюsingletonАх, чем это отличается? Не паникуйте, давайте проанализируем почему.

Аннотация области действия по умолчанию

Любой, у кого есть глубокие знания SpringFramework, должен знать, что я собираюсь сказать дальше:BeanDefinition. Вся информация об определении bean-компонента инкапсулируется SpringFramework дляBeanDefinition, а область определяется вBeanDefinitionабстрактный класс реализацииAbstractBeanDefinitionсередина:

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
		implements BeanDefinition, Cloneable {

	public static final String SCOPE_DEFAULT = "";

Как только он появляется, он объявляет, что областью действия по умолчанию является пустая строка, а неsingleton.

В это время некоторые друзья могут быть еще больше сбиты с толку: все объявляет, что bean-компонент с одним экземпляром является пустой строкой, затемsingletonА еще одно яйцо? Принятие решения о том, должен ли bean-компонент с одним экземпляром проверять,singleton?

Эй, вот так, давайте посмотримBeanDefinitionКак получить прицел в:

public String getScope() {
    return this.scope;
}

Способ получения прицела очень прост, в этом ничего не видно. но! ! ! Обратите внимание, что вы продолжаете прокручивать вниз, а затем метод, называемыйisSingleton:

/**
 * Return whether this a <b>Singleton</b>, with a single shared instance
 * returned from all calls.
 * @see #SCOPE_SINGLETON
 */
@Override
public boolean isSingleton() {
    return SCOPE_SINGLETON.equals(this.scope) || SCOPE_DEFAULT.equals(this.scope);
}

Глядя на суждение здесь, оно разделено на две части: является ли ** синглтоном или это пустая строка! **Тогда это имеет смысл.Если для него задана пустая строка, это также единственный экземпляр Bean в этом смысле.

Создание экземпляра компонента

Мы также знаем выше, что bean-компоненты являются одноэкземплярными по умолчанию, так как же SpringFramework узнает, являются ли эти bean-компоненты одноэкземплярными при инициализации контейнера IOC, и инициализирует и сохраняет его одновременно? Ниже рассмотрим базовую логику инициализации.

Эта часть лишь кратко знакомит с процессом инициализации Bean.Для подробного анализа обратитесь к Главе 14 моего буклета с исходным кодом SpringBoot для подробного изучения.

существуетAbstractBeanFactoryсередина,getBeanбудет вызываться методdoGetBean, этот способ очень длинный, здесь вырезаем только рамку:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
        @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    final String beanName = transformedBeanName(name);
    Object bean;

    // Eagerly check singleton cache for manually registered singletons.
    // 最开始先检查单实例对象缓存池中是否已经有对应的bean了
    Object sharedInstance = getSingleton(beanName);
    // ......
    else {
        // ......
        try {
            final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
            // 检查 ......

            // Create bean instance.
            if (mbd.isSingleton()) {
                // 单实例Bean
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        return createBean(beanName, mbd, args);
                    } // catch ......
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }

            else if (mbd.isPrototype()) {
                // 原型Bean
                // It's a prototype -> create a new instance.
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    // 必定创建全新的对象
                    prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            }

            else {
                // 自定义scope
                String scopeName = mbd.getScope();
                final Scope scope = this.scopes.get(scopeName);
                // ......
            }
        } // catch ......
    }
    // ......
    return (T) bean;
}

Внимательно прочитайте этот покадровый процесс, как только он появится, он сначала проверит, есть ли готовые бины в пуле буферов единственного экземпляра объекта, и не пойдет дальше. Если мы говорим о создании процесса, то давайте перейдем к его созданию.BeanDefinitionЧтобы судить о масштабах: если даодиночный экземплярДа, простовоплощать в жизньgetSingletonметод для создания одного экземпляра объекта(Нижний слой находится в лямбда-выраженииcreateBeanметод); если даprototypeBean-прототип, выполнить процесс создания Bean-прототипа (прямое создание); если это не так, его можно идентифицировать какпользовательская область, используя специальный процесс инициализации.

Таким образом, с этой точки зрения, основной метод создания отдельного экземпляра компонента по-прежнемуgetSingletonТеперь давайте зайдем сюда и посмотрим: (это пока только процесс большой рамы)

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 检查
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            } // catch finally ......
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

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

резюме

Таким образом, основная логика здесь может быть резюмирована:

Узор Singleton, реализованный на SpramFramework, находится вBeanDefinitionОбъем Singleton настроен по умолчанию в контейнере IOC. Во время фазы инициализации контейнера IOC боба создается и помещается в пул кеша объекта Only Exce (singletonObjects), реализует один экземпляр Bean.

заводской узор

Когда дело доходит до фабричного шаблона, проще всего придумать в SpringFramework этоFactoryBeanНу давай же! Но на самом деле не только этот является фабрикой в ​​SpringFramework, но и много других, перечислим их ниже.

FactoryBean

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

FactoryBeanИнтерфейс имеет три метода:

public interface FactoryBean<T> {
    // 返回创建的对象
    @Nullable
    T getObject() throws Exception;

    // 返回创建的对象的类型(即泛型类型)
    @Nullable
    Class<?> getObjectType();

    // 创建的对象是单实例Bean还是原型Bean,默认单实例
    default boolean isSingleton() {
        return true;
    }
}

статическая фабрика

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

public class CalculatorFactory {
    // 简单工厂
    public static Calculator getCalculator(String operationType) {
        switch (operationType) {
            case "+": 
                return new AddCalculator();
            case "-":
                return new SubtractCalculator();
            default: 
                return null;
        }
    }
    
    // 静态工厂
    public static Calculator getAddCalculator() {
        return new AddCalculator();
    }
}

При использовании статической фабрики в SpringFramework параметр отсутствует, нужно только объявить класс фабрики и метод (поэтому я написал дополнительный метод в приведенной выше фабрике):

<bean id="addCalculator" class="com.example.demo.bean.CalculatorFactory" factory-method="getAddCalculator"/>

Боба, полученная после регистрации таким образом, типAddCalculator.

фабрика экземпляров

Фабрики экземпляров используются почти так же, как и статические фабрики, за исключениемСама статическая фабрика не регистрируется в контейнере IOC, но фабрика экземпляров регистрируется вместе с контейнером IOC..

Настроив приведенный выше код, можно реализовать регистрацию bean-компонента фабрики экземпляров:

public class CalculatorFactory {
    // 工厂方法
    public Calculator getAddCalculator() {
        return new AddCalculator();
    }
}
<bean id="calculatorFactory" class="com.example.demo.bean.CalculatorFactory"/>
<bean id="addCalculator" factory-bean="calculatorFactory" factory-method="getAddCalculator"/>

ObjectFactory

Некоторым друзьям этот тип может показаться немного незнакомым, поэтому я написал его в конце.

@FunctionalInterface
public interface ObjectFactory<T> {
	T getObject() throws BeansException;
}

коэффициент структурыFactoryBeanпросто, конечно, его также можно понимать просто какFactoryBean, но разные.ObjectFactoryОбычно будетВводится в другие бобы как боб, который активно вызывается, когда необходимо использовать соответствующий bean-компонентObjectFactoryизgetObjectметодПолучить Бин очень нужно;FactoryBeanизgetObjectЭтот метод вызывается, когда SpringFramework инициализирует bean-компонент, поэтому вы также можете узнать разницу между двумяВремя звонка разное..

Собственно, этот интерфейс виден вышеПроцесс создания экземпляра компонентавстречается вgetSingletonВ методе с двумя аргументами вторым аргументом являетсяObjectFactoryтип, из которого он может быть вызванcreateBeanСоздайте один экземпляр объекта.

резюме

Фабричный шаблон в SpringFramework включает в себя встроенныеFactoryBean,ObjectFactory, а также объявленные пользователем статические фабрики и фабрики экземпляров.

прокси-режим

Все мы знаем, что два ядра Spring Framework: IOC и АОП, АОП отражает использование режима прокси. Однако если говорить только о том, что АОП отражает агентскую модель, то это слишком низкоуровнево, надо отвечать все более и более развернуто, чтобы интервьюер понял, что вы действительно учились и действительно понимаете!

Базовая реализация АОП

АОП-улучшение bean-компонентов в SpringFramework для создания прокси-объектов.BeanPostProcessor:AnnotationAwareAspectJAutoProxyCreator, название длинное, но легко запоминающееся:

  • Аннотация: Аннотация,
  • В курсе: инъекционный
  • AspectJ: АОП на основе AspectJ
  • AutoProxy: автоматический прокси
  • Создатель: создатель

Облегчает ли это разделение понимание?

Его основной функциональный метод является родительским классомAbstractAutoProxyCreatorизpostProcessAfterInitializationметод, нижний слой вызоветwrapIfNessaryспособ создания прокси-объекта:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 创建AOP代理对象
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

Что касается дальнейшего вниз, это много проблем.Вот краткое изложение.Для подробного создания прокси-объекта вы можете обратиться к главе 19 буклета с исходным кодом SpringBoot для обучения.

Bean-компоненты с расширенными возможностями AOP будут выполняться на этапе инициализации (когда объект был создан)AnnotationAwareAspectJAutoProxyCreatorОбработайте, интегрируйте аспекты, которые могут быть охвачены компонентом, и, наконец, используйте динамический прокси-сервер jdk или динамический прокси-сервер Cglib для создания и создания прокси-объектов в зависимости от того, имеет ли компонент реализацию интерфейса.

Создание прокси-объектов

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

Создание динамического прокси jdk, вJdkDynamicAopProxy, существует одинgetProxyметод, базовая реализация выглядит следующим образом:

public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    // jdk原生方法
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

Посмотрите на последнее предложение, оно вдруг знакомо! Это место можно вынести и продуть на собеседовании, так что интервьюер может действительно подумать, что вы разобрались с этой частью принципа (собачья голова).

Создание динамического прокси Cglib, вCglibAopProxyизcreateProxyClassAndInstanceЕсть реализация создания прокси-объекта в методе:

protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
    enhancer.setInterceptDuringConstruction(false);
    enhancer.setCallbacks(callbacks);
    return (this.constructorArgs != null && this.constructorArgTypes != null ?
            enhancer.create(this.constructorArgTypes, this.constructorArgs) :
            enhancer.create());
}

глянь сюдаEnhancer#create()метод, это снова знакомая сцена? Следовательно, мы также знаем, что каркас только усиливается слоями упаковки на основе того, что мы узнали, а нижний слой остается неизменным.

резюме

Режим прокси в SpringFramework отражен в АОП, Он интегрирует логику аспекта (усилителя Advice) через постпроцессор и расширяет исходный Bean (целевой объект Target) в прокси-бин с помощью динамического прокси jdk или Cglib.

режим стратегии

Говоря о шаблоне стратегии, реализованном в SpringFramework, я только что упомянул его: когда АОП генерирует прокси-объекты, онОпределите, следует ли использовать динамический прокси-сервер jdk или динамический прокси-сервер Cglib в зависимости от того, имеет ли исходный компонент реализацию интерфейса., который является типичным воплощением шаблона стратегии.

Поговорим непосредственно об обосновании.DefaultAopProxyFactoryЕсть вариант воплощения паттерна стратегии:

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    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.");
        }
        // 策略判断
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

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

[Ограниченное пространство, вариант осуществления оставшихся 6 шаблонов проектирования будет введен в следующую главу ~ Друзья не забывайте обращать внимание и нравится, если вам нужно изучить исходный код, вы можете прочитать мою буклет ~ Ollie Trasse]