Отсканируйте QR-код ниже или WeChat, чтобы найти официальную учетную запись.
菜鸟飞呀飞, вы можете подписаться на общедоступную учетную запись WeChat и прочитать больше статей об анализе исходного кода Spring.
Первое, что нужно отметить, это то, что, хотя FactoryBean и BeanFactory имеют похожие имена, это две совершенно разные концепции, и их использование также очень различно. BeanFactory — это Bean factory. В какой-то степени мы можем просто понять, что это то, что мы обычно называем контейнером Spring (обратите внимание, что здесь он понимается просто как контейнер). Он завершает процесс создания bean-компонента, автоматической сборки и т. д. ., и сохраняет файл Create законченный одноэлементный компонент. И FactoryBean смотрит на название, мы можем догадаться, что это Bean, но это особый Bean, что в нем такого особенного? Какая польза от его особенностей в нашем обычном процессе разработки?
1. Использование FactoryBean
Особенностью FactoryBean является то, что он может зарегистрировать в контейнере два компонента, один из которых является самим собой, а другой является компонентом, представленным возвращаемым значением метода FactoryBean.getObject(). Давайте сначала почувствуем полезность FactoryBean с помощью следующего примера кода.
- Настройте класс CustomerFactoryBean, дайте ему реализовать интерфейс FactoryBean и перепишите два метода интерфейса: в методе getObejct() возвращается экземпляр объекта UserService, в методе getObjectType() возвращается UserService.class. Затем добавьте аннотацию @Component к CustomerFactoryBean, что означает, что класс CustomerFactoryBean передается Spring для управления.
package com.tiantang.study.component;
import com.tiantang.study.service.UserService;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
@Component
public class CustomerFactoryBean implements FactoryBean<UserService> {
@Override
public UserService getObject() throws Exception {
return new UserService();
}
@Override
public Class<?> getObjectType() {
return UserService.class;
}
}
- Определен класс UserService, и в конструкторе печатается строка журнала.
package com.tiantang.study.service;
public class UserService {
public UserService(){
System.out.println("userService construct");
}
}
- Определяет класс конфигурации AppConfig, который указывает, что Spring необходимо сканировать пакет в классе
com.tiantang.study.component
package com.tiantang.study.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.tiantang.study.component")
public class AppConfig {
}
- стартовый класс
public class MainApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("容器启动完成");
UserService userService = applicationContext.getBean(UserService.class);
System.out.println(userService);
Object customerFactoryBean = applicationContext.getBean("customerFactoryBean");
System.out.println(customerFactoryBean);
}
}
- результат печати консоли
- Прежде чем приступить к анализу исходного кода, мы можем сначала рассмотреть эти два вопроса:
-
- В классе AppConfig мы просто сканируем
com.tiantang.study.componentКлассы в этом пакете, согласно нашему обычному пониманию, в настоящее время должны помещаться в контейнер Spring только классом CustomerFactoryBean, а UserService не сканировался. И мы можем пройти тестapplicationContext.getBean(UserService.class)Достать боб из контейнера, зачем?
- В классе AppConfig мы просто сканируем
-
- Мы знаем, что по умолчанию, если у нас нет пользовательской стратегии именования, после сканирования нашего пользовательского класса в контейнер с помощью Spring beanName по умолчанию для bean-компонента в контейнере Spring является первой буквой имени класса в нижнем регистре, поэтому эта демонстрационная середина,
CustomerFactoryBeanBeanName одноэлементного объекта класса в контейнере — это customerFactoryBean. Итак, в это время мы вызываем метод getBean(beanName) для получения Bean через beanName.В это время теоретически должен быть возвращен одноэлементный объект класса CustomerFactoryBean. Однако, когда мы печатаем результат, мы обнаруживаем, что хеш-код этого объекта точно такой же, как хэш-код объекта userService, а это означает, что два объекта являются одним и тем же объектом.Почему это происходит? Почему не экземпляр объекта класса CustomerFactoryBean?
- Мы знаем, что по умолчанию, если у нас нет пользовательской стратегии именования, после сканирования нашего пользовательского класса в контейнер с помощью Spring beanName по умолчанию для bean-компонента в контейнере Spring является первой буквой имени класса в нижнем регистре, поэтому эта демонстрационная середина,
-
- Поскольку одноэлементный объект CustomerFactoryBean не может быть получен через beanName из customerFactoryBean, как его получить?
Ответы на 3 вопроса выше можно решить одним ответом, то есть FactoryBean — это особый Bean. Наш пользовательский CustomerFactoryBean реализует интерфейс FactoryBean, поэтому, когда CustomerFactoryBean сканируется в контейнер Spring, он фактически регистрирует в контейнере два bean-компонента, один из которых является одноэлементным объектом класса CustomerFactoryBean; другой возвращается объектом метода getObject() в демо, в методе getObject(), который мы переопределили, мы возвращаем экземпляр объекта UserService через new UserService(), поэтому мы можем получить экземпляр объекта UserService из контейнера. Если мы хотим получить одноэлементный объект CustomerFactoryBean через beanName, нам нужно добавить
&символ, следующий код, чтобы можно было получить собственный объект в соответствии с beanName.
public class MainApplication {
public static void main(String[] args) {
CustomerFactoryBean rawBean = (CustomerFactoryBean) applicationContext.getBean("&customerFactoryBean");
System.out.println(rawBean);
}
}
2. Исходный код FactoryBean
Из приведенного выше примера кода мы знаем роль FactoryBean и то, как использовать FactoryBean, а затем давайте посмотрим на принцип работы FactoryBean через исходный код.
- На этапе запуска контейнера Spring вызывается метод refresh(), вызывается метод finishBeanFactoryInitialization() в refresh() и, наконец, вызывается метод beanFactory.preInstantiateSingletons(). Итак, давайте сначала взглянем на исходный код этого метода. (Для тех, кто не знаком с методом refresh(), вы можете прочитать две другие мои статьи:Процесс запуска контейнера из серии исходных кодов Spring,Посмотрите на процесс создания Bean через исходный код).
public void preInstantiateSingletons() throws BeansException {
// 从容器中获取到所有的beanName
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 在此处会根据beanName判断bean是不是一个FactoryBean,实现了FactoryBean接口的bean,会返回true
// 此时当beanName为customerFactoryBean时,会返回true,会进入到if语句中
if (isFactoryBean(beanName)) {
// 然后通过getBean()方法去获取或者创建单例对象
// 注意:在此处为beanName拼接了一个前缀:FACTORY_BEAN_PREFIX
// FACTORY_BEAN_PREFIX是一个常量字符串,即:&
// 所以在此时容器启动阶段,对于customerFactoryBean,应该是:getBean("&customerFactoryBean")
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
// 下面这一段逻辑,是判断是否需要在容器启动阶段,就去实例化getObject()返回的对象,即是否调用FactoryBean的getObject()方法
if (bean instanceof FactoryBean) {
final FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
}
}
}
- На этапе запуска контейнера экземпляр объекта CustomerFactoryBean сначала создается с помощью метода getBean(). Если реализован интерфейс SmartFactoryBean и метод isEagerInit() возвращает true, то на этапе запуска контейнера будет вызван метод getObject() для регистрации объекта с возвращаемым значением метода getObject() в контейнере. В противном случае метод getObject() будет вызван только тогда, когда объект возвращаемого значения getObject() будет получен в первый раз.
- Метод doGetBean() будет вызываться в getBean() Ниже приведен упрощенный исходный код doGetBean(). Из исходного кода мы обнаружили, что в конечном итоге будет вызван метод getObjectForBeanInstance().
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;
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
if (mbd.isSingleton()) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
}
return (T) bean;
}
- В методе getObjectForBeanInstance() он сначала определяет, является ли компонент компонентом FactoryBean, если нет, то возвращает непосредственно компонент. Если это FactoryBean и имя начинается с амперсанда, это означает, что получен нативный объект FactoryBean, и он также будет возвращен напрямую. Если имя не начинается с амперсанда, это означает получение объекта, возвращаемого методом getObject() в FactoryBean. Сначала он попытается получить его из карты кеша factoryBeanObjectCache класса FactoryBeanRegistrySupport, если он есть в кеше, то будет возвращен, если нет, то будет вызван метод getObjectFromFactoryBean(). Часть исходного кода метода getObjectForBeanInstance() выглядит следующим образом:
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
if (BeanFactoryUtils.isFactoryDereference(name)) {
if (beanInstance instanceof NullBean) {
return beanInstance;
}
if (!(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
}
}
// 如果bean不是factoryBean,那么会直接返回Bean
// 或者bean是factoryBean但name是以&特殊符号开头的,此时表示要获取FactoryBean的原生对象。
// 例如:如果name = &customerFactoryBean,那么此时会返回CustomerFactoryBean类型的bean
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
// 如果是FactoryBean,那么先从cache中获取,如果缓存不存在,则会去调用FactoryBean的getObject()方法。
Object object = null;
if (mbd == null) {
// 从缓存中获取。什么时候放入缓存的呢?在第一次调用getObject()方法时,会将返回值放入到缓存。
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
// 在getObjectFromFactoryBean()方法中最终会调用到getObject()方法
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
- В методе getObjectFromFactoryBean() компонент получается главным образом путем вызова метода doGetObjectFromFactoryBean(), затем компонент обрабатывается и, наконец, помещается в кэш. Кроме того, он также будет различать одноэлементные компоненты и неодноэлементные компоненты.Для одноэлементных компонентов они будут помещены в кеш после их создания, в то время как неодноэлементные компоненты не будут помещены в кеш и будут созданы заново.
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
// 如果BeanFactory的isSingleton()方法返回值是true,表示getObject()返回值对象是单例的
if (factory.isSingleton() && containsSingleton(beanName)) {
synchronized (getSingletonMutex()) {
// 再一次判断缓存中是否存在。(双重检测机制,和平时写线程安全的代码类似)
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
// 在doGetObjectFromFactoryBean()中才是真正调用getObject()方法
object = doGetObjectFromFactoryBean(factory, beanName);
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
else {
// 下面是进行后置处理,和普通的bean的后置处理没有任何区别
if (shouldPostProcess) {
if (isSingletonCurrentlyInCreation(beanName)) {
return object;
}
beforeSingletonCreation(beanName);
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Post-processing of FactoryBean's singleton object failed", ex);
}
finally {
afterSingletonCreation(beanName);
}
}
// 放入到缓存中
if (containsSingleton(beanName)) {
this.factoryBeanObjectCache.put(beanName, object);
}
}
}
return object;
}
}
// 非单例
else {
Object object = doGetObjectFromFactoryBean(factory, beanName);
if (shouldPostProcess) {
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
}
}
return object;
}
}
- Логика метода doGetObjectFromFactoryBean() относительно проста и напрямую вызывает метод getObject() компонента FactoryBean. Часть исходного кода выглядит следующим образом
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
throws BeanCreationException {
Object object;
if (System.getSecurityManager() != null) {
AccessControlContext acc = getAccessControlContext();
try {
object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
// 调用getObject()方法
object = factory.getObject();
}
return object;
}
- Код Spring действительно хорошо написан, и почти каждый метод имеет высокую степень повторного использования, что приводит к тому, что в методе всегда есть метод, а иерархия относительно глубокая, поэтому, наконец, блок-схема суммирует процесс создания FactoryBean.
- Прочитав анализ исходного кода в этом разделе, вы сможете понять результаты печати в демоверсии на данный момент.
3. Сценарий приложения FactoryBean — принцип плагина Spring-Mybatis
Теперь, когда вы знаете принцип FactoryBean, какие сценарии использования FactoryBean вы видели в своей повседневной работе? Если вы не заметили, то в качестве примера автор возьмет принцип интеграции Mybatis в Spring.
- Сначала мы можем вспомнить, что нам нужно делать при использовании только Mybatis. Добавьте зависимости, настройте источники данных, создайте SqlSessionFactory, и среда настроена. (Если у друга смутная память, можно обратиться к официальной документации:no-lift.org/no-lift-3/…).
- Когда мы интегрируем Mybatis в Spring, мы также добавляем зависимости mybatis, но нам также нужно добавить дополнительный пакет jar:
mybatis-spring, а затем настройте источник данных. Наконец, вам нужна конфигурация.Если вы настраиваете ее через XML, вам также потребуется следующая конфигурация:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
- Если вы не настроены через JavaConfig, то вам нужно настроить следующим образом:
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
- Мы обнаружили, что независимо от того, является ли это XML или JavaConfig, SqlSessionFactoryBean регистрируется в контейнере. Из имени класса мы можем знать, что это FactoryBean. Когда мы используем только Mybatis, нам нужно создать SqlSessionFactory, но когда MyBatis интегрирован со Spring, нам нужен SqlSessionFactoryBean, поэтому мы можем догадаться, регистрирует ли SqlSessionFactoryBean SqlSessionFactory с контейнером Spring по особенностям FactoryBean. При просмотре исходного кода SqlSessionFactoryBean обнаруживается, что он действительно реализует интерфейс FactoryBean, переписывает метод getObejct и регистрирует SqlSessionFactory в контейнере с помощью метода getObject().
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
// ...省略其他代码
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
}
- sqlSessionFactory — это свойство SqlSessionFactoryBean, и его назначение выполняется с помощью метода обратного вызова afterPropertiesSet(). (Поскольку SqlSessionFactoryBean реализует интерфейс InitializingBean, когда Spring инициализирует Bean, он может вызвать метод afterPropertiesSet())
public void afterPropertiesSet() throws Exception {
// buildSqlSessionFactory()方法会根据mybatis的配置进行初始化。
this.sqlSessionFactory = buildSqlSessionFactory();
}
- В интеграции Spring и MyBatis есть еще одно место, где также используется FactoryBean. Когда мы разрабатываем, мы обычно сканируем наши файлы Mapper с помощью аннотаций MapperScan. В аннотацию MapperScan добавлен Import(MapperScannerRegistrar.class), (на роль аннотации Import можно посмотреть другую статью автора:Tickets.WeChat.QQ.com/Yes/Yes_2Z9No 0…)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}
- В методе registerBeanDefinitions() MapperScannerRegistrar определенный Mapper будет просканирован и проанализирован в BeanDefinition.Обратите внимание, что после анализа в BeanDefinition свойство beanClass больше не является классом определенного нами класса Mapper, а устанавливается в MapperFactoryBean .class . Это показывает, что каждый интерфейс Mapper, который мы определяем, после загрузки в Spring, наконец, будет соответствовать MapperFactoryBean.
- Давайте посмотрим, что делает класс MapperFactoryBean. Ниже приведена часть исходного кода класса MapperFactoryBean. Из исходного кода мы обнаружили, что он реализует интерфейс FactoryBean и переписывает три метода интерфейса. В методе getObject(), вызвав
getSqlSession().getMapper(this.mapperInterface)Возвращается объект. Эта строка кода в конечном итоге вызовет метод newInstance() MapperProxyFactory для создания прокси-объекта для каждого Mapper.
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
// 返回true是为了让Mapper接口是一个单例的
return true;
}
}
- Исходный код класса MapperProxyFactory. Наконец, вызывается динамический прокси JDK для создания динамического прокси для определенного нами Mapper. (Среда MyBatis — это слой dao, реализованный динамическим прокси)
public class MapperProxyFactory<T> {
protected T newInstance(MapperProxy<T> mapperProxy) {
// JDK动态代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
- Таким образом, каждый интерфейс Mapper, который мы пишем, будет соответствовать MapperFactoryBean, а метод getObject() каждого MapperFactoryBean в конечном итоге будет использовать динамический прокси JDK для создания объекта, поэтому каждый интерфейс Mapper, наконец, соответствует прокси-объекту, таким образом реализуя Spring. и интеграция MyBatis.
4. Принцип mybatis-spring-boot-starter
Принцип интеграции MyBatis в SpringBoot тот же, хотя и используется зависимость mybatis-spring-boot-starter, финальный принцип интеграции точно такой же, как и у Spring. Основная функция SpringBoot — автоматическая настройка, а принцип интеграции с другими фреймворками почти такой же, как и у Spring.
- В mybatis-spring-boot-starter будет представлен пакет jar mybatis-spring-boot-autoconfigure Автоматическая настройка MyBatis реализована через класс MybatisAutoConfiguration в этом пакете jar. Из исходного кода MybatisAutoConfiguration мы видим, что SqlSessionBean также регистрируется в контейнере с помощью метода getObject() SqlSessionFactoryBean.
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
// 省略部分代码
return factory.getObject();
}
}
5. Резюме
- В этой статье в основном представлены роли и сценарии использования FactoryBean. Сначала демонстрируется использование FactoryBean в демо-версии, а затем анализируется принцип работы FactoryBean в сочетании с исходным кодом Spring.
- Затем с помощью исходного кода анализируется важная роль FactoryBean в процессе интеграции Spring и MyBatis. Один из них — предоставить SqlSessionFactory, а другой — создать динамический прокси-объект JDK для каждого Mapper.
- Наконец, принцип интеграции Mybatis в SpringBoot анализируется с помощью небольшого исходного кода. На самом деле принцип интеграции MyBatis со Spring точно такой же, поэтому основное внимание по-прежнему уделяется пониманию исходного кода Spring.
6. Думаю, вам это нравится
- Посмотрите на процесс создания Bean через исходный код
- Процесс запуска контейнера из серии исходных кодов Spring
- Весной самое то! самый! самый! Важный постпроцессор! никто из них! ! !
- @Импорт и @EnableXXX
- Напишите подключаемый модуль, который интегрирует Redis и Spring.
- Почему динамический прокси JDK должен быть реализован на основе интерфейсов, а не на основе наследования?
Отсканируйте QR-код ниже, чтобы подписаться на общедоступную учетную запись WeChat.
菜鸟飞呀飞и прочтите исходный код Spring вместе.