1、编写annotation
定义了两个注解@QhyuSanner和@QhyuSelect,QhyuSanner注解定义的就是需要扫描的路径,QhyuSelect注解就是执行sql使用。
-
@QhyuSanner注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface QhyuSanner { // 定义一个扫描接口,为了扫描我定义的接口,同时接口上有我的特殊注解 @AliasFor("path") String value() default ""; @AliasFor("value") String path() default ""; }
-
@QhyuSelect注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface QhyuSelect { String value() default ""; }
2、定义两个Mapper接口
-
定义两个Mapper接口,此处后续不真实的执行sql,而是打印出@QhyuSelect注解中的value值。
public interface GetUserNameMapper { @QhyuSelect("Select 'qhyu'") String getNameInfo(); } public interface QhyuTestMapper { /** * Title:getMessage <br> * Description:通过注解的方式调用方法并且返回 <br> * author:于琦海 <br> * date:2022/5/30 10:29 <br> * @return String */ @QhyuSelect("Select 'User'") String getMessage(); }
3、定义FactoryBean
定义FactoryBean的作用就是想使用其getObject()方法生成代理对象,但是我又不能为每个接口都写一个FactoryBean,那样很蠢,所以使用构造函数的方式来实现。
-
定义InvocationHandler,用来执行我的@QhyuSelect注解内容
@Component public class MethodArgsHandler 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); } QhyuSelect annotation = method.getAnnotation(QhyuSelect.class); // 这里我直接返回,实际上的操作是通过sql进行查询 System.out.println(annotation.value()); return annotation.value(); } }
-
定义MapperFactoryBean对象
@Component public class MapperFactoryBean<T> implements FactoryBean<T> { public MapperFactoryBean() { } private Class<T> target; public MapperFactoryBean(Class<T> target) { this.target = target; } @Override @SuppressWarnings({"unchecked","rawtypes"}) public T getObject() throws Exception { // 在这里产生代理对象 return (T) Proxy.newProxyInstance(target.getClassLoader(), new Class[]{getObjectType()}, new MethodArgsHandler()); } /** 默认返回单例就行 */ @Override public boolean isSingleton() { return true; } @Override public Class<T> getObjectType() { // 直接返回target的类型 return target; } }
4、组装BeanDefinition并注册
这部分就比较核心,首先我必须将我的com.qhyu.cloud.mybatis.mapper包路径下的所有接口进行扫描,然后进行代理对象的产生。一开始的想法是使用类是@mapper注解来获取我要的接口,但是为了方便我打算使用Spring自带的扫描器来处理
-
自定义QhyuConfigSanner继承ClassPathBeanDefinitionScanner
public class QhyuConfigSanner extends ClassPathBeanDefinitionScanner { public QhyuConfigSanner(BeanDefinitionRegistry registry) { super(registry); } /** Spring ClassPath扫描器扫描不包含接口,因为无法生成bean 所以我们这里进行判断修改*/ @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface(); } /** 修改doSacn方法 */ @Override protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages); for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) { // 修改beanDefinition中的BeanClass属性 BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition(); // 拿到beanDeifinition之后需要设置构造函数 beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName()); beanDefinition.setBeanClassName(MapperFactoryBean.class.getName()); } return beanDefinitionHolders; } }
-
定义好扫描器之后我需要把扫描之后的这些beanDefinition又放入到容器去,让Spring自己来给我处理后续的事情,所以打算使用@import注解 和ImportBeanDefinitionRegistrar类来让用户加上了@QhyuSanner注解后自动给我创建好我的代理对象。
public class QhyuImportBeanDefinitionRegist implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 这里就是为了将我mapper包下的接口进行扫描,然后生成BeanDefinition到Spring中进行注册 Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(QhyuSanner.class.getName()); String path = (String) annotationAttributes.get("value"); System.out.println("我的扫描路径为:"+path); QhyuConfigSanner qhyuConfigSanner = new QhyuConfigSanner(registry); qhyuConfigSanner.addIncludeFilter(new TypeFilter() { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return true; } }); qhyuConfigSanner.scan(path); } }
这时我的@qhyuSanner上就得加上@Import(QhyuImportBeanDefinitionRegist.class),如下所示
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(QhyuImportBeanDefinitionRegist.class) public @interface QhyuSanner { // 定义一个扫描接口,为了扫描我定义的接口,同时接口上有我的特殊注解 @AliasFor("path") String value() default ""; @AliasFor("value") String path() default ""; }
5、定义Service,开始调用
-
定义Qhyu对象
@Component public class Qhyu { /** 需要注入qhyuTestMapper的代理对象 */ @Autowired(required = false) QhyuTestMapper qhyuTestMapper; @Autowired(required = false) GetUserNameMapper getUserNameMapper; public String testMapper(){ return qhyuTestMapper.getMessage(); } public String testName(){ return getUserNameMapper.getNameInfo(); } }
-
启动
public class MyBatisAppliction { public static void main(String[] args) { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MybatisConfig.class); Qhyu bean = annotationConfigApplicationContext.getBean(Qhyu.class); bean.testMapper(); bean.testName(); } }
-
结果
> Task :spring-qhyu:MyBatisAppliction.main() 我的扫描路径为:com.qhyu.cloud.mybatis.mapper Select 'User' Select 'qhyu'