欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

在Spring中如何注入動態(tài)代理Bean

 更新時間:2025年03月26日 11:25:04   作者:還是轉(zhuǎn)轉(zhuǎn)  
這篇文章主要介紹了在Spring中如何注入動態(tài)代理Bean問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

在Spring中注入動態(tài)代理Bean

在Springboot中我們可以通過內(nèi)置的注解如@Service@Component,@Repository來注冊bean,也可以在配置類中通過@Bean來注冊bean。這些都是Spring內(nèi)置的注解。

除此之外,還可以用@WebFilter,@WebServlet@WebListener注解結(jié)合@ServletComponentScan自動注冊Bean。但這里的@WebFilter,@WebServlet@WebListener并不是Spring的注解,而是Servlet 3+ 的注解。為什么這些注解的類能自動注冊為Spring的Bean,其實現(xiàn)原理是什么呢?

如果進入@ServletComponentScan中查看可以發(fā)現(xiàn),該注解上有另外一個注解:@Import(ServletComponentScanRegistrar.class),進一步查看可知:class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar。這里的關鍵就是ImportBeanDefinitionRegistrar 接口。

ImportBeanDefinitionRegistrar

Spring中最經(jīng)典的設計就是AOP和IOC,使得Spring框架具有良好的擴展性,而ImportBeanDefinitionRegistrar 就是其中用來擴展的hook之一。

通常情況下,Spring中的bean就是通過XML配置文件,Spring中的注解或配置類來注冊的。但有時候,可能需要在運行時根據(jù)某些條件動態(tài)地注冊一些bean,這時就可以使用ImporterBeanDefinitionRegistrar接口來實現(xiàn)此功能。

具體來說,實現(xiàn)了ImporterBeanDefinitionRegistrar 接口的類可以在@Importer注解中被引入,Spring在初始化容器時會調(diào)用這個實現(xiàn)類的regisgterBeanDefinitions方法,以便在運行時根據(jù)需要需要注冊一些額外的bean。

這個接口通常用于一些高級的場景,比如根據(jù)運行時環(huán)境來動態(tài)的注冊不同的bean,或者根據(jù)某些外部配置來決定是否注冊某些bean等。通過這種方式使得Spring應用程序的配置更加靈活和動態(tài)化。

動態(tài)注冊Bean

下面通過ImportBeanDefinitionRegistrar 來動態(tài)注冊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 {};
}

然后實現(xiàn)自定義注冊器:

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;
    }
}

自定義注冊器必須實現(xiàn)ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware這三個接口,然后覆寫registerBeanDefinitions 方法,該方法在Spring容器初始化的時候被調(diào)用。

在該方法中,需要一個掃描器,該掃描器中有一個過濾器,用于過濾自定義的注解類。因此,需要一個自定義注解:

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

所有使用該注解的類都將被掃描器掃到并注冊為bean。掃描時需要知道要掃描的路徑,通過getBasePackages 方法獲取。最后調(diào)用ClassPathBeanDefinitionScanner 的scan方法來掃描和注冊bean,這部分是Spring中的固有實現(xiàn)。

現(xiàn)在來創(chuàng)建一個通過@Meta注解的類,看一下是否被自動注冊為bean:

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

啟動SpringBootApplication,會發(fā)現(xiàn)控制臺日志中有如下輸出:

DemoBean register!

表明確實調(diào)用了DemoBean 的構(gòu)造方法,自動注冊了一個bean。

注入動態(tài)代理bean

如果不是在第三方框架中,正常情況下,普通的類完全沒必要自定義注冊,直接用Spring內(nèi)置的注解如@Component即可。

那使用自定義注解來動態(tài)注冊Spring中的bean還有什么使用場景呢?

Mapper注入原理

如果了解Feign或者mybatis的Mapper應該知道,在通過feign調(diào)用遠程接口或者通過mapper訪問數(shù)據(jù)庫時,是不需要實現(xiàn)類的,而是直接通過接口進行調(diào)用的。

下面以Mapper為例(mapper-spring:4.3.0)看下是如何實現(xiàn)的。

同樣的,首先需要在Springboot的啟動類上加上注解@MapperScan,該注解中通過@Importer引入了MapperScannerRegistrar,而這個注冊器實現(xiàn)了ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware接口,并覆寫了registerBeanDefinitions方法。在該方法中,調(diào)用了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;
}

可以看到,該方法首先調(diào)用了父類的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有三個實現(xiàn),這里主要看DefaultListableBeanFactory,在該類的registerBeanDefinition方法里,從beanDefinitionMap中根據(jù)beanName來獲取beanDefinition,如果不存在,就將自定義的beanDefinition放到beanDefinitionMap 中。

調(diào)用完父類的doScan方法之后,接下來調(diào)用processBeanDefinitions方法對beanDefinitions進行處理。在該方法中,將beanClassmapper接口類變成了MapperFactoryBean,而MapperFactoryBean 實現(xiàn)了FactoryBean接口。這將使得最終生成的bean為代理對象。

當Spring容器啟動時,它會掃描應用程序中的所有Bean定義,并實例化那些需要實例化的Bean。如果遇到實現(xiàn)了FactoryBean接口的Bean定義,Spring將會為該Bean創(chuàng)建一個特殊的代理對象,以便在需要時調(diào)用FactoryBean的方法來創(chuàng)建實際的Bean實例。

當需要使用由FactoryBean創(chuàng)建的Bean時,Spring將會調(diào)用代理對象的getObject()方法來獲取實際的Bean實例。有需要的話,Spring還會調(diào)用代理對象的getObjectType()方法來確定實際Bean實例的類型。

如果FactoryBean創(chuàng)建的Bean是單例模式,那么Spring將在第一次調(diào)用getObject()方法時創(chuàng)建實例,并將其緩存起來。以后每次調(diào)用getObject()方法時,都會返回同一個實例。如果FactoryBean創(chuàng)建的Bean不是單例模式,則每次調(diào)用getObject()方法時都會創(chuàng)建一個新的實例。

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

自定義注入

下面仿照Mapper的實現(xiàn)原理來自定義注解和代理工廠,實現(xiàn)自定義注入動態(tài)代理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定義的功能需要自己實現(xiàn)。

注冊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定義的元數(shù)據(jù),這其中包含bean的類名,可以借此通過反射來獲取類對象。

然后更新bean定義,主要是更新beanClass,將其由原始的接口類更改為MapperBeanFactory。同時,還添加了一個type字段,值為原始的接口類。這樣實例化bean時就能生成代理對象了,且代理對象的類型為接口類。

最終看下MapperBeanFactory的實現(xiàn):

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來生成的。該方法需要三個參數(shù),分別是: 代理類的加載器,代理類要實現(xiàn)的接口列表,代理類handler(InvocationHandler接口的實現(xiàn)類)。其中,第三個參數(shù)是一個匿名類對象(這里用lambda表達式進行了簡化),該匿名類實現(xiàn)了InvocationHandler 接口,并覆寫了invoke代理方法。在代理方法中,根據(jù)原始調(diào)用方法的不同返回不同的值。

接下來看一下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);
    }
}

當系統(tǒng)啟動后,訪問http://localhost:8080/sample/hello?userName=testhttp://localhost:8080/sample/hi?userName=test會返回不同的結(jié)果。

這里UserMapper接口中的方法并沒有實現(xiàn),真正的實現(xiàn)邏輯是在代理方法中根據(jù)方法名做的。

可以做一下合理的推測,除了Mapper之外,Spring Data JPA中的接口訪問數(shù)據(jù)庫的具體邏輯,也是在代理方法中實現(xiàn)的。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • Java實例化一個抽象類對象的方法教程

    Java實例化一個抽象類對象的方法教程

    大家都知道抽象類無法實例化,就無法創(chuàng)建對象。所以下面這篇文章主要給大家介紹了關于Java實例化一個抽象類對象的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧。
    2017-12-12
  • SpringBoot項目微信云托管入門部署實踐

    SpringBoot項目微信云托管入門部署實踐

    本文主要介紹了SpringBoot項目微信云托管入門部署實踐,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • 詳解Java8 新特性之日期API

    詳解Java8 新特性之日期API

    Java 8 在包java.time下包含了一組全新的時間日期API。下面通過示例給大家講解java8 新特征日期api的相關知識,感興趣的朋友一起看看吧
    2017-07-07
  • Spring中FactoryBean的高級用法實戰(zhàn)教程

    Spring中FactoryBean的高級用法實戰(zhàn)教程

    FactoryBean是Spring框架的高級特性,允許自定義對象的創(chuàng)建過程,適用于復雜初始化邏輯,本文給大家介紹Spring中FactoryBean的高級用法實戰(zhàn),感興趣的朋友跟隨小編一起看看吧
    2024-09-09
  • Spring boot定時任務的原理及動態(tài)創(chuàng)建詳解

    Spring boot定時任務的原理及動態(tài)創(chuàng)建詳解

    這篇文章主要給大家介紹了關于Spring boot定時任務的原理及動態(tài)創(chuàng)建的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-03-03
  • SpringMVC實現(xiàn)用戶登錄全過程

    SpringMVC實現(xiàn)用戶登錄全過程

    這篇文章主要介紹了SpringMVC實現(xiàn)用戶登錄全過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-09-09
  • SpringBoot  jdbctemplate使用方法解析

    SpringBoot jdbctemplate使用方法解析

    這篇文章主要介紹了SpringBoot jdbctemplate使用方法解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-05-05
  • JAVA PDF操作之實現(xiàn)截取N頁和多個PDF合并

    JAVA PDF操作之實現(xiàn)截取N頁和多個PDF合并

    這篇文章主要為大家詳細介紹了java關于PDF的一些操作,例如截取N頁并生成新文件,轉(zhuǎn)圖片以及多個PDF合并,文中的示例代碼講解詳細,感興趣的可以了解下
    2025-01-01
  • MyBatis動態(tài)<if>標簽的使用

    MyBatis動態(tài)<if>標簽的使用

    本文主要介紹了MyBatis動態(tài)<if>標簽的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-05-05
  • java實現(xiàn)科研信息管理系統(tǒng)

    java實現(xiàn)科研信息管理系統(tǒng)

    這篇文章主要為大家詳細介紹了java科研信息管理系統(tǒng),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02

最新評論