通过自定义注解,简单模拟Mybatis通过注解查询SQL。
首先,创建自定义注解@MyDao和@MySelect。
MyDao.java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyDao {String[] value() default {};
}
MySelect.java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MySelect {String[] value() default {};
}
创建UserDao,使用上面的自定义注解。
UserDao.java
@MyDao
public interface UserDao {@MySelect("select * from sys_user where id = #{id}")String getNameById(String id);}
MyInterceptor.java
处理@MySelect注解的方法实现。
@Slf4j
@Component
public class MyInterceptor implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MySelect mySelect = method.getAnnotation(MySelect.class);if (mySelect != null) {String sql = mySelect.value()[0];// 解析sql中的参数Map<String, Object> paramMap = new HashMap<>();Parameter[] parameters = method.getParameters();for (int i = 0; i < parameters.length; i++) {paramMap.put(parameters[i].getName(), args[i]);}// 执行sqlString workSql = format(sql, paramMap);log.info("work sql: {}", workSql);}return null;}/*** 替换sql中的参数*/private String format(String str, Map<String, Object> paramMap) {if (MapUtil.isEmpty(paramMap)) {return str;}AtomicReference<String> text = new AtomicReference<>(str);paramMap.forEach((paramName, paramValue) -> {String value = String.valueOf(paramValue);if (paramValue instanceof String) {value = "'" + value + "'";}text.set(StringUtils.replace(text.get(), "#{" + paramName + "}", value));});return text.get();}
}
InterfaceDynamicRegister.java
处理@MyDao的注解的Mapper。
@Configuration
public class InterfaceDynamicRegister implements ImportBeanDefinitionRegistrar {private static final String BASE_PACKAGE = "com.jjh.business.mapper"; // 指定扫描的包路径private static final String RESOURCE_PATTERN = "/**/*.class"; // 指定扫描的文件类型@SneakyThrows@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 扫描所有包含MyDao注解的类List<Class<?>> classes = new ArrayList<>();PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + RESOURCE_PATTERN;Resource[] resources = resolver.getResources(packageSearchPath);SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);scanner.addIncludeFilter(new AnnotationTypeFilter(MyDao.class));for (Resource resource : resources) {if (resource.isReadable()) {MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);String className = metadataReader.getClassMetadata().getClassName();Class<?> clazz = Class.forName(className);classes.add(clazz);}}// 创建接口的实现类并注册到springfor (Class<?> clazz : classes) {// 将代理类实例注册到 Spring 容器中BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);// 调用BeanDefinition的setInstanceSupplier方法,传入一个Lambda表达式,该表达式返回代理对象的实例builder.getBeanDefinition().setInstanceSupplier(() -> {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(clazz);enhancer.setCallback(new MyInterceptor());Object proxyInstance = enhancer.create();return proxyInstance;});registry.registerBeanDefinition(clazz.getSimpleName(), builder.getBeanDefinition());}}
}
TestController.java
调用userDao,查询数据。
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {@Autowiredprivate UserDao userDao;@GetMapping("/do_sql_select")public Object doSqlSelect() {log.info("do_sql_select is do. {}", System.currentTimeMillis());userDao.getNameById("0001");return "Ok";}
}
pom 依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
调用 http://localhost:8080/test/do_sql_select
即可执行userDao方法。