FactoryBean簡介以及Mybatis-Spring應用


一、BeanFactory和FactoryBean區別?

BeanFactory是工廠類,提供了獲取和檢索Bean的接口。它代表着Spring的IoC容器,負責Bean實例化以及管理Bean之間的依賴關系。作為Spring框架中最核心的模塊,它提供容器的基本規范。

FactoryBean是一個bean,可以作為其他bean的工廠。FactoryBean像其他bean一樣在注入到IoC容器中,但是當從IoC容器中獲取FactoryBean的時候,實際返回的FactoryBean#getObject()方法返回的對象。如果想獲取FactoryBean本身,則需要在bean的名稱添加前綴&來獲取FactoryBean對象本身(applicationContext.getBean("&" + beanName))。

二、如何使用FactoryBean?

  1. 在了解如何使用FactoryBean之前,先看看FactoryBean接口的定義。
public interface FactoryBean<T> {

    /**
     * 實際返回的bean對象
     */
	@Nullable
	T getObject() throws Exception;

    /**
     * 實際返回bean的class對象
     */
	@Nullable
	Class<?> getObjectType();

    /**
     * 指定bean是否是單例,默認為true(Spring bean默認都是單例)。
     */
	default boolean isSingleton() {
		return true;
	}

}

FactoryBean接口定義了三個方法,其中getObject方法返回bean對象。

  1. 接下來通過一個簡單的獲取加密工具演示如何使用FactoryBean

定義一個FactoryBean類

@RequiredArgsConstructor
public class MessageDigestFactoryBean implements FactoryBean<MessageDigest> {
    // 算法名稱
    private final String algorithmName;

    @Override
    public MessageDigest getObject() throws Exception {
        return MessageDigest.getInstance(algorithmName);
    }

    @Override
    public Class<?> getObjectType() {
        return MessageDigest.class;
    }
}

通過上面定義的類,傳入不同的算法名稱實現獲取不同算法的加密類

@Configuration
public class MessageDigestConfiguration {

    @Bean
    public MessageDigestFactoryBean md5() {
        return new MessageDigestFactoryBean("MD5");
    }

    @Bean
    public MessageDigestFactoryBean sha1() {
        return new MessageDigestFactoryBean("SHA-1");
    }

}

這里定義了兩種加密算法MD5和SHA-1,這樣即可通過applicationContext對象獲取不同的加密工具

public class App {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MessageDigestConfiguration.class);
        MessageDigest md5 = applicationContext.getBean("md5", MessageDigest.class);
        System.out.println(Arrays.toString(md5.digest("test".getBytes())));
        MessageDigest sha1 = applicationContext.getBean("sha1", MessageDigest.class);
        System.out.println(Arrays.toString(sha1.digest("test".getBytes())));
    }

}

// 輸出
/*
[9, -113, 107, -51, 70, 33, -45, 115, -54, -34, 78, -125, 38, 39, -76, -10]
[-87, 74, -113, -27, -52, -79, -101, -90, 28, 76, 8, 115, -45, -111, -23, -121, -104, 47, -69, -45]
*/

可以看到,通過同一個FactoryBean類,向IoC容器中注入了兩個不同的bean。

如果讓FactoryBean#isSingleton方法返回false,那么得到的輸出如下

public class App {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MessageDigestConfiguration.class);

        System.out.println(applicationContext.getBean("md5", MessageDigest.class).hashCode());
        System.out.println(applicationContext.getBean("md5", MessageDigest.class).hashCode());
    }
}

// 輸出
/*
768192757
1697752980
*/

可以看出每次通過applicationContext獲取的對象都是一個新的對象,從而每個bean都是原型作用域。

三、FactoryBean在Mybatis集成Spring Boot中的應用

使用過Spring Boot的同學都知道,當我們需要掃描Mapper的時候,需要添加@MapperScan注解完成對Mapper對象的掃描,@MapperScan導入MapperScannerRegistrar類完成掃描。

但是Mapper類都是接口,無法被實例化,那么為什么在Spring中能夠直接注入Mapper對象呢?

實際上Mybatis是通過FactoryBean對象創建Mapper對象的代理對象,完成Mapper接口的注入。

下面跟隨Mybatis-Spring源碼了解如何動態創建Mapper對象的實現類。

首先看@MapperScan注解源碼

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class) // 導入MapperScannerRegistrar配置類
@Repeatable(MapperScans.class)
public @interface MapperScan {

  String[] basePackages() default {};

}

/**
 * 完成Mapper接口掃描
 */
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry);
    }
  }

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
    // 創建scanner對象,掃描Mapper
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    // 掃描的包
    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("value"))
            .filter(StringUtils::hasText)
            .collect(Collectors.toList()));

    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("basePackages"))
            .filter(StringUtils::hasText)
            .collect(Collectors.toList()));

    basePackages.addAll(
        Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
            .map(ClassUtils::getPackageName)
            .collect(Collectors.toList()));

    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

  static class RepeatingRegistrar extends MapperScannerRegistrar {
    /**
     * {@inheritDoc}
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
        BeanDefinitionRegistry registry) {
      AnnotationAttributes mapperScansAttrs = AnnotationAttributes
          .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
      if (mapperScansAttrs != null) {
        Arrays.stream(mapperScansAttrs.getAnnotationArray("value"))
            .forEach(mapperScanAttrs -> registerBeanDefinitions(mapperScanAttrs, registry));
      }
    }
  }

}

ClassPathMapperScan通過doScan方法掃描Mapper

  private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<>();
  
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      // 省略其余代碼
      
      // 記錄bean的類名(即Mapper接口類名),將Mapper類名傳遞給MapperFactoryBean作為構造方法參數
      // 這樣MapperFactoryBean的getObject方法即可通過動態代理創建Mapper的動態代理對象
      String beanClassName = definition.getBeanClassName();
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
      // 修改BeanClass
      definition.setBeanClass(this.mapperFactoryBean.getClass());
    }
  }

ClassPathMapperScan掃描到Mapper類之后,修改BeanClass為MapperFactoryBean

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  // Mapper的class對象,FactoryBean創建該Mapper的動態代理對象
  private Class<T> mapperInterface;

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
}

繼續跟蹤getSqlSession().getMapper(this.mapperInterface)方法

Configuration

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

MapperRegistry

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

可以看出來這里是通過MapperProxyFactory對象創建Mapper對象

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // JDK動態代理
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  /**
   * 創建Mapper對象的代理對象MapperProxy,MapperProxy實現了InvocationHandler接口
   */
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

繼續看MapperProxy對象的invoke方法

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
  
  /**
   * 根據接口定義方法執行SQL,並返回結果
   */
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
    	Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

可以看出Mybatis通過JDK動態代理的方式,創建Mapper接口的代理對象,並通過接口聲明的方法查找並執行SQL。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM