`
herman_liu76
  • 浏览: 99421 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

模仿dubbo的与spring无缝集成的RPC演示框架

 
阅读更多
    Dubbo以前也看过些源码,正好同事写了一个基于netty的通讯架构,想自己试试模仿dubbo,使用此通讯架构写一个RPC框架学习一下。根据百度百科定义:Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和Spring框架无缝集成。我的目标仅是实现一个与spring集成的rpc调用框架。

    初看了一下dubbo的解析XML标签,平时工作太忙了,写什么自定义标签解析也很麻烦,就没进展了。直到有一天,因为项目中处理mybatis的mapper接口冲突问题,为了解决问题,看了一下mybatis的一点源码,丰富了对与spring整合的技巧。感觉可以结合dubbo与mybatis与spring的整合技巧,用很少的时间,很少的代码写一个RPC框架了。

  核心功能:客户端动态代理,把接口类,方法,参数类,参数值发过去,服务端按这个找到可用的接口实现,再通过反射调用得到返回值,再传回客户端。



   本文先介绍一下碰到的mybatis冲突问题与解决,再介绍对这个仿dubbo框架的构思,再介绍如何实现客户端与服务端的代码的。最后总结一下,特别是对druid,dubbo学习后的使用体会与实践。由于时间仓促,学艺不精,里面有错误欢迎指正。

一、mybatis使得中碰到的问题

    最近的一个应用中,同一个数据库使用了两套mybatis的dao层,一套是老系统遗留的,一套是新做的公共maven包。由于自动生成,只是包不同,mapper接口名都一样,所以类似com.a.userMapper.java与com.b.userMapper.java这样的接口类共存,结果发现spring启动冲突了。

    先介绍一下IOC容器中规则,从name,或者是别名,或者id,一定只能得到一个bean;
    从type可以得到一个bean,或者是一组bean。
    在加载bean的时候,默认有个校验机制,SpringMVC中bean的加载,是采用类似 键值对(key/value)的映射方式存储的,而当中的(key)键,默认是用类名来作为键的(如果不取别名的话)。这样如果不同包路径下的两个组件(controller/service)重名的话就会触发这个校验机制,抛异常。

protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
if (!this.registry.containsBeanDefinition(beanName)) {
return true;
}
BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
if (originatingDef != null) {
existingDef = originatingDef;
}
if (isCompatible(beanDefinition, existingDef)) {
return false;
}
throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
}


   网上查到的解决方案都是取别名。但是普通的类可以写别名,可不知道如何介入到mapper接口的实现类中呢?人家是动态生成的。后来看了点文章,spring的IOC容器提供了好几个接口,允许你加入你的控制。按介入的先后顺序,总结如下:

   1. BeanNameGenerator:beandefination是IOC中最核心的对象了。在生成beandefination时,可以有BeanNameGenerator介入,提供一个产生名字的规则。解决mybatis的那个冲突就是靠这个了。

   2.BeanFactoryPostProcessor: Spring中BeanFactoryPostProcessor和下面介绍的BeanPostProcessor都是Spring初始化bean时对外暴露的扩展点。PostProcessor部分表示这是一个后置处理接口,相应还有前置处理接口。这里Factory的处理(工厂)肯定比bean产生的早,所以前者早于后者。BeanFactoryPostProcessor确实是在beandefination准备好之后可以介入的,它可以修改beanDefination。

    3.BeanPostProcessor:它是对从beandefination实例化后的Bean做进一步的操作。比如Aware一些外部的东西,容器事件监听啊什么的。

    4.最后还有一个InitializingBean接口,它的afterpropetySet接口我经常用。就是当bean生成了,感知aware外部也好了,一切都准备好了。那可能做些初始化的工作了。比如我之前在此方法中会启动通讯组件,或者微核心框架,并把自己引用的实现了微核心中接口的属性类传进去,以便让这些通讯组件或者微核心框架正常工作。都会放在这个接口的方法中实现,spring会调用这个接口方法的。

    既然知道问题是名字冲突,冲突的同名接口的beanDefination都无法生成,所以只能想办法修改bean名字了,要是可以用包全名肯定不冲突。在spring的配置中,扫描注解对象中可以配置一个BeanNameGenerator,但mabatis中怎么办?只好看源码,从spring-mybatis配置中看到这个类MapperScannerConfigurer,于是点进去看了一个源码,一个熟悉的对象出现了,就是BeanNameGenerator,不正是切入点嘛,于是我在配置的属性中加入自定义的namegenarator类,再用实现BeanFactoryPostProcessor类来打印出所有的beandefination,果然不冲突了,都是包全名。这样冲突解决了,如果一个service中使用上面两个原来冲突的mapper接口,eclipse会提示选择一个,或者写全名。

    处理好冲突,正好想到mybatis也是根据一个提供的接口,动态代理实现数据库操作嘛。dubbo的客户端也是这样啊,为何mybatis不用xml定义标签呢?那正好学习一下mybatis的方式。

    mybatis通过MapperScannerConfigurer扫描配置的mapper接口与xml文件,再加入sqlsession,产生一个个beanDefination,定义里放置的是实现了beanFactory的类,再由这个类的getObject来动态产生了一个实现接口的类。事实上,当beanDefination中如果放了一个beanFactory的类,就会被spring调用产生另外你getObject产生的类,而不是通常new出来的类。就正是可以做手脚的地方。dubbo是通过自定义的标签解析,来产生同样的beanDefination,里面放的也是实现了beanFactory的类。看来是异曲同工,最终目标就是产生这样的beanDefination。好吧,于是我选择mybatis的方式来写自己的miniDubbo了,放弃dubbo的标签定义与解析。

    mybatis的那个配置类MapperScannerConfigurer是实现了postProcessBeanDefinitionRegistry接口,在这个过程中扫描那些DAO与XML并产生一个个beanDefination的。我也这么用了。

    话说前面介绍的几个介入的接口没有它啊,查了一下:
public interface BeanDefinitionRegistryPostProcessor
extends BeanFactoryPostProcessor。好吧,它是BeanFactoryPostProcessor的子接口。介入的位置正好是在beanDefination产生后。


二、客户端的设计

   
    客户端的功能就是找到所有的接口,产生出一个个实现类,实现类功能不是执行方法调用,因为没有真正的功能实现类。只是通过代理的实现类,把获取到的接口的名子,方法的名字,参数类型,参数值等传给服务器,服务器当然找到服务器上实现相同接口的真正的实现类,调用后,得到值,再由服务器传递回来。客户端把传递过来的值返回调用者。
    调用者是透明调用,不知道其后面是远程调用,与本地调用的感觉是一样一样的。都可以是autowired的接口。

    要实现对任何接口的适用,动态代理是少不了的。除非你一个接口写一个静态代理。动态代理中最主要是一个invocation的产生,把在invoke方法中,把参数类型,值都发出去。

设计以下类:
1. Configurer类,它配置在spring-context.xml中,模仿mybatis,配置好接口。咱不学习dubbo的自定义标准产生beanDefination的方式了。它用于加载配置的接口,产生bean定义并放入springIOC中。它 implements BeanDefinitionRegistryPostProcessor。在postProcessBeanDefinitionRegistry方法中new 出一个个Beandefination,里面放置一个 2

	<bean id="configurer" class="dubbura.spring.Configurer">
		<property name="beanName" value="msgSendI"></property>
	</bean>


2.ReferenceBean类,正是上面的beanDefination中放入的类。ReferenceBean<T>  implements FactoryBean接口。在getObject()方法getObject()中返回真正的代理类T。每一个接口产生一个bean定义,每个bean定义中放的就是这样一个类。

  ReferenceBean中的动态代理类怎么生成呢?在ReferenceBean类的afterPropertiesSet()中可以生成啊。

	public void afterPropertiesSet() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("begin get the proxy object");
		InvocationHandler invocationHandler=new InvocationHandler(){
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				// TODO Auto-generated method stub
				System.out.println("method invoke:"+method.getName());
				System.out.println("method invoke:"+args[0].toString());
				Client client=ClientCenter.getAClient("192.168.117.35", 9166, "85f035103434343402655fff888", "h4343888",null);
				MiddleMsg msg = new MiddleMsg(method.getName(), args[0].toString());
				//下面是发同步消息
				MiddleMsg resultMsg=client.sendMsgSync(msg);
				System.out.println("【收到消息:】"+resultMsg.getBody());
				ResultObject result=new ResultObject();
				Map map=new HashMap();
				map.put("data",resultMsg.getBody());
				result.setAttachment(map);
//				return method.invoke(this, args);
				return result;
			}
		};
		invoker=invocationHandler;
		ref =(T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[] { interfaceClass }, invocationHandler);
		System.out.println("has create the Proxy Bean");
	}



3.还要有一个接口。客户端只有这个接口。

4.主要的类就3个,其它还有一个controller,将会autowired这个接口用于测试。




三、服务端的设计

    服务端的功能,首先收到信息的是通讯服务端。我们知道基于netty的tcp通讯框架主要是处理channelHandler。客户端其实把调用的接口,方法,参数类型与值传过来,你也可以简单传过来一些String,关键是找到服务端的spring IOC中的实现类。实现真正调用。想到实现任何的接口方法,调用实现类,那肯定反射调用了。method.invoke()。

    我用的这个通讯组件传的是方法名,参数是jason对象的字符串。服务端根据方法名,找到实现channelHandler(msgHandler)的一个注册进来的类,用这个类处理消息并返回的,返回的意思就是写回netty通道给客户端。是根据方法,并不是根据接口名,找到处理类的。接口名与方法名不是一个级别的。dubbo好象有一个请求结构体,封装了类、方法、参数类型与值,我不搞那么复杂了。
    客户端发过来的消息是有方法名的,再找到处理类。我干脆不管什么方法,都用同一个handler吧,在handler中再找找map存放方法与对应处理类。收到任何消息,就用同一个处理类,它的方法会从消息体内含有的方法名查找这个方法要用的实现类。我如何建的这个map,会放置好方法名对应的接口实现类呢?

    这个接口实现类是spring管理的常见的接口实现类。如何把自己放入另一个map中呢?也不是所有实现类都要放MAP中,有些不需要暴露出来给通讯层又的如何区分呢?所以自己实现注册进MAP并不好,还是学习dubbo方式吧(当然可以用自己的annotation,在beanDefinaton产生后介入处理),从spring配置中拿到信息,这样再从spring中拿到实现类,构建这样一个过渡类吧。想想在mybatis中,每个动态代理类必然要有一个数据库连接,连接是IOC中的对象,所以都要拿到容器中的连接对象。
    要从方法反射调用类,干脆定义一个invocor对象作为过渡类吧,它持有接口实现类的对象,还有访求参数对象数组与参数值对象数组。这样,前面说的MAP中就是方法名与持有实现类的invoker类了。

    总体的实现策略,与客户端一样,也是一个配置类开始的,再产生一个个beanDefination,里面放置一个serviceBean类持有配置参数,再它的afterporpetyset中,产生一个invoker类,并从容器中找到实现类给它,参数也给它,就完整了。再把它们注册到通讯层处理的静态map表中。

   另外通讯层的tcp服务在哪启动呢?要么在配置类的afterpropetySet中,也可以在serviceBean的afterpropetySet方法中启动。反正启动一次,在整个spring容器启动中完成就行了,serviceBean比较多,可能会重复。就选择在配置类config中启动吧。


1.config类,用于产生serviceBean的定义,并启动服务。它配置在spring-context.xml中,模仿mybatis,并设置接口与实现是谁。

<bean id="configurer" class="dubburaver.spring.Configurer">
		<property name="interfaceName" value="MsgSendI"></property>
		<property name="actionName" value="spush_notification"></property>
	</bean>


	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		// TODO Auto-generated method stub
		System.out.println("begin......");
		BeanDefinition beanDefinitionref = registry.getBeanDefinition("msgSendImpl");
		
		int a=registry.getBeanDefinitionCount();
		System.out.println(a);
//		BeanDefinition candidate=null;
		RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(ServiceBean.class);
        beanDefinition.setBeanClassName(ServiceBean.class.getName());
        beanDefinition.getPropertyValues().addPropertyValue("interfaceName", actionName);
//      definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));//mybatis的方式:运行时加载对象
        beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(beanDefinitionref, "msgSendI"));//dubbo的方式:定义中引用定义。
        
        registry.registerBeanDefinition(ServiceBean.class.getName(), beanDefinition);
		int aa=registry.getBeanDefinitionCount();
		System.out.println(aa);
	}



	public void afterPropertiesSet() throws Exception {
		initMiddlerWareStart(actionName,MsgHandler.class);//启动TCP监听
	}

	public void initMiddlerWareStart(String msgName, Class clazz) {
		System.out.println("启动服务器,在9166端口监听tcp");
		try {
			MsgServiceHandlerRegister register = MsgServiceHandlerRegister.getRegister();
			// 注册事件处理类
			register.addMsgServiceHandler(msgName, clazz);
			new Thread(new Runnable() {
				@Override
				public void run() {
					// TODO Auto-generated method stub
					try {
						java.util.List<ClientApp> clientAppList = new java.util.ArrayList<ClientApp>();
						ClientApp eachClient = new ClientApp();// 通讯层用的客户对象,校验客户端					eachClient.setAppKey("85f035102cb411e8b971002655fff888");						eachClient.setAppSecret("homer888");
						clientAppList.add(eachClient);
						ServerInit.init(9166, clientAppList);
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						// isMiddlewearStarted=false;
					}
				}
			}).start();
			System.out.println("启动消息服务成功!端口:" + 9166);
		} catch (Exception e) {
//			isMiddlewearStarted = false;
			e.printStackTrace();
			System.out.println("启动消息服务失败!异常:" + e.toString());
		}

	}



2.ServiceBean.java. 上面的代码中beanDefinition.setBeanClass(ServiceBean.class);这句是用的它。它的目标是产生invoker对象,并把真正实现类的定义置给它。最后它会把invoker对象注册到通讯处理类的map中,供选用。


	public void afterPropertiesSet() throws Exception {
		System.out.println("【serviceBean】afterPropertiesSet...");
		System.out.println("【serviceBean】interfaceName..."+interfaceName);
		InvokerHolder invokerHolder=new InvokerHolder();
		invokerHolder.methodName=interfaceName;
		invokerHolder.parameterTypes=new Class[]{String.class};
		invokerHolder.proxy=ref;//上面的代码中有置这个值,正被的实现类。在spring的IOC中。
		System.out.println("【serviceBean】ref..."+(ref==null?"is null":ref));
		MsgHandler.invoderMap.put(interfaceName, invokerHolder);//放入MAP中。
	}



3. InvokerHolder类,它是被serviceBean生成的,serviceBean生了它,并传递给它实现类后,serviceBean也就没啥用了。这个InvokerHolder类就是根据参数值,进行反射调用。等通讯层找到它,用通讯层拿到的值设置好这个对象的值,就可以调用了。

public class InvokerHolder {
Object proxy;
String methodName;
Class<?>[] parameterTypes;
Object[] arguments;
    protected Object doInvoke(Object[] arguments) throws Exception
    {
    Method method = proxy.getClass().getMethod(methodName, parameterTypes);
    return method.invoke(proxy, arguments);
     }
}

4.MsgHandler类,这个是低层通讯用的,从通讯中拿到需要的数据,找到invoker,并调用,再处理返回值写入nettyChannel。

public class MsgHandler implements MsgServiceHandler {
	public static Map<String, InvokerHolder> invoderMap = new java.util.HashMap<String, InvokerHolder>();
	@Override
	public MiddleMsg handleMsgEvent(MsgEvent dm, MiddleMsg msg) {
		// TODO Auto-generated method stub

		System.out.println("invoderMap.size():"+invoderMap.size());
		Iterator<Entry<String, InvokerHolder>> it=invoderMap.entrySet().iterator();
		
		while(it.hasNext()){
			Entry aa= it.next();
			System.out.println("map:"+aa.getKey()+"|"+aa.getValue());
		}
		
		String action = msg.getHeader().getAction();
		String msgBody=msg.getBody().toString();
		try {
			System.out.println("action:"+action);
			System.out.println("msgBody:"+msgBody);
			//从请求参数中,找到invoker对象并设置好值。类型啥都舍弃了。
			InvokerHolder ref = invoderMap.get(action);
			Object[] arguments=new Object[]{msgBody};//参数值
			ResultObject resultObject = (ResultObject) ref.doInvoke(arguments);
			String body = "" + resultObject.getStatus() + resultObject.getStatusMsg();
			msg.setBody(body);

		} catch (Exception e) {
			e.printStackTrace();
		}
		return msg;
	}
}



5. 接口类与接口实现类,这个就不说明了。客户端接口与服务端接口应该是同一个包的。客户端没有实现,服务端有。

   下图为服务端工程,类不是很多,上面都介绍过:


   下图为客户端运行结果,客户端controller中autowired了接口,服务端invoker了调用。只有最后一句“我从服务端实现了这个接口”是接口方法的返回值。



四、总结

    上述功能已经在测试中很快实现了。以前看过些源码,但真正写出来,写之前还是思考不少东西。代码虽然不多,平时就要有空想想,看资料还是花了些时间的。碰巧平时处理项目中的mybatis问题中发现了另一种处理方式,所以快速就写出来。

    之前看过些dubbo与druid的源码,但直到近来在项目中使用,才真正感觉到源码的价值了。不久前写一个业务数据内存中的处理,用到了druid中的连接池的线程管理方式。写另一业务依次处理中,用到了我前面一个文章写的过滤链模式。dubbo中的主要的代理与技术目前还没有在实际中用过,但对于类的设计与关系处理更透彻了,看其它代码也很快。有些人喜欢看java的源码,但我建议看这两个项目的源码,这是更系统化的解决问题的代码,而不仅是什么arrayList,Hashmap里面一些技巧的学习。有点象设计模式与具体代码技巧的区别。

    如果你写基于netty的通讯架构,可以模仿dubbo协议。如果用到集群,用到SPI,用到策略,用到与zookeeper, redis连接,如果让系统集成多种实现需要写抽象层的代码,也可以学习它的思路。

    现在dubbo与spring cloud是微服务的两个主流,不知道spring cloud中有啥可以借鉴的,过一阵有空用用它。



  • 大小: 23.3 KB
  • 大小: 55.1 KB
  • 大小: 99 KB
  • 大小: 843.2 KB
分享到:
评论

相关推荐

    springcloud:Dubbo集成RPC框架demo源码案例演示

    springcloud:Dubbo集成RPC框架demo源码案例演示

    DUBBO与Spring Cloud的集成通讯示例

    DUBBO与Spring Cloud的集成通讯示例

    dubbo与spring集成的最小例子

    分布式服务治理soa面向服务架构 dubbo与spring集成的最小例子

    dubbo spring4.1集成demo

    【标题】"dubbo spring4.1集成demo"是一个示例项目,展示了如何将流行的Java微服务框架Dubbo与Spring 4.1版本整合。这个项目涵盖了Dubbo 2.5.3,一个高性能、轻量级的服务治理框架,以及Zookeeper 3.4.5,一个分布式...

    spring_dubbo spring_dubbo spring_dubbo

    spring_dubbo spring_dubbospring_dubbospring_dubbospring_dubbospring_dubbospring_dubbospring_dubbospring_dubbospring_dubbospring_dubbospring_dubbospring_dubbospring_dubbospring_dubbospring_dubbospring_...

    zookeeper+dubbo+spring

    在IT行业中,Zookeeper、Dubbo和Spring是三个非常重要的组件,它们在分布式系统和微服务架构中扮演着核心角色。下面将详细解释这三个技术及其相互间的整合。 **Zookeeper** 是一个分布式的,开放源码的分布式应用...

    springboot2.0.x+dubbo-spring-boot-starter

    标题 "springboot2.0.x+dubbo-spring-boot-starter" 涉及的是将流行的微服务框架 Dubbo 集成到 Spring Boot 2.0.x 的项目实践中。这个集成使得开发者能够利用 Spring Boot 的便利性和 Dubbo 的高性能远程服务调用...

    Dubbo和Spring Cloud微服务架构对比——服务注册和发现.docx

    Dubbo侧重于高性能RPC通信,并提供了一套完整的服务治理体系;而Spring Cloud则构建在Spring Boot之上,拥有更为广泛的生态系统支持和更加灵活的服务治理方式。开发者可以根据项目的具体需求和技术背景选择合适的...

    dubbo与spring集成maven pom文件

    dubbo与spring4集成maven pom文件 此pom为我生产项目中的配置,开始想省事使用spring boot,结果与springmvc不兼容,导致tomcat启动失败,后来找了maven shade来打包,解决了xsd兼容问题 另外注意,dubbo阿里的分支...

    基于dubbo实现的rpc框架RPC 框架

    基于Dubbo实现的RPC框架,是Java开发中常见的一种高效率、高性能的服务治理方案,尤其在微服务架构中广泛应用。 Dubbo是由阿里巴巴开源的高性能RPC框架,它提供了服务注册、服务发现、负载均衡、流量控制、熔断降级...

    【42】使用dubbo、spring-boot等技术实现互联网后台服务项目架构视频教程 .txt

    Apache Dubbo(以下简称“Dubbo”)是一款高性能、轻量级的开源Java RPC框架。它提供了三个核心功能:面向接口代理、服务自动注册与发现以及高透明化的负载均衡,这些功能极大地简化了分布式系统之间的调用过程。...

    Dubbo与Zookeeper、spring框架的整合。

    3. **Spring集成**:Spring框架可以与Dubbo无缝集成,通过Spring的XML配置或注解方式,可以方便地声明服务提供者和服务消费者,实现服务的发布和引用。Spring的DI特性使得服务实例化更加灵活,AOP支持则能方便地添加...

    基于dubbo实现的rpc框架RPC

    Dubbo作为RPC框架,提供了一套完整的解决方案,包括服务注册与发现、负载均衡、故障转移、服务治理等特性。 **Dubbo核心组件** 1. **服务提供者(Provider)**:服务提供者是提供服务的实体,它会暴露服务,并将服务...

    dubbo+spring实例

    在IT行业中,`Dubbo` 是一款非常知名的分布式服务框架,由阿里巴巴开源并维护,它致力于提供高性能、透明化的RPC(远程过程调用)服务,以及服务治理功能。本实例结合了 `Spring` 框架,使得服务的创建、管理和调用...

    基于Maven的dubbo集成spring简单实例

    基于Maven的dubbo集成spring简单实例.网上有不少例子均无法运行,只好自已整理一个可运行的例子.这是最简单可运行的例子,方便大家交流学习.在zookeeper-3.4.8和zookeeper-3.4.9下均可正常运行.

    dubbo整合spring

    在IT行业中,Dubbo是一个广泛使用的高性能Java RPC框架,它由阿里巴巴开源并维护。Spring则是一个功能强大的企业级应用开发框架,提供了丰富的依赖注入、事务管理等功能。将Dubbo与Spring整合,可以充分利用两者的...

    Apache Dubbo的java实现 RPC和微服务框架

    Dubbo可以无缝集成Spring,使得配置和服务治理变得极其简单,通过XML配置或注解方式即可实现服务的声明式管理。 10. **异步调用** Dubbo支持同步和异步调用模式,异步调用能够提高系统的响应速度和并发处理能力,...

    DUBBO_SPRING_DEMO

    本示例"DUBBO_SPRING_DEMO"旨在演示如何将Dubbo与Spring进行深度整合,以便更好地管理和调用服务。这个项目名为"DubboDemo-master",通常包含以下几个关键部分: 1. **服务提供者(Provider)**:提供服务的模块,...

    dubbo spring mybatis redis

    Dubbo 是一个高性能、轻量级的 Java RPC 框架,用于构建分布式服务。Spring 是一个广泛使用的 Java 应用框架,提供依赖注入(DI)和面向切面编程(AOP)等功能,极大地简化了应用开发。MyBatis 是一个持久层框架,它...

Global site tag (gtag.js) - Google Analytics