模拟Mybatis"无实现类"调用接口

kyaa111 3年前 ⋅ 726 阅读

Mybatis让我们通过接口(interface)就能调用到对应sql, 看起来好像没有实现类, 貌似不符合"常识"

通过这个简易demo, 带你大致了解mybatis背后所做的事

先建立maven工程

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
    </dependencies>

application.yml

server:
  port: 8088

启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

数据对象

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class User {

    private Long id;

    private String username;

    private String password;
}

定义一个接口

UserDAO

public interface UserDAO {
    User getOne();

    List<User> getAll();
}

以下是重要的部分

实现FactoryBean接口

重写getObject方法, 该方法返回的是代理后的对象

实现InvocationHandler接口

重写invoke方法, 编写代理逻辑

public class UserDaoFactoryBean<T> implements FactoryBean<T>, InvocationHandler {


    @Override
    public T getObject() throws Exception {
        @SuppressWarnings("unchecked")
        T t = (T)Proxy.newProxyInstance(UserDAO.class.getClassLoader(), new Class[]{UserDAO.class}, this);
        return t;
    }

    @Override
    public Class<?> getObjectType() {
        return UserDAO.class;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            if ("getOne".equals(method.getName())) {
                return new User(1L, "zhangsan", "123456");
            } else if ("getAll".equals(method.getName())) {
                List<User> userList = new ArrayList<>(2);
                userList.add(new User(1L, "zhangsan", "123456"));
                userList.add(new User(2L, "lisi", "09876"));
                return userList;
            }
            throw new NoSuchMethodException();
        }
    }
}

动态注册bean

实现BeanDefinitionRegistryPostProcessor接口并加入容器

@Component
public class InterfaceBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        BeanDefinitionBuilder definitionBuilder =
                BeanDefinitionBuilder.genericBeanDefinition(UserDAO.class);
        AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
        //将beanClass替换为UserDaoFactoryBean, spring在生成对象时就会获取到UserDaoFactoryBean#getObject生成的代理对象
        beanDefinition.setBeanClass(UserDaoFactoryBean.class);
        registry.registerBeanDefinition("userDAO", beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

}

启动测试

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserDAO userDAO;

    @GetMapping("/getAll")
    public List<User> getAll() {
        return userDAO.getAll();
    }

    @GetMapping("/getOne")
    public User getOne() {
        return userDAO.getOne();
    }
}

访问两个接口

getAll返回

[
    {
        "id": 1,
        "username": "zhangsan",
        "password": "123456"
    },
    {
        "id": 2,
        "username": "lisi",
        "password": "09876"
    }
]

getOne返回

{
    "id": 1,
    "username": "zhangsan",
    "password": "123456"
}

实际上mybatis的生成代理过程更为复杂, 这里只是简略写出, 有兴趣的可以翻翻源码