技术标签: Dubbo 实现分析 java rpc
Dubbo 泛化调用、阿里的好舒服的泛化调用都是类似的功能。最近给同事排查一个dubbo-admin 控制台调用报错的问题的小研究,为此还给社区提了一个issue,具体可以查看链接 dubbo-admin 泛型参数测试 ClassNotFoundException 看了最新版本好像自动去掉了出现泛型<这样的信息通通干掉,不过没有发布release版本。 参考文档:使用泛化调用
泛化接口调用方式主要用于客户端没有 API 接口(泛化调用是指不需要依赖服务的二方包)及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,API网关比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。泛化调用的方式可以有效减少平台型产品的二方包依赖,实现系统的轻量级运行。对于服务提供方新增的接口不需要修改二方包的版本,即可调用,不方便的就是对于数据入参、返回值由于没有二方包很难理解,全部都是org.apache.dubbo.common.utils.PojoUtils#generalize(java.lang.Object) 转换为基本类型或者Map等等。
PojoUtils. Travel object deeply, and convert complex type to simple type. (深度遍历对象,并将复杂类型转换为简单类型),同时也提供了反向转换。
Simple type below will be remained:
PojoUtils 提供了讲复杂对象转换为简单的对象,通过简单对象转换为复杂对象。有点像序列化和反序列话,你要说它是不是,感觉也没错哈!官方的测试例子不错,可以去了解一下
List<User> users = new ArrayList<>();
User user = new User();
user.setName("other");
user.setAge(31);
users.add(user);
Object generalize = PojoUtils.generalize(users);
System.out.println(generalize);
//[{name=other, class=org.apache.dubbo.samples.Dto.User, age=31}]
基于官方demo 中 dubbo-samples-zookeper,本地安装一个zk 即可启动
https://github.com/apache/dubbo-samples/tree/master/java/dubbo-samples-zookeeper
泛化调用最好的一个参考例子就是dubbo-admin 中 ,通过泛化进行控制台的调用实践,泛化没有二方包,服务提供方的方法参数类型无法直接使用强类型表示,传递的参数也无法直接使用强类型只能通过 参考 :PojoUtils 序列化 Object generalize = PojoUtils.generalize(data) 例子传递 Map、List 、Array 等基本类型的参数
eg List -> [{name=other, age=31, class=org.apache.dubbo.samples.Dto.User}] 就是List<HashMap<String,String> 这样的结构**,中增加 class 字段信息,反序列化时候使用表征对应的泛型的信息,或者类class的信息**。
为啥需要这些,就是为了找到具体的接口的提供者、接口的具体的方法是哪个?调用传递的参数是啥,即可享受正常的调用逻辑。如下是dubbo-admin 控制台实践指南,Dubbo API 网关也是类似的概念哦!只要通过配置或者传递参数中有如下的概念即可搞定问题。
泛化泛化的结果:也是基本类型的数据,根据 PojoUtils.generalize(result) 根据result 进行Pojo作为泛化的结果。如下是 dubbo-admin 控制台测试的代码
https://github.com/apache/dubbo-admin/blob/develop/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/service/impl/GenericServiceImpl.java
public Object invoke(String service, String method, String[] parameterTypes, Object[] params) {
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
String group = Tool.getGroup(service);
String version = Tool.getVersion(service);
String intf = Tool.getInterface(service);
reference.setGeneric(true);
reference.setApplication(applicationConfig);
reference.setInterface(intf);
reference.setVersion(version);
reference.setGroup(group);
try {
removeGenericSymbol(parameterTypes);
GenericService genericService = reference.get();
return genericService.$invoke(method, parameterTypes, params);
} finally {
reference.destroy();
}
}
/**
* remove generic from parameterTypes 排除掉泛型信息
*
* @param parameterTypes
*/
private void removeGenericSymbol(String[] parameterTypes){
for (int i = 0; i < parameterTypes.length; i++) {
int index = parameterTypes[i].indexOf("<");
if (index > -1) {
parameterTypes[i] = parameterTypes[i].substring(0, index);
}
}
}
针对官方的版本 添加了一个泛化的对象的接口
import java.util.List;
import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.samples.Dto.User;
import org.apache.dubbo.samples.api.GreetingService;
@Service(version = "1.0.0.daily", group = "dubbo")
public class AnnotatedGreetingService implements GreetingService {
/**
* 简单测试
*
* @param name
* @return
*/
@Override
public String sayHello(String name) {
System.out.println("greeting service received: " + name);
return "hello, " + name;
}
/**
* 测试泛型接口
*
* @param users
* @return
*/
@Override
public List<User> getListUserIncludeMe(List<User> users) {
if (users == null) {
return null;
}
User user = new User();
user.setName("wangji");
user.setAge(28);
users.add(user);
return users;
}
}
/**
* @author wangji
* @date 2020/6/2 7:12 PM
*/
public class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
最最关键的地方在于 genericService.$invoke,调用这个泛化接口的几个最主要的参数信息。最好自己动手实践一下子。
public static void main(String[] args) {
ApplicationConfig application = new ApplicationConfig();
application.setName("Test-" + UUID.randomUUID().toString());
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
application.setRegistry(registryConfig);
application.setQosEnable(false);
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
String group = "dubbo";
String version = "1.0.0.daily";
String interfaceName = "org.apache.dubbo.samples.api.GreetingService";
reference.setGeneric(true);
reference.setApplication(application);
reference.setInterface(interfaceName);
reference.setVersion(version);
reference.setGroup(group);
GenericService genericService = reference.get();
Object data = genericService.$invoke("sayHello", new String[] {
"java.lang.String" },
new Object[] {
"wangji" });
System.out.println("result:" + data);
}
// result:hello, wangji
如果你不知道怎么编写这个参数,可以 PojoUtils.generalize(data) 通过这个接口来生成对象,来看一下生成的数据的结构是咋样的。
注意泛型参数传递的参数类型不包含 java.util.List<org.apache.dubbo.samples.Dto.User> 后面的泛型的信息,只需要传递 java.util.List 即可。
public static void main(String[] args) {
ApplicationConfig application = new ApplicationConfig();
application.setName("Test-" + UUID.randomUUID().toString());
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
application.setRegistry(registryConfig);
application.setQosEnable(false);
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
String group = "dubbo";
String version = "1.0.0.daily";
String interfaceName = "org.apache.dubbo.samples.api.GreetingService";
reference.setGeneric(true);
reference.setApplication(application);
reference.setInterface(interfaceName);
reference.setVersion(version);
reference.setGroup(group);
GenericService genericService = reference.get();
List<User> users = new ArrayList<>();
User user = new User();
user.setName("other");
user.setAge(31);
users.add(user);
Object generalize = PojoUtils.generalize(users);
System.out.println(generalize);
Object getListUserIncludeMe = genericService.$invoke("getListUserIncludeMe", new String[] {
"java.util.List" },
new Object[] {
generalize });
System.out.println(getListUserIncludeMe);
}
// [{name=other, class=org.apache.dubbo.samples.Dto.User, age=31}]
// [{name=other, class=org.apache.dubbo.samples.Dto.User, age=31}, {name=wangji, class=org.apache.dubbo.samples.Dto.User, age=28}]
测试环境中相同的服务会存在多台,如果你想调用具体的某台机子咋办呢?指定具体的IP进行调用,没有看到官方的文档、问了一下小杰子的实践,参考试了一下泛化调用直接ip 也是ok的,只需要找到具体的服务提供者即可。
不过不同的版本好像不一样,没有仔细尝试
设置服务的url 即可满足,能够定位一个服务即可
reference.setUrl("dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService");
需要添加分组的信息才行
reference.setUrl("dubbo://192.168.1.3:20880/分组/org.apache.dubbo.samples.api.GreetingService");
基于 2.7.2 版本
public static void main(String[] args) {
ApplicationConfig application = new ApplicationConfig();
application.setName("Test-" + UUID.randomUUID().toString());
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
application.setRegistry(registryConfig);
application.setQosEnable(false);
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
String group = "dubbo";
String version = "1.0.0.daily";
String interfaceName = "org.apache.dubbo.samples.api.GreetingService";
reference.setGeneric(true);
reference.setApplication(application);
reference.setInterface(interfaceName);
reference.setVersion(version);
reference.setGroup(group);
reference.setUrl("dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService");
GenericService genericService = reference.get();
Object data = genericService.$invoke("sayHello", new String[] {
"java.lang.String" },
new Object[] {
"wangji" });
System.out.println("result:" + data);
}
java.util.List<org.apache.dubbo.samples.Dto.User> 这个是由于PojoUtils的实现问题,导致这样的写法是有问题的。
Object getListUserIncludeMe = genericService.$invoke("getListUserIncludeMe", new String[] { "java.util.List<org.apache.dubbo.samples.Dto.User>" },
new Object[] { generalize });
// at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
// at org.apache.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:41)
// at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
// at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$CallbackRegistrationInvoker.invoke(ProtocolFilterWrapper.java:150)
// at org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:152)
// at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:102)
// at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:193)
// at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
// at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
// at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
// at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
// at java.lang.Thread.run(Thread.java:748)
// Caused by: java.lang.ClassNotFoundException: java.util.List<org.apache.dubbo.samples.Dto.User>
// at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
// at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
// at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
// at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
// at java.lang.Class.forName0(Native Method)
// at java.lang.Class.forName(Class.java:348)
// at org.apache.dubbo.common.utils.ReflectUtils.name2class(ReflectUtils.java:743)
// at org.apache.dubbo.common.utils.ReflectUtils.name2class(ReflectUtils.java:670)
// at org.apache.dubbo.common.utils.ReflectUtils.findMethodByMethodSignature(ReflectUtils.java:885)
dubbo泛化调用原理 画的这个图不错十分的形象生动,可以总结一下 泛化的原理
+-------------------------------------------+ +-------------------------------------------+
| consumer 端 | | provider 端 |
| | | |
| | | |
| | | |
| | | |
| +------------------+ | | +--------------+ |
| |GenericImplFilter | | Invocation | |GenericFilter | |
| +----> | +-------------------------> | | |
| | +------------------+ | | +--------------+ |
| +-----------+ | | | +-----------+ |
| | | | | | | | |
| |Client | | | +--> | Service | |
| | | | | | | |
| +-----------+ | | +-------+---+ |
| | | | |
| ^ +------------------+ | | +--------------+ | |
| | |GenericImplFilter | | | |GenericFilter | <----------+ |
| +-------------+ | <-------------------------+ | |
| +------------------+ | | +--------------+ |
| | | |
| | | |
| | | |
| | | |
+-------------------------------------------+ +-------------------------------------------+
/**
* GenericImplInvokerFilter 基于官方的class 2.7.2 删掉不要的东西,简化理解
*/
@Activate(group = CommonConstants.CONSUMER, value = GENERIC_KEY, order = 20000)
public class GenericImplFilter extends ListenableFilter {
private static final Logger logger = LoggerFactory.getLogger(GenericImplFilter.class);
private static final Class<?>[] GENERIC_PARAMETER_TYPES = new Class<?>[]{
String.class, String[].class, Object[].class};
public GenericImplFilter() {
super.listener = new GenericImplListener();
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String generic = invoker.getUrl().getParameter(GENERIC_KEY);
if ((invocation.getMethodName().equals($INVOKE) || invocation.getMethodName().equals($INVOKE_ASYNC))
&& invocation.getArguments() != null
&& invocation.getArguments().length == 3
&& ProtocolUtils.isGeneric(generic)) {
invocation.setAttachment(
GENERIC_KEY, invoker.getUrl().getParameter(GENERIC_KEY));
}
return invoker.invoke(invocation);
}
static class GenericImplListener implements Listener {
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
}
@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
}
}
}
/**
* GenericInvokerFilter. 基于官方的class 2.7.2 删掉不要的东西,简化理解
*/
@Activate(group = CommonConstants.PROVIDER, order = -20000)
public class GenericFilter extends ListenableFilter {
public GenericFilter() {
super.listener = new GenericListener();
}
@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
&& inv.getArguments() != null
&& inv.getArguments().length == 3
&& !GenericService.class.isAssignableFrom(invoker.getInterface())) {
String name = ((String) inv.getArguments()[0]).trim();
String[] types = (String[]) inv.getArguments()[1];
Object[] args = (Object[]) inv.getArguments()[2];
try {
// 找到调用的接口
Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
Class<?>[] params = method.getParameterTypes();
if (args == null) {
args = new Object[params.length];
}
String generic = inv.getAttachment(GENERIC_KEY);
if (StringUtils.isBlank(generic)) {
generic = RpcContext.getContext().getAttachment(GENERIC_KEY);
}
if (StringUtils.isEmpty(generic)
|| ProtocolUtils.isDefaultGenericSerialization(generic)) {
// 将PojoUtils 转换的简单对象 转换为复杂的对象
args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
}
return invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
} catch (NoSuchMethodException e) {
throw new RpcException(e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new RpcException(e.getMessage(), e);
}
}
return invoker.invoke(inv);
}
static class GenericListener implements Listener {
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation inv) {
if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
&& inv.getArguments() != null
&& inv.getArguments().length == 3
&& !GenericService.class.isAssignableFrom(invoker.getInterface())) {
String generic = inv.getAttachment(GENERIC_KEY);
if (StringUtils.isBlank(generic)) {
generic = RpcContext.getContext().getAttachment(GENERIC_KEY);
}
if (appResponse.hasException() && !(appResponse.getException() instanceof GenericException)) {
appResponse.setException(new GenericException(appResponse.getException()));
}
// 设置反序列化 讲复杂对象转换为简单的基础对象
appResponse.setValue(PojoUtils.generalize(appResponse.getValue()));
}
}
@Override
public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
}
}
}
org.apache.dubbo.rpc.RpcInvocation 打印的效果,方便理解。
public String toString() {
return "RpcInvocation [methodName=" + methodName + ", parameterTypes="
+ Arrays.toString(parameterTypes) + ", arguments=" + Arrays.toString(arguments)
+ ", attachments=" + attachments + "]";
}
org.apache.dubbo.rpc.filter.GenericImplFilter
dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService?application=Test-0519fed7-71d4-494b-b225-15f153bb79db&dubbo=2.0.2&generic=true&group=dubbo&interface=org.apache.dubbo.samples.api.GreetingService&lazy=false&pid=12632&qos-enable=false®ister.ip=192.168.1.3&side=consumer&sticky=false×tamp=1591110891800&version=1.0.0.daily
RpcInvocation
[methodName=$invoke,//调用的方法
parameterTypes=[class java.lang.String, class [Ljava.lang.String;,
class [Ljava.lang.Object;],//通用泛型的参数类型
arguments=[sayHello, [Ljava.lang.String;@68b32e3e, [Ljava.lang.Object;@bcef303],
attachments={}
]
org.apache.dubbo.rpc.filter.GenericFilter
RpcInvocation
[methodName=$invoke,
parameterTypes=[class java.lang.String, class [Ljava.lang.String;, class [Ljava.lang.Object;],
arguments=[sayHello, [Ljava.lang.String;@4174acb6, [Ljava.lang.Object;@5486085f],
attachments={path=org.apache.dubbo.samples.api.GreetingService,
input=351, dubbo=2.0.2,
interface=org.apache.dubbo.samples.api.GreetingService,
version=1.0.0.daily, generic=true,
group=dubbo}]
dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService?anyhost=true&application=zookeeper-demo-provider&bean.name=ServiceBean:org.apache.dubbo.samples.api.GreetingService:1.0.0.daily:dubbo&bind.ip=192.168.1.3&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=dubbo&interface=org.apache.dubbo.samples.api.GreetingService&methods=sayHello,getListUserIncludeMe&pid=12595&qos-accept-foreign-ip=false®ister=true&release=2.7.2&revision=1.0.0.daily&side=provider×tamp=1591110149299&version=1.0.0.daily
对于dubbo的泛化调用的文章一直想记录一篇,一直苦于没有思路,不知道怎么找到合适的切入角来描述这个问题,正好最近协助同事排查问题,正好有所收获,因此记录一篇博客,在了解过程中不断的进化,加深了对于dubbo的认知。6-2 号,特殊的日子,好像是我阳历的生日 happy ~~
文章浏览阅读150次。最近我遇到了一个问题,我在 Windows 10 上不小心修改了桌面路径,但现在无法将其改回原来的路径。我想知道如何通过编程来解决这个问题。要解决这个问题,我们可以使用编程来修改桌面路径。下面是一个使用 Python 编程语言的示例,演示了如何通过注册表修改桌面路径。在修改桌面路径后,你可能需要重新启动 Windows Explorer 进程以使更改生效。如果你有任何其他问题,请随时提问。注意:在运行以上代码之前,请确保你具有管理员权限。函数来将桌面路径修改为新路径。变量设置为你想要的新路径,并调用。_无法更改桌面路径
文章浏览阅读1.4k次,点赞3次,收藏22次。VoLTE就像一位优雅的败家姑娘,千呼万唤使出来!难免有人会在VoLTE与VoIP之间打量对比。关于两者之间的对比分析太多,尽管如此,也是依然一头雾水。我们今天就从她们的前世说起,希望能够说清楚关于VoIP、VoLTE、CSFB、VoWiFi、SIP、IMS那些事...从VoIP到VoLTE观察家们认为VoIP的出现起始于1995年,那个时候GSM刚进入中国。20年来,VoIP语音服..._volte视频转语音原因
文章浏览阅读773次。最近在做一个基于JAVA Servlet的WEB应用以及对应的Anroid应用客户端的开发工作。其中,在接口的访问和数据的传输方面使用的比较多的是使用JSON对象来操作格式化数据:在服务器端采用JSON字符串来传递数据并在WEB前端或者Android客户端使用JSON来解析接收到的数据。首先,在JAVA中使用JSON需要引入 org.json 包(点击这里 可以下载相应的JAR包!_java 如何实现 动态json里面有个json 想js 一样进行连点拿去数据
文章浏览阅读1.6k次,点赞3次,收藏11次。Modbus是一种单主站的主/从通信模式,它定义了一种通讯规范,用于在工业自动化系统中实现设备之间的数据交换。Modbus网络上只能有一个主站存在,主站在Modbus网络上没有地址,而从站的地址范围为0-247,其中0为广播地址,从站的实际地址范围为1-247。Modbus通信协议可以通过多种传输方式传播,如RS232C、RS485、光纤和无线电等。它具有两种串行传输模式,即ASCII和RTU,它们定义了数据如何打包和解码的方式。_java modbus
文章浏览阅读1.6k次。2008--8--61。修改LCD参数--------支持的像素/RGB格式《5/6/5---5/5/5》/前后仰参数修改2。USB键盘与鼠标/U盘3。电池电量检测-------BAT组件4。注册表永久保存5。多国语言支持----MUI6。ACTIVESY_wince可以实现哪些功能
文章浏览阅读2.7k次,点赞3次,收藏7次。IOS技术栈总结自己开发中使用或接触到的技能集合,整理了一份IOS开发的技能栈。_ios开发技术栈
文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn
文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios
文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql
文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...
文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120
文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数