spingboot自定义扫描器,扫描自定义注解并添加到IOC容器中

本文介绍了如何使用自定义注解在接口上标记域名和请求路径,然后通过扫描机制创建代理类,模拟FeignClient的功能。主要涉及启动类注解、接口注解、扫描类、工厂类和代理类的实现。

1.概要

本文示例是扫描接口上的自定义注解,并为接口生成相应代理类,模拟FeignClient

2.代码

2.1 启动类上注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(HttpClientBeanDefinitionRegistrar.class)
public @interface EnableHttpClient {
}

2.2 自定义注解(用于接口上)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HttpClient {

    String address();//域名

}

2.3自定义get请求注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GetRequest {

    String value();//请求路径

}

2.4 自定义扫描类

public class HttpScanHandler extends ClassPathBeanDefinitionScanner {

    public HttpScanHandler(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        super(registry, useDefaultFilters);
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {

        addIncludeFilter(new AnnotationTypeFilter(HttpClient.class));
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        if(CollectionUtil.isNotEmpty(beanDefinitionHolders)){
            beanDefinitionHolders.forEach(beanDefinitionHolder -> {
                GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
                try {
                    Class<?> aClass = Class.forName(beanDefinition.getBeanClassName());
                    String address = aClass.getAnnotation(HttpClient.class).address();
                    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass);
                    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(address);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                beanDefinition.setBeanClass(HttpFactory.class);
                beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            });
        }
        return beanDefinitionHolders;
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }
}

2.5 自定义ImportBeanDefinitionRegistrar

public class HttpClientBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        HttpScanHandler scanHandle = new HttpScanHandler(beanDefinitionRegistry,false);
        scanHandle.doScan(ClassUtils.getPackageName(annotationMetadata.getClassName()));
    }

}

2.6 启动类

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableHttpClient
public class SpringRegisterApplication {

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

}

2.7 工厂类

public class HttpFactory implements FactoryBean<Object> {

    private final Class<?> interFace;

    private final String address;

    public HttpFactory(Class<?> interFace,String address) {
        this.interFace = interFace;
        this.address = address;
    }

    @Override
    public Object getObject() {
        return Proxy.newProxyInstance(interFace.getClassLoader(),new Class[]{interFace},new HttpProxyHandler(address));
    }

    @Override
    public Class<?> getObjectType() {
        return interFace;
    }
}

2.8 代理类

@Slf4j
public class HttpProxyHandler implements InvocationHandler {

    private String address;

    public HttpProxyHandler(String address) {
        this.address = address;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String query = method.getAnnotation(GetRequest.class).value();
        Map<String,String> map = new HashMap<>();
        map.put("question","1111");
        String s = HttpClient.doGet(address + query, map, null);
        return null;
    }
}

3.示例

@HttpClient(address = "http://localhost:8080")
public interface HttpInterface {

    @GetRequest("/test/getNluResult")
    void run();

}
@Slf4j
@Service
public class BeanServiceImpl implements BeanService {

    @Resource
    private HttpInterface httpInterface;

    @Override
    public void k() {
        httpInterface.run();
    }
}

简单模拟了FeignClient请求,有问题的小伙伴可以留言

<think>我们正在讨论的是@Mapper注解如何将接口加入到Spring IOC容器中的原理。根据之前的引用内容,我们可以总结如下: 1. 引用[1]提到,当使用@MapperScan注解扫描包时,不需要在每个Mapper接口上添加@Mapper注解,因为扫描会将这些接口注入到IOC容器中。 2. 引用[3]解释了@Mapper和@MapperScan的区别:@Mapper是注解在单个接口上,而@MapperScan是扫描整个包。 3. 引用[4]说明,添加@Mapper注解后,在编译时会生成相应的接口实现类(动态代理类)。 那么,@Mapper注解是如何将接口加入IOC容器的呢? 实际上,@Mapper注解是MyBatis提供的注解。当Spring与MyBatis整合时(通常使用mybatis-spring-boot-starter或类似整合包),有一个关键的处理器:`MapperScannerConfigurer`(或者通过`@MapperScan`注解触发扫描)。 具体过程如下: 1. **@Mapper注解的作用**: 在MyBatis中,@Mapper注解标记一个接口是MyBatis的Mapper接口。但是,仅仅使用MyBatis本身,这个注解不会让Spring管理该接口的实例。需要与Spring整合。 2. **Spring整合MyBatis**: 通过整合包(如mybatis-spring),提供了一个`MapperFactoryBean`。这个工厂Bean能够为Mapper接口创建代理对象。当我们使用`@MapperScan`或者配置`MapperScannerConfigurer`时,它会扫描指定包下带有@Mapper注解的接口,为每个接口注册一个`MapperFactoryBean`到Spring IOC容器中。这样,当我们需要注入一个Mapper时,实际上注入的是这个工厂Bean创建的代理对象。 3. **动态代理**: 当Spring容器启动时,对于扫描到的每个Mapper接口,MyBatis会通过`MapperFactoryBean`创建一个动态代理对象。这个代理对象会拦截接口方法的调用,转换成对SQLSession的操作,从而执行相应的SQL语句。 4. **为什么加了@Mapper就能被注入?** 因为在Spring Boot中,如果使用了`@MapperScan`(或者在配置类中配置了扫描),它会去扫描指定包下的所有带有`@Mapper`注解的接口。然后,通过上述机制,将每个接口的动态代理对象注册为Spring Bean(默认的Bean名称是接口名的首字母小写,也可以自定义)。 5. **如果没有@MapperScan,只有@Mapper注解会怎样?** 如果只使用`@Mapper`注解而没有使用`@MapperScan`或者在配置中指定扫描,那么Spring不会自动扫描这些接口,也就不会创建代理对象注入到容器中。所以,通常我们需要配合使用(比如在启动类上使用`@MapperScan`)或者在Spring Boot的配置中设置自动扫描(如使用`mybatis.mapper-locations`属性指定映射器接口的位置,但实际注入还是需要扫描机制)。 因此,总结原因: - `@Mapper`注解标识一个接口是MyBatis的Mapper接口。 - 通过`@MapperScan`(或`MapperScannerConfigurer`)扫描包,找到这些被`@Mapper`标记的接口(或者也可以扫描没有`@Mapper`注解的接口,但通常我们使用注解来明确标识)。 - 扫描器为每个接口创建一个`MapperFactoryBean`,将其注册为Spring Bean。这样,该接口的代理对象就存在于IOC容器中了。 注意:在Spring Boot中,如果使用了`mybatis-spring-boot-starter`,它会自动配置一个`MapperScannerConfigurer`,扫描启动类所在包及其子包下的所有Mapper接口(这些接口必须被`@Mapper`注解标记)。或者,我们可以显式使用`@MapperScan`指定扫描的包,这样就不需要在每个Mapper接口上写`@Mapper`了(因为扫描会注册所有接口,不管有没有`@Mapper`,但为了明确,通常还是加上或者只用`@MapperScan`)。 引用[1]和[3]也提到了,使用`@MapperScan`可以避免在每个Mapper接口上写`@Mapper`。 所以,核心机制是:**MyBatis通过Spring扫描机制,将Mapper接口的动态代理对象注册为Spring Bean。** 引用说明: - 引用[3]解释了@MapperScan的作用:自动扫描包路径下的类生成动态代理类[^3]。 - 引用[4]说明@Mapper注解在编译后会生成接口实现类(实际是运行时动态代理)[^4]。 </think>### @Mapper注解将接口加入IOC容器的原理分析 #### 1. **核心机制:动态代理与扫描机制** `@Mapper`注解的本质作用是**标识Mapper接口触发动态代理生成**,但其加入IOC容器的核心依赖Spring与MyBatis的整合机制: 1. **注解标记**:`@Mapper`标记在接口上,声明这是一个MyBatis Mapper接口[^4] 2. **代理生成**:MyBatis在运行时为标记`@Mapper`的接口生成**动态代理类**(实现SQL操作)[^3] 3. **IOC注册**:通过`@MapperScan`或自动配置,Spring将这些代理类注册为Bean[^2] > 关键点:单独使用`@Mapper`不足以加入IOC容器,必须配合扫描机制(如`@MapperScan`) #### 2. **实现流程详解** ```mermaid graph LR A[@Mapper注解] --> B[接口声明] B --> C{扫描机制激活} C -->|@MapperScan配置| D[动态代理生成] C -->|SpringBoot自动配置| D D --> E[代理对象注册到IOC] E --> F[通过@Autowired注入使用] ``` #### 3. **与@MapperScan的协作关系** | 特性 | `@Mapper` | `@MapperScan` | |---------------|------------------------------------|-----------------------------------| | **作用范围** | 单个接口 | 整个包路径 | | **IOC注册** | 依赖扫描机制 | 直接触发扫描注册 | | **使用场景** | 小规模项目/分散接口 | 企业级项目/集中管理 | | **优势** | 精确控制 | 避免重复注解[^3] | #### 4. **技术实现原理** - **动态代理生成**: MyBatis通过`MapperProxy`为接口创建代理对象,拦截方法调用转换为SQL操作[^4] ```java public class MapperProxy<T> implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) { // 将方法调用路由到SQLSession执行 } } ``` - **Spring整合**: `MapperScannerConfigurer`扫描标记`@Mapper`的接口,通过`MapperFactoryBean`将代理对象注册为Spring Bean[^2] #### 5. **对比传统方式** - **旧方案**:需在XML中手动配置每个Mapper接口 - **`@Mapper`方案**: - 编译期:接口保留原始定义 - 运行期:动态代理接管方法调用 - 整合期:Spring自动注册Bean[^1] > 本质:**基于接口的声明式编程** + **运行时字节码增强** + **Spring容器集成** --- ### 相关问题 1. 如果只加`@Mapper`不加`@MapperScan`,接口能注入IOC容器吗? 2. MyBatis动态代理和JDK动态代理有何区别? 3. 如何自定义MyBatis的Bean命名策略? [^1]: 依赖`@MapperScan`实现自动扫描注入[^1] [^2]: Spring通过`MapperScannerConfigurer`完成Bean注册[^2] [^3]: `@MapperScan`可替代分散的`@Mapper`注解[^3] [^4]: `@Mapper`触发动态代理实现类生成[^4]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值