Spring Boot 2 实践记录之 条件装配

时间:2022-07-09 21:12:35

实验项目是想要使用多种数据库访问方式,比如 JPA 和 MyBatis。

项目的 Service 层业务逻辑相同,只是具体实现代码不同,自然是一组接口,两组实现类的架构比较合理。

不过这种模式却有一个问题,如果 Bean 是按实现类装配,则在切换数据库访问方式时,就需要大量的代码修改。如果按接口装配,则会出现歧义(同一接口有两个实现,无法自动装配)。

虽然可以使用「首选Bean」或「限定」装配,但是与直接使用实现类装配一样,切换数据库访问地,仍然要大量修改源码。

经过实验,使用「条件装配」实现了利用配置切换数据库访问方式,不需要修改代码了。

示例:

先定义 Service 接口:

public interface UserServiceInterface {
......
}

再定义 MyBatis 实现类:

@Service
@Conditional(MybatisCondition.class)
public class UserServiceMybatisImpl implements UserServiceInterface {
......
}

注意其中的 @Conditional(MybatisCondition.class),MybatisCondition 类必须实现 org.springframework.context.annotation.Condition 接口,该接口仅有一个 matches 方法,当该方法返回真时,UserServiceMybatisImpl 被装配。

MybatisCondition 的 matches 方法的逻辑被实现为根据配置文件中的 use.data.access.method 属性是否为 mybatis,来决定是否装配 UserServiceMybatisImpl 类:

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata; public class MybatisCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
if (env.getProperty("use.data.access.method").equals("mybatis")) {
return true;
}
return false;
}
}

再定义 SPA 实现类及其 SpaCondition 类,实现方式与 Mybatis 相同,仅在配置项为 spa 时,装配 UserServiceSpaImpl 类:

@Service
@Conditional(SpaCondition.class)
public class UserServiceSpaImpl implements UserServiceInterface {
......
}
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata; public class SpaCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
if (env.getProperty("use.data.access.method").equals("spa")) {
return true;
}
return false;
}
}

定义一个类,自动装配 UserServiceInterface,并打印其类名:

public class Test {
@Autowired
private UserServiceInterface userServiceInterface; public void testUserService() {
System.out.println(userServiceInterface.getClass();
}
}

现在还不能运行,需要添加配置项。

先将配置项配置为 mybatis:

use.data.access.method = mybatis

运行 Test 类的 testUserService 方法,结果为:

UserServiceMybatisImpl

将配置项修改为 spa:

use.data.access.method = spa

运行 Test 类的 testUserService 方法,结果为:

UserServiceSpaImpl

不过,有个小小的缺憾,就是在 Idea 中,如下代码行:

@Autowired
private UserServiceInterface userServiceInterface;

会有错误提示:

Could not autowire. There is more than one bean of 'UserServiceInterface' type.
Beans:
userServiceMybatisImpl   (UserServiceMybatisImpl.java)
userServiceSpaImpl   (UserServiceSpaImpl.java)

由于配置项是在运行时读取的,Idea 在静态语法检查时,实在没办法搞定这个自动装配的判断。

没关系,不影响运行!