手动实现mybatis代理接口对象

mybatis代理接口实现

        • 1、编写annotation
        • 2、定义两个Mapper接口
        • 3、定义FactoryBean
        • 4、组装BeanDefinition并注册
        • 5、定义Service,开始调用

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 
    * Description:通过注解的方式调用方法并且返回
    * author:于琦海
    * date:2022/5/30 10:29
    * @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'
    

你可能感兴趣的:(源码解析,java,spring,开发语言,mybatis)