当前位置: 代码网 > it编程>编程语言>Java > 在Spring中如何注入动态代理Bean

在Spring中如何注入动态代理Bean

2025年03月26日 Java 我要评论
在spring中注入动态代理bean在springboot中我们可以通过内置的注解如@service,@component,@repository来注册bean,也可以在配置类中通过@bean来注册b

在spring中注入动态代理bean

在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中的接口访问数据库的具体逻辑,也是在代理方法中实现的。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com