spring基于xml配置构造需要的bean

引言

  某些场景下我们可能需要根据xml配置自定义bean,较为典型的场景是rpc调用,以下为一个完整的示例。(步骤8也包含了通过注解配置自定义rpc调用)

正文

  1.创建一个spring+maven的工程,这儿贴入pom.xml (完整的项目结构可以直接拉到最底下看倒)

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>trade-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.16</version>
        </dependency>
    </dependencies>
</project>

 

  2.定义两个需要调用的抽象rpc示例,代表我们需要调用的订单服务和商品服务

 

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RpcCall {
}

public interface GoodsFacade {
    @RpcCall
    String distribute(String goodsCode);

    @RpcCall
    String goodsDetail(String serialNo);
}

public interface OrderFacade {
    @RpcCall
    String createOrder(String productCode,String memberId);

    @RpcCall
    String orderDetail(String orderNo);
}

   以及一个用的测试类

package org.example.client;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

public class TradeService {

    @Resource
    private GoodsFacade goodsFacade;

    @Resource
    private OrderFacade orderFacade;




    public void call(){
        String res1 = goodsFacade.distribute("PK24649495");
        System.out.println("the result1 is " + res1);

        System.out.println("=======================================");

        String res2 = orderFacade.orderDetail("12345789");
        System.out.println("the result2 is " + res2);
    }

}

 

  3.定义rpc服务调用规范

  文件位置

 

  rpc.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema
            xmlns="http://schema.test.rpc/schema/rpc"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:beans="http://www.springframework.org/schema/beans"
            targetNamespace="http://schema.test.rpc/schema/rpc"
            elementFormDefault="qualified" attributeFormDefault="unqualified">

    <xsd:import namespace="http://www.springframework.org/schema/beans"/>

    <xsd:complexType name="rpcService">
        <xsd:complexContent>
            <xsd:extension base="beans:identifiedType">
                <xsd:attribute name="interface" type="xsd:token" use="required"/>
                <xsd:attribute name="timeout" type="xsd:string"/>
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>

    <xsd:element name="rpcService" type="rpcService"/>
</xsd:schema>

  spring.handlers

http\://schema.test.rpc/schema/rpc=org.example.config.RpcNamespaceHandler

  spring.schemas

http\://schema.test.rpc/schema/rpc.xsd=META-INF/rpc.xsd

 

  4.定义spring的xml

 

spring-main.xml 

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:task="http://www.springframework.org/schema/task"
        xmlns="http://www.springframework.org/schema/beans"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"
        default-autowire="byName">

    <context:component-scan base-package="org.example"/>
    <import resource="spring-rpc.xml"/>
    <import resource="spring-bean.xml"/>
</beans>

spring-rpc.xml 定义我们需要调用的服务  超时时间等(还可以加入其他配置,如序列化方式,注册中心等)(配置可以采用spring的配置 即${}的方式,后续BeanDefinition会识别出配置属性)

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:task="http://www.springframework.org/schema/task"
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:rpc="http://schema.test.rpc/schema/rpc"

        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
        http://schema.test.rpc/schema/rpc http://schema.test.rpc/schema/rpc.xsd "
        default-autowire="byName">

        <rpc:rpcService id="goodsFacade" interface="org.example.client.GoodsFacade" timeout="500"/>
        <rpc:rpcService id="orderFacade" interface="org.example.client.OrderFacade" timeout="800"/>
        
</beans>

spring-bean.xml  定义一个测试用的service

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:task="http://www.springframework.org/schema/task"
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:rpc="http://schema.test.rpc/schema/rpc"

        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
        http://schema.test.rpc/schema/rpc http://schema.test.rpc/schema/rpc.xsd "
        default-autowire="byName">


        <bean class="org.example.client.TradeService" />

</beans>

 

  5.定义xml namespace解析器,以及bean解析工具

 

  RpcNamespaceHandler  xmlnamepsace解析

package org.example.config;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class RpcNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("rpcService", new RpcBeanDefinitionParser());
    }

}

  

  RpcBeanDefinitionParser

package org.example.config;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

public class RpcBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {


    @Override
    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        builder.addPropertyValue("interfaceName", element.getAttribute("interface"));
        builder.addPropertyValue("timeout", element.getAttribute("timeout"));
        try {
            builder.addPropertyValue("clazz", Class.forName(element.getAttribute("interface")));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected Class<RpcInvokerBean> getBeanClass(Element element) {
        return RpcInvokerBean.class;
    }

    @Override
    protected String getBeanClassName(Element element) {
        return RpcInvokerBean.class.getName();
    }
}

  由于我们是要对接口代理,所以RpcInvolerBean需要定义为FactoryBean

package org.example.config;

import org.springframework.beans.factory.FactoryBean;

import java.lang.reflect.Proxy;

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

    private String interfaceName;

    private String timeout;


    private Class<T> clazz;

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

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

    public String getInterfaceName() {
        return interfaceName;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public String getTimeout() {
        return timeout;
    }

    public void setTimeout(String timeout) {
        this.timeout = timeout;
    }

    public Class<T> getClazz() {
        return clazz;
    }

    public void setClazz(Class<T> clazz) {
        this.clazz = clazz;
    }
}

  模拟rpc调用过程 RpcInvokerHandler

package org.example.config;

import org.example.client.RpcCall;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.UUID;

public class RpcInvokerHandler implements InvocationHandler {

    private RpcInvokerBean<?> rpcInvokerBean;

    public RpcInvokerHandler(RpcInvokerBean<?> bean) {
        this.rpcInvokerBean = bean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        boolean rpcCall = method.isAnnotationPresent(RpcCall.class);
        if (!rpcCall){
            return method.invoke(proxy,args);
        }
        System.out.println("the param is : " + Arrays.toString(args));
        String methodName = method.getName();

        System.out.println("this call timeout is "+rpcInvokerBean.getTimeout()+" ; the methodName is " + methodName);

        return UUID.randomUUID().toString();
    }

}

 

  6.定义main测试类

package org.example;

import org.example.client.GoodsFacade;
import org.example.client.TradeService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.Map;

public class Main {
    public static void main(String[] args) {

        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("META-INF/spring/spring-main.xml");
        TradeService service = applicationContext.getBean(TradeService.class);
        service.call();
    }

}

  7.启动main函数可以得到结果

the param is : [PK24649495]
this call timeout is 500 ; the methodName is distribute
the result1 is 4653006f-b34f-4227-8323-64b39d2554ea
=======================================
the param is : [12345789]
this call timeout is 800 ; the methodName is orderDetail
the result2 is 66ba73be-1b2b-404b-a82e-f2bba5491137

   

  8.如果更喜欢注解类型配置处理,案例如下(有点跑题)

 

    例如定义一个退订服务,希望把调用信息定义成注解形式使用

package org.example.client;


@AnnotationRpcService
public interface RefundFacade {

    @AnnotationRpcCall(url = "https://www.baidu.com",timeOut = "${rpc.timeout}")
    String refundGoods(String serialNo);

    @AnnotationRpcCall(url = "https://www.sougou.com",timeOut = "${rpc.timeout}")
    String refundDetail(String serialNo);

}

  新增config.properties以及配置内容

rpc.timeout=600

  将配置导入spring

 <context:property-placeholder location="classpath:META-INF/spring/config.properties"/>

  定义自定义rpc注解

package org.example.client;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.lang.annotation.*;

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

}

 

package org.example.client;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnnotationRpcCall {

    String url() default "";

    int timeOut() default 0;
}

 

  做如下处理即可

  定义扫描注册工具

package org.example.config;

import org.example.client.AnnotationRpcCall;
import org.example.client.AnnotationRpcService;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.*;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Set;

@Configuration
@Import(value = AnnotationRpcInvokerRegister.class)
public class AnnotationRpcInvokerRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private Environment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider componentScanner = new ClassPathScanningCandidateComponentProvider(false,environment){
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                return beanDefinition.getMetadata().isIndependent() && beanDefinition.getMetadata().isInterface();
            }
        };

        componentScanner.addIncludeFilter(new AnnotationTypeFilter(AnnotationRpcService.class));

        String basePackage = "org.example.client";

        Set<BeanDefinition> candidateComponents = componentScanner.findCandidateComponents(basePackage);

        for (BeanDefinition candidateComponent : candidateComponents) {
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(AnnotationRpcInvokerBean.class);
            beanDefinitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            beanDefinitionBuilder.addPropertyValue("clazz",candidateComponent.getBeanClassName());
            beanDefinitionBuilder.setScope("singleton");

            //handle method
            try {
                Class<?> clazz = Class.forName(candidateComponent.getBeanClassName());
                Method[] methods = clazz.getMethods();
                ManagedList<Object> methodList = new ManagedList<Object>();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(AnnotationRpcCall.class)){
                        AnnotationRpcCall methodAnnotation = method.getAnnotation(AnnotationRpcCall.class);
                        BeanDefinitionBuilder methodBeanBuilder = BeanDefinitionBuilder.genericBeanDefinition(AnnotationRpcMethodBean.class);
                        methodBeanBuilder.addPropertyValue("url",methodAnnotation.url());
                        methodBeanBuilder.addPropertyValue("timeOut",methodAnnotation.timeOut());
                        methodBeanBuilder.addPropertyValue("methodName",method.getName());
                        methodList.add(methodBeanBuilder.getBeanDefinition());
                        beanDefinitionBuilder.addPropertyValue("methodBeans",methodList);
                    }
                }

            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }

            BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinitionBuilder.getBeanDefinition(),candidateComponent.getBeanClassName(),new String[]{candidateComponent.getBeanClassName()});
            BeanDefinitionReaderUtils.registerBeanDefinition(holder,registry);

        }

    }


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

 

  定义被注入的类型,依旧定义为FactoryBean

package org.example.config;

import org.springframework.beans.factory.FactoryBean;

import java.lang.reflect.Proxy;
import java.util.List;

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

    private Class<T> clazz;

    private List<AnnotationRpcMethodBean> methodBeans;

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

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

    public Class<T> getClazz() {
        return clazz;
    }

    public void setClazz(Class<T> clazz) {
        this.clazz = clazz;
    }

    public List<AnnotationRpcMethodBean> getMethodBeans() {
        return methodBeans;
    }

    public void setMethodBeans(List<AnnotationRpcMethodBean> methodBeans) {
        this.methodBeans = methodBeans;
    }
}

 

  定义method类

package org.example.config;

public class AnnotationRpcMethodBean {

    private String methodName;

    private String url;

    private String timeOut;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getTimeOut() {
        return timeOut;
    }

    public void setTimeOut(String timeOut) {
        this.timeOut = timeOut;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }
}

 

  定义处理方法(模拟rpc处理)

package org.example.config;

import org.example.client.AnnotationRpcCall;
import org.example.client.RpcCall;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

public class AnnotationRpcInvokerHandler implements InvocationHandler {

    private AnnotationRpcInvokerBean<?> rpcInvokerBean;

    public AnnotationRpcInvokerHandler(AnnotationRpcInvokerBean<?> bean) {
        this.rpcInvokerBean = bean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        List<AnnotationRpcMethodBean> methodBeans = rpcInvokerBean.getMethodBeans();
        for (AnnotationRpcMethodBean methodBean : methodBeans) {
            if (method.getName().equals(methodBean.getMethodName())){
                AnnotationRpcCall rpcCall = AnnotationUtils.getAnnotation(method, AnnotationRpcCall.class);
                if (rpcCall == null){
                    throw new RuntimeException("this method is not support");
                }
                System.out.println("rpc call url is [" + methodBean.getUrl() + "] param is [" + Arrays.toString(args) + "] and timeout is" + methodBean.getTimeOut());
                return UUID.randomUUID().toString();
            }
        }

        throw new RuntimeException("can not find method :" + method.getName());
    }

}

 

 

   测试类增加退订服务

package org.example.client;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

public class TradeService {

    @Resource
    private RefundFacade refundFacade;


    public void call(){

        String res3 = refundFacade.refundGoods("987654321");
        System.out.println("the result3 is " + res3);
    }

}

  输出

rpc call url is [https://www.baidu.com] param is [[987654321]] and timeout is600
the result3 is adf5a9ff-d1a9-4b2a-8000-9c258d69be91

 

  完整项目列表为

 

 

posted @ 2025-04-11 18:08  雨落寒沙  阅读(46)  评论(0)    收藏  举报