Это второй в серии вопросов весеннего интервью, тема этой статьи: что вовлечено веснойШаблоны проектирования, Как отвечать на собеседовании максимально полно, точно и подробно.
Эта статья отвечает только на один вопрос:
Какие шаблоны проектирования используются в 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]