在Spring中注入动态代理Bean

发布于:2024-05-05 ⋅ 阅读:(24) ⋅ 点赞:(0)

在Springboot中我们可以通过内置的注解如@Service@Component@Repository来注册bean,也可以在配置类中通过@Bean来注册bean。这些都是Spring内置的注解。

除此之外,还可以用@WebFilter@WebServlet@WebListener注解结合@ServletComponentScan自动注册Bean。但这里的@WebFilter@WebServlet@WebListener并不是Spring的注解,而是Servlet 3+ 的注解。为什么这些注解的类能自动注册为Spring的Bean,其实现原理是什么呢?

如果进入@ServletComponentScan中查看可以发现,该注解上有另外一个注解:@Import(ServletComponentScanRegistrar.class),进一步查看可知:class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar。这里的关键就是ImportBeanDefinitionRegistrar 接口。


ImportBeanDefinitionRegistrar

Spring中最经典的设计就是AOP和IOC,使得Spring框架具有良好的扩展性,而ImportBeanDefinitionRegistrar 就是其中用来扩展的hook之一。

通常情况下,Spring中的bean就是通过XML配置文件,Spring中的注解或配置类来注册的。但有时候,可能需要在运行时根据某些条件动态地注册一些bean,这时就可以使用ImporterBeanDefinitionRegistrar接口来实现此功能。

具体来说,实现了ImporterBeanDefinitionRegistrar 接口的类可以在@Importer注解中被引入,Spring在初始化容器时会调用这个实现类的regisgterBeanDefinitions方法,以便在运行时根据需要需要注册一些额外的bean。

这个接口通常用于一些高级的场景,比如根据运行时环境来动态的注册不同的bean,或者根据某些外部配置来决定是否注册某些bean等。通过这种方式使得Spring应用程序的配置更加灵活和动态化。

动态注册Bean

下面通过ImportBeanDefinitionRegistrar 来动态注册Bean。

首先将@ServletComponentScan 抄过来改一下名字:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MetaAutoConfigureRegistrar.class})
public @interface MetaComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};
    @AliasFor("value")
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
}

然后实现自定义注册器:

public class MetaComponentScanRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    private ResourceLoader resourceLoader;

    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metaData, BeanDefinitionRegistry registry) {
        MetaBeanDefinitionScanner scanner = new MetaBeanDefinitionScanner(registry, this.environment,
                this.resourceLoader);
        Set<String> packagesToScan = this.getBasePackages(metaData);
        scanner.scan(packagesToScan.toArray(new String[]{}));
    }

    private static class MetaBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
        public MetaBeanDefinitionScanner(BeanDefinitionRegistry registry, Environment environment,
                                         ResourceLoader resourceLoader) {
            super(registry, false, environment, resourceLoader);
            registerFilters();
        }

        protected void registerFilters() {
            addIncludeFilter(new AnnotationTypeFilter(Meta.class));
        }
    }

    private Set<String> getBasePackages(AnnotationMetadata metadata) {
        AnnotationAttributes attributes = AnnotationAttributes
                .fromMap(metadata.getAnnotationAttributes(MetaComponentScan.class.getName()));
        String[] basePackages = attributes.getStringArray("basePackages");
        Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
        Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));
        for (Class<?> basePackageClass : basePackageClasses) {
            packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
        }
        if (packagesToScan.isEmpty()) {
            packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
        }
        return packagesToScan;
    }
}

自定义注册器必须实现ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware这三个接口,然后覆写registerBeanDefinitions 方法,该方法在Spring容器初始化的时候被调用。

在该方法中,需要一个扫描器,该扫描器中有一个过滤器,用于过滤自定义的注解类。因此,需要一个自定义注解:

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

所有使用该注解的类都将被扫描器扫到并注册为bean。扫描时需要知道要扫描的路径,通过getBasePackages 方法获取。最后调用ClassPathBeanDefinitionScanner 的scan方法来扫描和注册bean,这部分是Spring中的固有实现。

现在来创建一个通过@Meta注解的类,看一下是否被自动注册为bean:

@Meta
public class DemoBean {
    public  DemoBean() {
        System.out.println("DemoBean register!");
    }
}

启动SpringBootApplication,会发现控制台日志中有如下输出:

DemoBean register!

表明确实调用了DemoBean 的构造方法,自动注册了一个bean。

注入动态代理bean

如果不是在第三方框架中,正常情况下,普通的类完全没必要自定义注册,直接用Spring内置的注解如@Component即可。

那使用自定义注解来动态注册Spring中的bean还有什么使用场景呢?

Mapper注入原理

如果了解Feign或者mybatis的Mapper应该知道,在通过feign调用远程接口或者通过mapper访问数据库时,是不需要实现类的,而是直接通过接口进行调用的。

下面以Mapper为例(mapper-spring:4.3.0)看下是如何实现的。

同样的,首先需要在Springboot的启动类上加上注解@MapperScan,该注解中通过@Importer引入了MapperScannerRegistrar,而这个注册器实现了ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware接口,并覆写了registerBeanDefinitions方法。在该方法中,调用了ClassPathBeanDefinitionScanner的子类ClassPathMapperScannerdoScan方法来对符合条件的包进行扫描并注册bean,其代码如下:

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
        logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
        processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

可以看到,该方法首先调用了父类的doScan方法,也就是Spring类ClassPathBeanDefinitionScanner 中的doScan方法,通过BeanDefinitionReaderUtils来注册bean,代码如下:

public static void registerBeanDefinition(
		BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
		throws BeanDefinitionStoreException {

	// Register bean definition under primary name.
	String beanName = definitionHolder.getBeanName();
	registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

	// Register aliases for bean name, if any.
	String[] aliases = definitionHolder.getAliases();
	if (aliases != null) {
		for (String alias : aliases) {
			registry.registerAlias(beanName, alias);
		}
	}
}

reigstry有三个实现,这里主要看DefaultListableBeanFactory,在该类的registerBeanDefinition方法里,从beanDefinitionMap中根据beanName来获取beanDefinition,如果不存在,就将自定义的beanDefinition放到beanDefinitionMap 中。

调用完父类的doScan方法之后,接下来调用processBeanDefinitions方法对beanDefinitions进行处理。在该方法中,将beanClassmapper接口类变成了MapperFactoryBean,而MapperFactoryBean 实现了FactoryBean接口。这将使得最终生成的bean为代理对象。

当Spring容器启动时,它会扫描应用程序中的所有Bean定义,并实例化那些需要实例化的Bean。如果遇到实现了FactoryBean接口的Bean定义,Spring将会为该Bean创建一个特殊的代理对象,以便在需要时调用FactoryBean的方法来创建实际的Bean实例。

当需要使用由FactoryBean创建的Bean时,Spring将会调用代理对象的getObject()方法来获取实际的Bean实例。有需要的话,Spring还会调用代理对象的getObjectType()方法来确定实际Bean实例的类型。

如果FactoryBean创建的Bean是单例模式,那么Spring将在第一次调用getObject()方法时创建实例,并将其缓存起来。以后每次调用getObject()方法时,都会返回同一个实例。如果FactoryBean创建的Bean不是单例模式,则每次调用getObject()方法时都会创建一个新的实例。

至此,Mapper接口注入到Spring中的过程就比较清晰了。

自定义注入

下面仿照Mapper的实现原理来自定义注解和代理工厂,实现自定义注入动态代理Bean。

同样地,先定义基础注解,通过该注解引入Registrar:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MapperScanRegistrar.class})
public @interface MapperScan {
    @AliasFor("basePackages")
    String[] value() default {};
    @AliasFor("value")
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
}

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

public class MapperScanRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    private ResourceLoader resourceLoader;

    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false, environment) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                return beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation();
            }
        };
        scanner.setResourceLoader(this.resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(Mapper.class));

        Set<String> basePackages = getBasePackages(metadata);
        for (String pkg : basePackages) {
            Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(pkg);
            for (BeanDefinition candidate : beanDefinitions) {
                if (candidate instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {
                    AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                    String className = annotationMetadata.getClassName();
                    Class<?> beanClass = ClassUtils.resolveClassName(className, ClassUtils.getDefaultClassLoader());
                    String beanName = ClassUtils.getShortName(className);
                    BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder
                            .genericBeanDefinition(MapperBeanFactory.class)
                            .addPropertyValue("type", beanClass);
                    registry.registerBeanDefinition(beanName, definitionBuilder.getBeanDefinition());
                }
            }
        }

    }

    private Set<String> getBasePackages(AnnotationMetadata metadata) {
        AnnotationAttributes attributes = AnnotationAttributes
                .fromMap(metadata.getAnnotationAttributes(MapperScan.class.getName()));
        String[] basePackages = attributes.getStringArray("basePackages");
        Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
        Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));
        for (Class<?> basePackageClass : basePackageClasses) {
            packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
        }
        if (packagesToScan.isEmpty()) {
            packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
        }
        return packagesToScan;
    }
}

这里的注册逻辑是重点。

其中Scanner不是继承自ClassPathBeanDefinitionScanner 的,而是与其同级的,需要覆写isCandidateComponent 方法。
ClassPathBeanDefinitionScanner是直接用于扫描Bean并注册的类,它继承了ClassPathScanningCandidateComponentProvider,并添加了注册Bean定义的功能。
ClassPathScanningCandidateComponentProvider是扫描候选组件的provider,它负责识别符合条件的类,但不负责注册这些类。换句话说,注册Bean定义的功能需要自己实现。

注册Bean定义的代码如下:

if (candidate instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {
    AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
    String className = annotationMetadata.getClassName();
    Class<?> beanClass = ClassUtils.resolveClassName(className, ClassUtils.getDefaultClassLoader());
    String beanName = ClassUtils.getShortName(className);
    BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder
            .genericBeanDefinition(MapperBeanFactory.class)
            .addPropertyValue("type", beanClass);
    registry.registerBeanDefinition(beanName, definitionBuilder.getBeanDefinition());
}

先获取bean定义的元数据,这其中包含bean的类名,可以借此通过反射来获取类对象。
然后更新bean定义,主要是更新beanClass,将其由原始的接口类更改为MapperBeanFactory。同时,还添加了一个type字段,值为原始的接口类。这样实例化bean时就能生成代理对象了,且代理对象的类型为接口类。

最终看下MapperBeanFactory的实现:

public class MapperBeanFactory<T> implements FactoryBean<T> {

    private Class<T> type;

    public MapperBeanFactory() {
    }

    public MapperBeanFactory(Class<T> type) {
        this.type = type;
    }

    @Override
    public Class<T> getObjectType() {
        return type;
    }

    @Override
    public T getObject() {
        return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, (proxy, method, args) -> {
            System.out.printf("Class %s, execute %s method, parameters=%s%n",
                    method.getDeclaringClass().getName(), method.getName(), args[0]);
            return switch (method.getName()) {
                case "sayHello" -> "hello, " + args[0];
                case "sayHi" -> "hi, " + args[0];
                default -> "hello, world!";
            };
        });
    }

    public void setType(Class<T> type) {
        this.type = type;
    }
}

这里的setType方法是必须的,添加的"type"属性就是通过此set方法设置进来的。getObject方法用于生成实际的代理对象,具体是由Proxy.newProxyInstance来生成的。该方法需要三个参数,分别是: 代理类的加载器,代理类要实现的接口列表,代理类handler(InvocationHandler接口的实现类)。其中,第三个参数是一个匿名类对象(这里用lambda表达式进行了简化),该匿名类实现了InvocationHandler 接口,并覆写了invoke代理方法。在代理方法中,根据原始调用方法的不同返回不同的值。

接下来看一下Mapper注解的接口和接口controller:

@Mapper
public interface UserMapper {

    String sayHello(String userName);

    String sayHi(String userName);
}

@RestController
@RequestMapping("/sample")
public class HelloController {

    @Resource
    private UserMapper userMapper;

    @RequestMapping("/hello")
    public String sayHello(@RequestParam String userName) {
        return userMapper.sayHello(userName);
    }

    @RequestMapping("/hi")
    public String sayHi(@RequestParam String userName) {
        return userMapper.sayHi(userName);
    }
}

当系统启动后,访问http://localhost:8080/sample/hello?userName=testhttp://localhost:8080/sample/hi?userName=test会返回不同的结果。
这里UserMapper接口中的方法并没有实现,真正的实现逻辑是在代理方法中根据方法名做的。

可以做一下合理的推测,除了Mapper之外,Spring Data JPA中的接口访问数据库的具体逻辑,也是在代理方法中实现的。

参考资料

[1]. https://blog.csdn.net/u011972171/article/details/80295778
[2]. https://blog.csdn.net/zxd1435513775/article/details/121104087
[3]. https://www.cnblogs.com/kukuxjx/p/17505609.html
[4]. https://www.cnblogs.com/zuxp/p/15504619.html


网站公告

今日签到

点亮在社区的每一天
去签到