Spring5:使用Spring中的MetadataReader来读取class文件并为接口执行jdk代理

Posted 你是小KS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring5:使用Spring中的MetadataReader来读取class文件并为接口执行jdk代理相关的知识,希望对你有一定的参考价值。

1. 声明

当前内容主要为复习和学习Spring中如何实现MapperScanner的功能(即为接口实现动态代理),模拟mybatis和spring之间的交互操作(个人理解)

内容为:

  1. 扫描指定文件下的所有的接口的class文件
  2. 使用MetadataReader的实现类来读取class文件内容
  3. 为接口创建执行代理

当前内容思路来源:Spring源码解析

2. 基本demo

1. 创建接口

public interface BookMapper {
	List<String> findAllBookNames();
}
public interface UserMapper {
	List<String> findAllUserNames();
}

使用非常简单的接口模拟

2.其他部分

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;

/**
 * 
 * @author hy
 * @createTime 2021-07-11 14:49:55
 * @description 通过扫描文件方式实现为接口添加动态代理
 *
 */
public class Test {
	// 模拟bean工厂
	static Map<Class<?>, Object> beanFactory = new HashMap<Class<?>, Object>();

	public static void main(String[] args) {
		String basePackage = "com.hy.java.proxy";
		String userClassPath = "D:\\\\eclipse-workspace\\\\Java8BasicReStudy\\\\target\\\\classes";
		// 这个basePackage可以转换为下面形式
		String replaceAll = basePackage.replaceAll("\\\\.", "\\\\\\\\");
		// 只扫描接口
		String path = userClassPath + "\\\\" + replaceAll + "\\\\";
		
		// 1.使用文件扫描器方式扫描所在的接口文件
		List<File> findFiles = searchFileUsingRegex(path, "*.class");
		List<Class<?>> findInterfaces = new ArrayList<Class<?>>();
		for (File clazzFile : findFiles) {
			MetadataReader metadataReader;
			try {
				metadataReader = new SimpleMetadataReaderFactory().getMetadataReader(new FileSystemResource(clazzFile));
				ClassMetadata classMetadata = metadataReader.getClassMetadata();
				boolean interface1 = classMetadata.isInterface();
				System.out.println("当前读取的class文件是否为接口:" + interface1);
				String className = classMetadata.getClassName();
				System.out.println("当前类名称:" + className);
				// 只选择接口文件
				if (interface1) {
					Class<?> forName;
					try {
						forName = Class.forName(className);
						findInterfaces.add(forName);
						System.out.println("找到接口====>" + className);
					} catch (ClassNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

				}

			} catch (IOException e) {
				;// ignore
			}

		}

		// 2. 通过获取当前的接口类,并使用代理方式生成和执行
		for (Class<?> interfaceClazz : findInterfaces) {
			Object proxy = generatorProxy(interfaceClazz);
			beanFactory.put(interfaceClazz, proxy);
		}
		// 模拟工厂bean的获取
		UserMapper userMapper = (UserMapper) beanFactory.get(UserMapper.class);

		List<String> findAllUserNames = userMapper.findAllUserNames();
		System.out.println(findAllUserNames);
		BookMapper bookMapper = (BookMapper) beanFactory.get(BookMapper.class);

		List<String> findAllBookNames = bookMapper.findAllBookNames();
		System.out.println(findAllBookNames);
	}

	static List<File> searchFileUsingRegex(String searchDir, String regex) {
		Path sourcePath = Paths.get(searchDir);
		// 开启递归扫描文件
		String findFileRegex = regex;
		try {
			List<File> result = new ArrayList<File>();
			Files.walkFileTree(sourcePath, new SimpleFileVisitor<Path>() {
				@Override
				public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
					try (DirectoryStream<Path> newDirectoryStream = Files.newDirectoryStream(dir, findFileRegex)) {
						newDirectoryStream.forEach(x -> {
							// 加入文件中
							result.add(x.toFile());
						});
					}
					return FileVisitResult.CONTINUE;
				}
			});
			return result;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return new ArrayList<File>(0);
		}
	}

	// 生成代理类的方法
	static Object generatorProxy(Class<?> interfaceClazz) {
		return Proxy.newProxyInstance(interfaceClazz.getClassLoader(), new Class<?>[] { interfaceClazz },
				new BasicInvocationHandler(interfaceClazz));
	}

	// 模拟的执行器
	static class Executor {
		Object executor(String sql) {
			System.out.println("执行sql......" + sql);
			return new ArrayList<String>(0);
		}
	}

	// 接口执行代理
	static class BasicInvocationHandler implements InvocationHandler {
		private final Class<?> interfaceClazz;

		public BasicInvocationHandler(Class<?> interfaceClazz) {
			this.interfaceClazz = interfaceClazz;
		}

		private Map<String, String> sqlMap = new HashMap<String, String>();
		{
			sqlMap.put("com.hy.java.proxy.BookMapper.findAllBookNames", "select bookName from book");
			sqlMap.put("com.hy.java.proxy.UserMapper.findAllUserNames", "select userName from user");
		}
		private Executor executor = new Executor();

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			String name = method.getName();
			String interfaceName = interfaceClazz.getName();
			String id = interfaceName + "." + name;
			String sql = sqlMap.get(id);
			System.out.println(id);
			return executor.executor(sql);
		}

	}
}

这里简化mybatis的sql获取部分

3. 执行结果

在这里插入图片描述
执行成功

以上是关于Spring5:使用Spring中的MetadataReader来读取class文件并为接口执行jdk代理的主要内容,如果未能解决你的问题,请参考以下文章

Spring5源码解析-Spring框架中的事件和监听器

Spring5基于注解的Bean管理简直是Spring中的Spring

Spring5依赖注入(DI)

spring5中的响应式编程框架webflux,到底是什么东西啊?

学习Spring5必知必会~Spring tx

Spring5——IOC操作Bean管理(基于xml文件)