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;
}
}