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
完整项目列表为


浙公网安备 33010602011771号