2020-06-25_Mybatis和Spring整合高级系列知

2020-06-26  本文已影响0人  kikop

Mybatis和Spring整合高级系列知识学习之factoryBeanByParseAnnotation8

1概述

我们知道MyBatis的Mapper Bean扫描注入是通过MapperScan注解扫描basePackages完成的,Java代码配置的方式如下:

Java代码方式:


@Configuration

// 本质:扫描 service、bean、controller, 转成 BeanDefinition类,然后交给 spring管理

@ComponentScan("com.kikop.myspringstudy.mybatislinkspring1")

// mybatis 自带的生成动态代理类

// mybatis会扫描 classpath*:com/kikop/myspringstudy/mycommon/mapper/**/*.class

@MapperScan("com.kikop.myspringstudy.mycommon.mapper")

public class AppConfig {

本节来模仿MapperScan注解(存在mybatis-spring包中)方式手动实现注解及对应的参数动态解析。通过扫描mapper包名, 完成多个mapper bean的注入。

1.1 MapperScan核心类MapperScannerRegistrar

通过如下配置,spring会扫描classpath* com.kikop.myspringstudy.mycommon.mapper的所有java类,包括子目录,排除package-info.java。

// mybatis 自带的生成动态代理类

// mybatis会扫描 classpath:com/kikop/myspringstudy/mycommon/mapper//.class

@MapperScan("com.kikop.myspringstudy.mycommon.mapper")

1.2 SpringClassUtils

spring-core-5.1.6.RELEASE-sources.jar!\org\springframework\util\ClassUtils.java


// 取 className最后的名称

public static String getShortName(String className) {     Assert.hasLength(className, "Class name must not be empty");     int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);     int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);     if (nameEndIndex == -1) {        nameEndIndex = className.length();     }     String shortName = className.substring(lastDotIndex + 1, nameEndIndex);     shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);     return shortName;  }

// 首字母小写(URL except)

protected String buildDefaultBeanName(BeanDefinition definition) {

String beanClassName = definition.getBeanClassName();

Assert.state(beanClassName != null, "No bean class name set");

String shortClassName = ClassUtils.getShortName(beanClassName);

return Introspector.decapitalize(shortClassName);

}

2 多个Bean注入(扫描mapper类目录)

2.1 config


package com.kikop.myspringstudy.mybatislinkspring8.config;

import com.kikop.myspringstudy.mybatislinkspring8.annotations.MyMapperScan2;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

/**

 * @author kikop

 * @version 1.0

 * @project Name: javawebinaction

 * @file Name: AppConfig

 * @desc 功能描述

 * @date 2020/6/22

 * @time 8:43

 * @by IDE: IntelliJ IDEA

 */

@Configuration

// 本质:扫描 service、bean、controller, 转成 BeanDefinition类,然后交给 spring管理

@ComponentScan("com.kikop.myspringstudy.mybatislinkspring8")

// mybatis 自带的生成动态代理类

//@MapperScan("com.kikop.myspringstudy.mycommon.mapper")

//参数传给:AnnotationMetadata

@MyMapperScan2("com.kikop.myspringstudy.mycommon.mapperbak")

public class AppConfig {

    @Bean

    public DataSource dataSource() {

        // 基于 Spring-jdbc

        DriverManagerDataSource drivermanagerDataSource = new DriverManagerDataSource();

        drivermanagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");

        drivermanagerDataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?userUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true");

        drivermanagerDataSource.setUsername("root");

        drivermanagerDataSource.setPassword("123456");

        return drivermanagerDataSource;

    }

    @Bean

    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        sqlSessionFactoryBean.setDataSource(dataSource);

        return sqlSessionFactoryBean;

    }

}

2.2 service


package com.kikop.myspringstudy.mybatislinkspring1.service;

import com.kikop.myspringstudy.mycommon.mapper.UsersMapper;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import java.util.List;

import java.util.Map;

/**

 * @author kikop

 * @version 1.0

 * @project Name: javawebinaction

 * @file Name: UsersService

 * @desc 功能描述

 * @date 2020/6/22

 * @time 9:16

 * @by IDE: IntelliJ IDEA

 */

@Service // appconfig 就可以扫描到

public class UsersService {

    /**

     * 这里注入是关键

     */

    @Autowired

    UsersMapper usersMapper;

    public List<Map<String, Object>> list() {

        return  usersMapper.list();

    }

}

2.3 factoryBean


package com.kikop.myspringstudy.mybatislinkspring5.factorybean;//package com.kikop.mybatisaspring.factorybean;

import com.kikop.myspringstudy.mycommon.sqlsession.MySqlSession;

import org.springframework.beans.factory.FactoryBean;

/**

 * @author kikop

 * @version 1.0

 * @project Name: javawebinaction

 * @file Name: MyFactoryBean

 * @desc 功能描述 2个Bean

 * @date 2020/6/23

 * @time 23:01

 * @by IDE: IntelliJ IDEA

 */

//注意:

//1.属性注入时,不能加 @Component,@Service 主要是无法实例化 mapperInterface属性。

//2.无法自定义一个bean名称

// @Component

public class MySetterEnableFactoryBean implements FactoryBean {

    private Class mapperInterface;

    /**

     * setter 属性注入

     *

     * @param mapperInterface

     */

    public void setMapperInterface(Class mapperInterface) {

        this.mapperInterface = mapperInterface;

    }

    @Override

    public Object getObject() throws Exception {

        Object usersMapper = MySqlSession.getMapper(mapperInterface);

        return usersMapper;

    }

    @Override

    public Class<?> getObjectType() {

        return mapperInterface;

    }

}

2.4 annotation


package com.kikop.myspringstudy.mybatislinkspring8.annotations;

import com.kikop.myspringstudy.mybatislinkspring8.beandef.MyImportBeanDefinitionRegistrar;

import org.springframework.context.annotation.Import;

import java.lang.annotation.Documented;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

/**

 * @author kikop

 * @version 1.0

 * @project Name: javawebinaction

 * @file Name: MyMapperScan

 * @desc 功能描述

 * @date 2020/6/25

 * @time 17:47

 * @by IDE: IntelliJ IDEA

 */

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Import({MyImportBeanDefinitionRegistrar.class})

public @interface MyMapperScan2 {

    String value() ;

    int type() default 0;

}

2.5 beandef


package com.kikop.myspringstudy.mybatislinkspring8.beandef;

import com.kikop.myspringstudy.mybatislinkspring8.factorybean.MySetterEnableFactoryBean;

import org.springframework.beans.factory.support.AbstractBeanDefinition;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;

import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;

import org.springframework.core.type.AnnotationMetadata;

import org.springframework.core.type.StandardAnnotationMetadata;

import java.io.File;

import java.io.IOException;

import java.net.URL;

import java.util.*;

/**

 * @author kikop

 * @version 1.0

 * @project Name: javawebinaction

 * @file Name: MyImportBeanDefinitionRegistrar

 * @desc 功能描述

 * @date 2020/6/24

 * @time 17:17

 * @by IDE: IntelliJ IDEA

 */

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    // 1.获取所有的类名,such as :com.kikop.myspringstudy.mycommon.mapper.UsersMapper

    private List<String> clsNames = new ArrayList<String>();

    /**

     * Get a list of {@link URL}s from the context classloader for all the resources found at the

     * specified path.

     *

     * @param path The resource path.

     * @return A list of {@link URL}s, as returned by {@link ClassLoader#getResources(String)}.

     * @throws IOException If I/O errors occur

     */

    private List<URL> getResources(String path) throws IOException {

        return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));

    }

    /**

     * 扫描包路径,提取相关的类

     * .class > clsNames

     * 思路:

     * com.kikop.myspringstudy.mycommon.mapper 转成文件目录

     * 并遍历放入到list中

     * XMLConfigBuilder.java

     * configuration.addMappers(mapperPackage);

     *

     * @param scanPackages com.kikop.myspringstudy.mycommon.mapper

     */

    private void doScanner(String scanPackages) throws IOException {

        //: com.kikop.myspringstudy.mycommon.mapper--> com/kikop/myspringstudy/mycommon/mapper

        String slatingBarPackages = scanPackages.replaceAll("\\.", "/");

        List<URL> currentMapperDirectory = getResources(slatingBarPackages);

        if (currentMapperDirectory == null || currentMapperDirectory.size() == 0) {

            return;

        }

//        URL webUrl = this.getClass().getClassLoader().getResource("/" + scanPackages.replaceAll("\\.", "/"));

        // file:/E:/MyWorkspace/javawebinaction/target/classes/com/kikop/myspringstudy/mycommon/mapper

        URL url = currentMapperDirectory.get(0);

        File classPathDir = new File(url.getFile());

        for (File file : classPathDir.listFiles()) {

            if (file.isDirectory()) {

                doScanner(scanPackages + "." + file.getName());

            } else {

                if (!file.getName().endsWith(".class")) { // 只扫描:.class,排除可能的.xml

                    continue;

                }

                String clazzName = (scanPackages + "." + file.getName().replace(".class", ""));

                clsNames.add(clazzName);

            }

        }

    }

    /**

     * 首字母小写

     *

     * @param simpleName

     * @return

     */

    private String toLowerFirstCase(String simpleName) {

        char[] chars = simpleName.toCharArray();

        chars[0] += 32; // 首字母 ascii加32  A 65;a:97

        return String.valueOf(chars);

    }

    @Override

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        if (importingClassMetadata instanceof StandardAnnotationMetadata) {

            Set<String> annotationTypes = importingClassMetadata.getAnnotationTypes();

            for (String annotation : annotationTypes) {

                //  获取 appconfig 中 自定义注解 MyMapperScan2的 value 值(com.kikop.myspringstudy.mycommon.mapper)

                if (annotation.equalsIgnoreCase("com.kikop.myspringstudy.mybatislinkspring8.annotations.MyMapperScan2")) {

                    Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(annotation);

//                    for (Map.Entry<String, Object> entries : annotationAttributes.entrySet()) {

//                        System.out.println(String.format("[key]:%s,[value]:%s", entries.getKey(), entries.getValue()));

//                    }

                    if (annotationAttributes.containsKey("value")) {  // 扫描 所有 com.kikop.myspringstudy.mycommon.mapper

                        Object scanPackageName = annotationAttributes.get("value");

                        try {

                            doScanner((String) scanPackageName);

                            if (clsNames.isEmpty()) {

                                return;

                            }

                            for (String strClsName : clsNames) { // 根据包名 annotationValue(com.kikop.myspringstudy.mycommon.mapper):循环遍历里的所有 classPath中 Mapper接口

                                // 1.根据 MySetterEnableFactoryBean构建一个 BeanDefinitionBuilder,与 Bean的关系一对一

                                BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MySetterEnableFactoryBean.class);

                                // 2.获取 beanDefinition

                                // 该类 MySetterEnableFactoryBean 描述了 bean的所有信息

                                AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();

                                // 3.给自定义的bean MySetterEnableFactoryBean

                                // 设定自定义Property 参数mapperInterface并放到 ArrayList

                                // 3.1.设置属性 mapperInterface

                                // strClsName = "com.kikop.myspringstudy.mycommon.mapper.UsersMapper";

                                beanDefinition.getPropertyValues().add("mapperInterface", strClsName);

                                // 3.2.设置 beanName

                                // String strBeanName = "usersMapper"; // 默认驼峰表示, 首字母小写

                                String strBeanName = strClsName.substring(strClsName.lastIndexOf(".") + 1);

                                strBeanName = toLowerFirstCase(strBeanName);

                                // 3.3.将构建好的 beanDefinition 放到 beanDefinitionMap中

                                registry.registerBeanDefinition(strBeanName, beanDefinition);

                                System.out.println(String.format("[strClsName]:%s,[strBeanName]:%s", strClsName, strBeanName));

                            }

                        } catch (IOException e) {

                            e.printStackTrace();

                        }

                    }

                }

            }

        }

    }

}

2.6 test


package com.kikop.myspringstudy.mybatislinkspring8.test;

import com.kikop.myspringstudy.mybatislinkspring8.config.AppConfig;

import com.kikop.myspringstudy.mybatislinkspring8.factorybean.MySetterEnableFactoryBean;

import com.kikop.myspringstudy.mybatislinkspring8.service.UsersService;

import com.kikop.myspringstudy.mycommon.mapper.UsersMapper;

import com.kikop.myspringstudy.mycommon.mapperbak.CountryMapper;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**

 * @author kikop

 * @version 1.0

 * @project Name: javawebinaction

 * @file Name: UsersServiceTest

 * @desc 功能描述

 * @date 2020/6/22

 * @time 9:17

 * @by IDE: IntelliJ IDEA

 */

public class BeanToSpringByFactoryBean2Test {

    /**

     * 模拟mybatis进行代理Mapper的创建

     */

    public static void createProxyMapperClsTest1() {

        // 1.初始化spring容器(通过AppConfig进行 依赖注入、对象创建、service\bean\controller注解的扫描

        // 类似web.xml中配置的ContextLoaderListener

        // 已经完成spring初始化(注意时机)

        AnnotationConfigApplicationContext annotationConfigWebApplicationContext =

                new AnnotationConfigApplicationContext(AppConfig.class);

        System.out.println(annotationConfigWebApplicationContext.getBean(MySetterEnableFactoryBean.class));

//        // 包装类(Srping本身)

        System.out.println(annotationConfigWebApplicationContext.getBean("&usersMapper"));

//        // 代理类(自定义,执行 TosTRING 方法)

        System.out.println(annotationConfigWebApplicationContext.getBean("usersMapper"));

        // call service

        System.out.println("--------------by bean----------------");

        System.out.println(annotationConfigWebApplicationContext.getBean(UsersMapper.class).list());

        System.out.println("--------------by service,注意 appconfig包扫描要修改----------------");

        System.out.println(annotationConfigWebApplicationContext.getBean(UsersService.class).list());

    }

    /**

     * 模拟mybatis进行代理Mapper的创建

     */

    public static void createProxyMapperClsTest2() {

        // 1.初始化spring容器(通过AppConfig进行 依赖注入、对象创建、service\bean\controller注解的扫描

        // 类似web.xml中配置的ContextLoaderListener

        // 已经完成spring初始化(注意时机)

        AnnotationConfigApplicationContext annotationConfigWebApplicationContext =

                new AnnotationConfigApplicationContext(AppConfig.class);

        // System.out.println(annotationConfigWebApplicationContext.getBean(MySetterEnableFactoryBean.class));

//        // 包装类(Srping本身)

        System.out.println(annotationConfigWebApplicationContext.getBean("&countryMapper"));

//        // 代理类(自定义,执行 TosTRING 方法)

        System.out.println(annotationConfigWebApplicationContext.getBean("countryMapper"));

        // call service

        System.out.println("--------------by bean----------------");

        System.out.println(annotationConfigWebApplicationContext.getBean(CountryMapper.class).list());

    }

    public static void main(String[] args) {

        createProxyMapperClsTest2();

    }

}

3框架说明

3.1 项目依赖


Spring core:

Spring-context、spring-webmvc

Spring自带连接池:

Spring-JDBC(对标:c3p0 druid(德鲁伊)

Mybatis core :

Mybatis(v3.5.0)

Spring-mybatis 插件包

Mybatis-spring(v2.0.0)

mysql-connector-java v6.0.6驱动

[图片上传失败...(image-25935f-1593170712556)]

图 1 公共部分

3.2 Users


package com.kikop.myspringstudy.mycommon.model;

/**

 * @author kikop

 * @version 1.0

 * @project Name: mybatis

 * @file Name: User

 * @desc 功能描述

 * @date 2020/6/21

 * @time 16:41

 * @by IDE: IntelliJ IDEA

 */

public class Users {

  public int getId() {

    return id;

  }

  public void setId(int id) {

    this.id = id;

  }

  public String getName() {

    return name;

  }

  public void setName(String name) {

    this.name = name;

  }

  public int getAge() {

    return age;

  }

  public void setAge(int age) {

    this.age = age;

  }

  public String getRemark() {

    return remark;

  }

  public void setRemark(String remark) {

    this.remark = remark;

  }

  private int id;

  private String name;

  private int age;

  private String remark;

}

3.3 UsersMapper


package com.kikop.myspringstudy.mycommon.mapper;

import org.apache.ibatis.annotations.Select;

import java.util.List;

import java.util.Map;

/**

 * @author kikop

 * @version 1.0

 * @project Name: mybatis

 * @file Name: UserMapper(IUserDao)

 * @desc 功能描述

 * @date 2020/6/21

 * @time 16:40

 * @by IDE: IntelliJ IDEA

 */

public interface UsersMapper {

    @Select("select * from users")

    public List<Map<String, Object>> list();

}

3.4 MyInvocationHandler


package com.kikop.myspringstudy.mycommon.handler;

import org.apache.ibatis.annotations.Select;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

/**

 * @author kikop

 * @version 1.0

 * @project Name: javawebinaction

 * @file Name: MyInvocationHandler

 * @desc 功能描述

 * @date 2020/6/22

 * @time 10:03

 * @by IDE: IntelliJ IDEA

 */

public class MyInvocationHandler implements InvocationHandler {

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if (Object.class.equals(method.getDeclaringClass())) {

            return method.invoke(this, args);

        } else {

            Select selectAnnotation = method.getAnnotation(Select.class);

            if (selectAnnotation != null) {

                String strSql = selectAnnotation.value()[0];

                // 模拟执行 jdbc 数据查询,并返回数据

                System.out.println(strSql);

                if (method.getName().equals("toString")) {

                    // proxy.getClass()--> userMapper,返回被代理类的名称

                    return proxy.getClass().getInterfaces()[0].getName();

                }

            }

            return null;

        }

    }

}

3.5 MySqlSession


package com.kikop.myspringstudy.mycommon.sqlsession;

import com.kikop.myspringstudy.mycommon.handler.MyInvocationHandler;

import java.lang.reflect.Proxy;

/**

 * @author kikop

 * @version 1.0

 * @project Name: javawebinaction

 * @file Name: MySqlSession

 * @desc 功能描述 模拟Mapper接口代理类的创建

 * @date 2020/6/22

 * @time 9:57

 * @by IDE: IntelliJ IDEA

 */

public class MySqlSession {

    /**

     * 模拟Mapper接口代理类的创建

     *

     * @return

     */

    public static Object getMapper(Class<?> clazz) {

        // 第1个参数:AppClassLoader

        // 第2个参数:数组,因为java单继承,多实现,所以要定义为数组

        // 第3个参数:invaocationHandler

        Class<?> classArray[]=new Class<?>[]{clazz};

        Object proxy = Proxy.newProxyInstance(MySqlSession.class.getClassLoader(),classArray,

                new MyInvocationHandler());

        return proxy;

    }

}

参考

上一篇下一篇

猜你喜欢

热点阅读