spring整合mybatis原理,动态创建bean,注入到spring容器

6 篇文章 0 订阅
6 篇文章 0 订阅

使用过mybatis的都知道,要在配置中定义扫描mybatis对应的接口,也就是 MapperScan 注解。

那么MapperScan做了什么?

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.mybatis.spring.annotation;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.Import;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    Class<? extends Annotation> annotationClass() default Annotation.class;

    Class<?> markerInterface() default Class.class;

    String sqlSessionTemplateRef() default "";

    String sqlSessionFactoryRef() default "";

    Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}

 这是MapperScan注解的源码,从中我们可以看到 @Import({MapperScannerRegistrar.class}) 

@import注解,就是导入一个对象到容器中,他这里指定的类就是要导入到容器中的类,那么我们也可以模仿这个做法,自己创建一个注解。

package lxq.mybatis.springboot.annotation;

import lxq.mybatis.springboot.handler.ServiceBeanDefinitionRegistry;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * @description: 自定义扫描注解
 * @author: Lxq
 * @date: 2020/7/13 9:03
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({ServiceBeanDefinitionRegistry.class})
public @interface LxqMapperScan {

    String value() default "";
}

可以看到我这里指定的是  ServiceBeanDefinitionRegistry.class ,没错这个就是我们自定义的类

ServiceBeanDefinitionRegistry.class

package lxq.mybatis.springboot.handler;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * @description: 用于Spring动态注入自定义接口
 * @author: Lxq
 * @date: 2020/7/10 14:57
 */
public class ServiceBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, ApplicationContextAware {

    private ResourcePatternResolver resourcePatternResolver;
    private CachingMetadataReaderFactory metadataReaderFactory;
    private ApplicationContext applicationContext;
    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 这里通过反射获取需要代理的接口的clazz列表
        // 获取所有的接口类
        Set<Class<?>> beanClazzs = scannerPackages("lxq.mybatis.springboot.test");
        for (Class beanClazz : beanClazzs) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
            GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
            // 如果采用definition.getConstructorArgumentValues(),也就是构造方法注入
            // 则FactoryBean中需要提供包含该属性的构造方法,否则会注入失败
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);

            //注意,这里的BeanClass是生成Bean实例的工厂,不是Bean本身。
            // FactoryBean是一种特殊的Bean,其返回的对象不是指定类的一个实例,
            // 其返回的是该工厂Bean的getObject方法所返回的对象。
            definition.setBeanClass(ServiceFactory.class);

            //这里采用的是byType方式注入,类似的还有byName等
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
        }

    }


    /**
     * 获取包路径以及子包下面的所有类
     *
     * @param basePackage
     * @return
     */
    private Set<Class<?>> scannerPackages(String basePackage) {
        Set<Class<?>> set = new LinkedHashSet<>();
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN;
        try {
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    String className = metadataReader.getClassMetadata().getClassName();
                    Class<?> clazz;
                    try {
                        clazz = Class.forName(className);
                        set.add(clazz);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return set;
    }


    protected String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(this.getEnvironment().resolveRequiredPlaceholders(basePackage));
    }


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    /**
     * 获取资源加载器,可以获得外部资源文件
     */
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);

    }

    /**
     * 当前的application context从而调用容器的服务
     *
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private Environment getEnvironment() {
        return applicationContext.getEnvironment();
    }
}

 这里做的就是先扫包,将所有的接口扫描出来,然后构建 BeanDefinition ,也就是bean的定义,然后通过bean工厂生产。

仔细看的时候会发现,definition.setBeanClass(ServiceFactory.class) 在这里的时候我们设置了一个bean class 解释我上面已经说明。

ServiceFactory.class

 

package lxq.mybatis.springboot.handler;

import org.springframework.beans.factory.FactoryBean;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @description:接口实例工厂,这里主要是用于提供接口的实例对象
 * @author: Lxq
 * @date: 2020/7/10 14:49
 */
public class ServiceFactory<T> implements FactoryBean {

    private Class<T> interfaceType;

    public ServiceFactory(Class<T> interfaceType) {
        this.interfaceType = interfaceType;
    }


    @Override
    public Object getObject() throws Exception {
        //这里主要是创建接口对应的实例,便于注入到spring容器中
        InvocationHandler handler = new ServiceProxy<>(interfaceType);
        return Proxy.newProxyInstance(interfaceType.getClassLoader(), new Class[]{interfaceType}, handler);
    }

    @Override
    public Class<?> getObjectType() {
        return interfaceType;
    }
}

这里利用的就是JDK动态代理,还有我模仿mybtais 将 InvocationHandler 也抽出来了,你也直接用匿名方法创建。

ServiceProxy.class

package lxq.mybatis.springboot.handler;

import com.alibaba.fastjson.JSON;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @description: 动态代理,需要注意的是,这里用到的是JDK自带的动态代理,
 * 代理对象只能是接口,不能是类
 * @author: Lxq
 * @date: 2020/7/10 14:38
 */
public class ServiceProxy<T> implements InvocationHandler {


    private Class<T> interfaceType;

    public ServiceProxy(Class<T> intefaceType) {
        this.interfaceType = interfaceType;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this,args);
        }
        System.out.println("调用前,参数:{}" + args);
        //这里可以得到参数数组和方法等,可以通过反射,注解等,进行结果集的处理
        //mybatis就是在这里获取参数和相关注解,然后根据返回值类型,进行结果集的转换
        Object result = JSON.toJSONString(args);
        System.out.println("调用后,结果:{}" + result);
        return result;
    }
}

当我们将接口注入,调用的时候,就会执行invoke方法,接下来创建两个接口并测试。

package lxq.mybatis.springboot.test;

/**
 * @description:
 * @author: Lxq
 * @date: 2020/7/10 14:48
 */
public interface TestService {
    String getList(String code, String name);
}

 

package lxq.mybatis.springboot.test;

/**
 * @description:
 * @author: Lxq
 * @date: 2020/7/10 14:47
 */
public interface CalculateService {
    String getResult(String name);
}

TestController

package lxq.mybatis.springboot.web;

import lxq.mybatis.springboot.test.CalculateService;
import lxq.mybatis.springboot.test.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @description:
 * @author: Lxq
 * @date: 2020/7/10 15:17
 */
@RestController
public class TestController {

    @Autowired
    private TestService testService;

    @Autowired
    private CalculateService calculateService;

    @RequestMapping("/test")
    public String getHello() {
        String testList = testService.getList("code","name");
        String calculateResult = calculateService.getResult("测试");
        return (testList + "," +calculateResult);
    }


}

 成功注入到spring容器中,并实现调用,那么接下来我们来看spring如何整合mybatis,原理是一样的。

从 MapperScan 入手我们可以看到用的是

MapperScannerRegistrar.class

this.registerBeanDefinitions(mapperScanAttrs, registry);

 我就挑重点看,其他的直接忽略。

 doScan(String... basePackages) 

 

processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions)

这里做法和我一样,通过扫描包,将接口转换成beanDefinitions定义

 

重点看 getObject,

return this.getSqlSession().getMapper(this.mapperInterface);

这里我们看sqlsession 默认实现类  DefaultSqlSession

这里是用 configuration 获取,因为启动项目的时候,mybatis会将接口放入其中

继续进去看。

从集合中获取 MapperProxyFactory,这里不就是我们的ServiceFactory 

 MapperProxy 就是我们的 ServiceProxy

当我们调用接口就执行这里。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值