• 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。

  • 相关阅读:
    Ubuntu加上一个命令搜索路径/etc/ environment
    在Ubuntu中编译QT工程Tesful
    Java中的多线程
    [转载]iOS开发之手势识别
    OracleDBA之表管理
    JDBC与JAVA数据库编程
    Oracle之PL/SQL学习笔记
    在Objective-C中浅谈面向对象
    Web前端上万字的知识总结
    类比Spring框架来实现OC中的依赖注入
  • 原文地址:https://www.cnblogs.com/thisismartin/p/12254304.html
Copyright © 2020-2023  润新知