学习了Spring源码之后如何自己扩展spring的功能呢?今天就举几个实用的小例子
我们知道通过Feign调用服务时,会根据配置的负载均衡策略调用微服务中的一台机器,这也正是微服务的作用之一。
但是此时我们有一个需求,通过Feign调用时需要调用所有服务下的机器接口,用于刷新服务器上的静态缓存。
通过Spring提供的BeanPostProcessor接口在初始化完成后对bean进行改造。具体代码如下:
//这个类本身也要注册为一个Spring的bean,这样才会被Spring调用
@Component
public class FeignPostProcessor implements BeanPostProcessor {
private Logger logger = LoggerFactory.getLogger(FeignPostProcessor.class.getSimpleName());
/**
* 微服务基于Eureka作为注册中心,由于要调用微服务的所有机器,
* 需要通过该服务获取所有的微服务,用于多机 器调用
*/
@Resource
private DiscoveryClient discoveryClient;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
logger.info("自定义后置处理器接口,beanName:{},beanClass:{}",beanName,bean.getClass());
FeignClient annotation = ExpressFeign.class.getAnnotation(FeignClient.class);
Class<?> fallback = annotation.fallback();
//这里定义好自己需要处理的feign,我这里是ExpressFeign
if(bean instanceof ExpressFeign ){
if(fallback != null && bean.getClass().isAssignableFrom(fallback) ){
logger.info("bean:{},是熔断限流的实现",bean.getClass());
return bean;
}
//这里真正的实现多调用时采用JDK动态代理重新生成bean,重点看InvokeAllFeignInvocationHandler的invoke方法
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ExpressFeign.class},
new InvokeAllFeignInvocationHandler(discoveryClient));
}
return bean;
}
}
public class InvokeAllFeignInvocationHandler implements InvocationHandler {
private static final String EXPRESS_SERVICE_NAME = "express-service";
private DiscoveryClient discoveryClient;
public InvokeAllFeignInvocationHandler(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
List<ServiceInstance> instances = discoveryClient.getInstances(EXPRESS_SERVICE_NAME);
if(CollUtil.isEmpty(instances)){
return null;
}
//这里暂定返回值为String,数组,实际上多接口调用时不应该有返回值,我们只关心是否调用成功即可。
List<String> strings = new ArrayList<>();
for (ServiceInstance instance : instances) {
//这里假设Feign接口的请求都是使用的@RequestMapping注解,其他的注解如:@GetMapping都是一样的。
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
String path = annotation.value()[0];
String forObject = new RestTemplate().getForObject(instance.getUri() + path, String.class,args);
strings.add(forObject);
}
return Arrays.toString(strings.toArray());
}
}
我们大多数人都在Spring环境中用过Mybatis的功能,通常的使用方式是直接注入Mapper接口即可实现数据库的操作,例如:
public interface UserMapper{
List<User> selectAll();
}
public class TestService{
@Resource
private UserMapper userMapper;
public void test(){
userMapper.selectAll();
}
}
上面代码中的UserMapper是一个接口,我们并没有为这个接口提供实现类,那么为什么能使用@Resrouce注解注入到Spring中实现数据库的操作呢?这就是我们本次学习需要了解的内容:如何将Mybatis的Mapper接口注入到Spring Ioc容器中。
除了常规的Spring相关的依赖,Mysql驱动外,我们只导入Mybatis包。
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.3version>
dependency>
<configuration>
<properties resource="config.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<mappers>
<package name="org.chenglj.mybatis.mapper"/>
mappers>
configuration>
@Configuration
public class DataSourceConfig {
/**
* 配置Mybatis的SqlSessionFactory
* @return
* @throws IOException
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
}
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("org.chenglj.mybatis")
public class MybatisSpringDemo {
}
public interface UserMapper2 {
//这里先使用Mybatis提供的注解方式实现sql语句,后面的版本调整为我们最常用的xml文件
@Select("select * from user limit 10;")
List<User> selectAll();
}
//这里的@Component注解一定要加上,表示这个类本身也会注册到springIOC容器中,这样Spring才会回调getObject方法注册目标bean对象。
@Component
public class UserMapperFactoryBean implements FactoryBean<UserMapper2> {
@Resource
private SqlSessionFactory sqlSessionFactory;
public UserMapperFactoryBean(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public UserMapper2 getObject() throws Exception {
return sqlSessionFactory.openSession().getMapper(UserMapper2.class);
}
@Override
public Class<?> getObjectType() {
return UserMapper2.class;
}
}
public class MybatisSpringDemoTest {
private static AnnotationConfigApplicationContext context ;
static {
context = new AnnotationConfigApplicationContext();
context.register(MybatisSpringDemo.class);
}
@Test
public void testV1(){
context.refresh();
UserMapper2 bean = context.getBean(UserMapper2.class);
System.out.println(bean.selectAll());
}
}
对V1.0中我们注册UserMapper2.java的方式采用Spring的
FactoryBean
有一个弊端,当我们项目中有多张表时就需要创建多个Factorybean的实现吗?显然这种方式不符合实际需求?(但却能帮我们很好理解Spring)。
public class MapperFactoryBean implements FactoryBean {
//重点是这里,我们不再写死一个UserMapper2.java文件
private Class<?> mapperClass;
public MapperFactoryBean(Class<?> mapperClass) {
this.mapperClass = mapperClass;
}
@Resource
private SqlSessionFactory sqlSessionFactory;
@Override
public Object getObject() throws Exception {
return sqlSessionFactory.openSession().getMapper(mapperClass);
}
@Override
public Class<?> getObjectType() {
return mapperClass;
}
}
有什么用我们等下说到
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.7.13version>
dependency>
public class MybatisSpringDemoTest {
private static AnnotationConfigApplicationContext context ;
static {
context = new AnnotationConfigApplicationContext();
context.register(MybatisSpringDemo.class);
}
@Test
public void testV2(){
//1.定义扫描的Mybaits的Mapper接口路径
String mapperPackage = "org.chenglj.mybatis.mapper";
//2.使用hutool工具包、扫描包下所有的class文件。
Set<Class<?>> mapperClasses = ClassUtil.scanPackage(mapperPackage);
for (Class<?> mapperClazz : mapperClasses) {
//3. 循环创建BeanDefinition对象
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(MapperFactoryBean.class)
.getBeanDefinition();
//这里通过MapperFactoryBean的有参构造器方法将class文件传入
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapperClazz);
//4.为Spring上下文循环注册BeanDefition对象
context.registerBeanDefinition(mapperClazz.getSimpleName(),beanDefinition);
}
context.refresh();
//此时我们在UserMapper2的同级包下创建多个Mapper接口均可被注册到Spring中。
UserMapper2 userMapper2Bean = context.getBean(UserMapper2.class);
System.out.println(userMapper2Bean.selectAll());
StudentMapper studentMapper = context.getBean(StudentMapper.class);
System.out.println(studentMapper.getNameByPrimaryKey(2));
}
上面的V2.0版本中有一个问题,我们扫描包的路径(代码:
String mapperPackage = "org.chenglj.mybatis.mapper";
)是在代码中写死的,这样作为公共组件显示是不符合需求的。
public class MybatisMapperImportBeanDefinitionRegistrar2 implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
Map<String, Object> annotationAttributes = annotationMetadata
.getAnnotationAttributes(MybatisMapperScan.class.getName());
String mapperPackage = (String)annotationAttributes.get("value");
Assert.notNull(mapperPackage,"@MybatisMapperScan value can not be null");
//这里的代码就是将版本V2.0中的test代码移动到这里
Set<Class<?>> mapperClasses = ClassUtil.scanPackage(mapperPackage);
for (Class<?> mapperClazz : mapperClasses) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(MapperFactoryBean.class)
.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapperClazz);
beanDefinitionRegistry.registerBeanDefinition(mapperClazz.getSimpleName(),beanDefinition);
}
}
}
//这里这里的Class是我们刚刚写的
@Import(MybatisMapperImportBeanDefinitionRegistrar2.class)
@Documented
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MybatisMapperScan {
String value();
}
@ComponentScan("org.chenglj.mybatis")
@MybatisMapperScan("org.chenglj.mybatis.mapper")
public class MybatisSpringImportDemo2 {
}
public class MybatisSpringDemoTest {
private static AnnotationConfigApplicationContext context ;
static {
context = new AnnotationConfigApplicationContext();
context.register(MybatisSpringDemo.class);
}
@Test
public void testV3(){
context = new AnnotationConfigApplicationContext();
context.register(MybatisSpringImportDemo2.class);
context.refresh();
UserMapper2 userMapper2Bean = context.getBean(UserMapper2.class);
System.out.println(userMapper2Bean.selectAll());
StudentMapper studentMapper = context.getBean(StudentMapper.class);
System.out.println(studentMapper.getNameByPrimaryKey(2));
}
}
在V2.0和V3.0的整合中我们使用了hutool的工具包、通过扫描指定包下的所有class文件实现了Spring beanDefinition的注册,其实Spring本身有比较成熟的包扫描实现了
public class MybatisMapperScanner extends ClassPathBeanDefinitionScanner {
private static Logger logger = LoggerFactory.getLogger(MybatisMapperScanner.class);
public MybatisMapperScanner(BeanDefinitionRegistry registry) {
super(registry);
}
/*@Override
public int scan(String... basePackages) {
int scan = super.scan(basePackages);
logger.info("scan packages {} nums is {}",basePackages,scan);
return scan;
}*/
/*
方案一、在scan结束后循环修改BeanDefinition中的集合内容
@Override
protected Set doScan(String... basePackages) {
Set beanDefinitionHolders = super.doScan(basePackages);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClassName(MapperFactoryBean.class.getName());
}
return beanDefinitionHolders;
}
*/
/**
* 重写2个isCandidateComponent方法 是否候选的组件,我们只需要扫描接口,默认接口不会被扫描的
* @param metadataReader
* @return
* @throws IOException
*/
@Override
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
return metadataReader.getClassMetadata().isInterface();
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean anInterface = beanDefinition.getMetadata().isInterface();
return anInterface;
}
/**
* 方案二:重新注册beanDefinition方法,此处修改下beanDefinition的ClassName为我们自定义的MapperFactoryBean即可
* @param definitionHolder
* @param registry
*/
/*@Override
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = definitionHolder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClassName(MapperFactoryBean.class.getName());
super.registerBeanDefinition(definitionHolder, registry);
}*/
/**
* 重新方案三 推荐
* @param beanDefinition
* @param beanName
*/
@Override
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
//BeanClass和BeanClassName二则设置其一即可,Spring会优先使用beanClassName,注意BeanClassName一定要是Class的全称
beanDefinition.setBeanClass(MapperFactoryBean.class);
beanDefinition.setBeanClassName(MapperFactoryBean.class.getName());
super.postProcessBeanDefinition(beanDefinition, beanName);
}
}
public class MybatisMapperImportBeanDefinitionRegistrarV4 implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
Map<String, Object> annotationAttributes = annotationMetadata
.getAnnotationAttributes(MybatisMapperScanV4.class.getName());
String mapperPackage = (String)annotationAttributes.get("value");
Assert.notNull(mapperPackage,"@MybatisMapperScan value can not be null");
ClassPathBeanDefinitionScanner mapperScan = new MybatisMapperScanner(beanDefinitionRegistry);
// 【重点】我们只需要调用scan方法即可。
mapperScan.scan(mapperPackage);
}
}
@Import(MybatisMapperImportBeanDefinitionRegistrarV4.class)
@Documented
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MybatisMapperScanV4 {
String value();
}
public class MybatisSpringDemoTest {
private static AnnotationConfigApplicationContext context ;
static {
context = new AnnotationConfigApplicationContext();
context.register(MybatisSpringDemo.class);
}
@Test
public void testV4(){
context = new AnnotationConfigApplicationContext();
context.register(MybatisSpringImportDemoV4.class);
context.refresh();
UserMapper2 userMapper2Bean = context.getBean(UserMapper2.class);
System.out.println(userMapper2Bean.selectAll());
StudentMapper studentMapper = context.getBean(StudentMapper.class);
System.out.println(studentMapper.getNameByPrimaryKey(2));
}
}
上述的整合mybatis的完整源码均在github:https://github.com/jurnea/my-batis
- V1.0分支:mybatis-spring
- V2.0分支:mybatis-spring-v2
- V3.0分支:mybatis-spring-v3
- V4.0分支:mybatis-spring-v4