【Spring】GoF 之代理模式

一、代理模式

在 Java 程序中的代理模式的作用:

  • 当一个对象需要受到保护的时候,可以考虑使用代理对象去完成某个行为

  • 需要给某个对象的功能进行功能增强的时候,可以考虑找一个代理进行增强

  • A 对象无法和 B 对象直接交互时,也可以使用代理模式来解决

代理模式中的三大角色:

  • 目标对象(演员)

  • 代理对象(替身演员)

  • 目标对象和代理对象的公共接口(演员与替身演员相同的行为,可以让观众不知道是替身演员)

如果使用代理模式,对于客户端程序来说,客户端是无法察觉的,客户端在使用代理对象的时候就像在使用目标对象

代理模式在代码实现上,包括两种形式:

  • 静态代理

  • 动态代理

 

二、静态代理

package org.qiu.proxy.service;/*** 订单业务接口* @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-16-13:09* @since 1.0*/
public interface OrderService {/*** 生成订单*/void generate();/*** 修改订单信息*/void modify();/*** 查看订单详情*/void detail();
}
package org.qiu.proxy.service;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-16-13:10* @since 1.0*/
public class OrderServiceImpl implements OrderService{@Overridepublic void generate() {// 模拟网络延迟try {Thread.sleep(1024);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成!");}@Overridepublic void modify() {// 模拟网络延迟try {Thread.sleep(512);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已修改!");}@Overridepublic void detail() {// 模拟网络延迟try {Thread.sleep(800);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单详情......");}
}
public class Test {public static void main(String[] args) {OrderService orderService = new OrderServiceImpl();orderService.generate();orderService.detail();orderService.modify();}
}

运行结果:  

项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。如果是你,你该怎么做呢?

 

第一种方案

直接修改Java源代码,在每个业务方法中添加统计逻辑,如下:

package org.qiu.proxy.service;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-16-13:10* @since 1.0*/
public class OrderServiceImpl implements OrderService{@Overridepublic void generate() {long begin = System.currentTimeMillis();// 模拟网络延迟try {Thread.sleep(1024);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成!");long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void modify() {long begin = System.currentTimeMillis();// 模拟网络延迟try {Thread.sleep(512);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已修改!");long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void detail() {long begin = System.currentTimeMillis();// 模拟网络延迟try {Thread.sleep(800);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单详情......");long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}
}

运行效果:  

需求可以满足,但显然是违背了OCP开闭原则,这种方案不可取

第二种方案

编写一个子类继承OrderServiceImpl,在子类中重写每个方法,代码如下:

package org.qiu.proxy.service;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-16-13:24* @since 1.0*/
public class OrderServiceImplSub extends OrderServiceImpl{@Overridepublic void generate() {long begin = System.currentTimeMillis();super.generate();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void modify() {long begin = System.currentTimeMillis();super.modify();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void detail() {long begin = System.currentTimeMillis();super.detail();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}
}
public class Test {public static void main(String[] args) {OrderService orderService = new OrderServiceImplSub();orderService.generate();orderService.detail();orderService.modify();}
}

运行效果:  

这种方式可以解决,但是存在两个问题:

  • 第一个问题:假设系统中有100个这样的业务类,需要提供100个子类,并且之前写好的创建Service对象的代码,都要修改为创建子类对象

  • 第二个问题:由于采用了继承的方式,导致代码之间的耦合度较高。

这种方案也不可取

第三种方案

使用代理模式(这里采用静态代理)

可以为 OrderService 接口提供一个代理类

package org.qiu.proxy.service;/*** 代理对象* @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-16-13:32* @since 1.0*/
public class OrderServiceProxy implements OrderService {// 目标对象// 这里要使用“公共接口”类型,因为公共接口耦合度低private OrderService orderService;// 通过构造方法将目标对象传递给代理对象public OrderServiceProxy(OrderService orderService){this.orderService = orderService;}@Overridepublic void generate() {long begin = System.currentTimeMillis();// 执行目标对象的目标方法orderService.generate();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void modify() {long begin = System.currentTimeMillis();orderService.modify();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void detail() {long begin = System.currentTimeMillis();orderService.detail();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}
}

这种方式的优点:

符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的 

package org.qiu.proxy.client;import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.OrderServiceImplSub;
import org.qiu.proxy.service.OrderServiceProxy;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.client* @date 2022-11-16-13:13* @since 1.0*/
public class Test {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderServiceImpl();// 创建代理对象OrderService proxy = new OrderServiceProxy(target);// 调用代理对象的代理方法proxy.generate();proxy.modify();proxy.detail();}
}

运行效果:  

以上就是代理模式中的静态代理,其中:

OrderService 接口是代理类和目标类的共同接口

OrderServiceImpl 是目标类

OrderServiceProxy 是代理类

思考一下:如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用

 

三、动态代理 

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量,解决代码复用的问题

在内存当中动态生成类的技术常见的包括:

  • JDK 动态代理技术:只能代理接口

  • CGLIB 动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比 JDK 动态代理要好(底层有一个小而快的字节码处理框架ASM)

  • Javassist 动态代理技术:Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码 JBoss 应用服务器项目,通过使用 Javassist 对字节码操作为 JBoss 实现动态"AOP"框架。

JDK 动态代理 

package org.qiu.proxy.service;/*** 订单业务接口* @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-08:23* @since 1.0*/
public interface OrderService {/*** 生成订单*/void generate();/*** 修改订单信息*/void modify();/*** 查看订单详情*/void detail();
}
package org.qiu.proxy.service;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-08:23* @since 1.0*/
public class OrderServiceImpl implements OrderService{@Overridepublic void generate() {// 模拟网络延迟try {Thread.sleep(1024);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成!");}@Overridepublic void modify() {// 模拟网络延迟try {Thread.sleep(512);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已修改!");}@Overridepublic void detail() {// 模拟网络延迟try {Thread.sleep(800);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单详情......");}
}

使用静态代理的时候,除了以上一个接口和一个实现类之外,还需要写一个代理类 UserServiceProxy!在动态代理中UserServiceProxy 代理类是可以动态生成的,这个类不需要写。我们直接写客户端程序即可:  

package org.qiu.proxy.client;import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.TimeInvocatioinHandler;
import org.qiu.proxy.util.ProxyUtil;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.client* @date 2022-11-20-08:22* @since 1.0*/
public class Test {public static void main(String[] args) {OrderService target = new OrderServiceImpl();/*** 参数解析:* 参数一:Classloader loader 类加载器*      JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个* 参数二:Class<?>[] interfaces 代理类要实现的接口*      代理类和目标类要实现同一个接口或同一些接口* 参数三:InvocationHandler h 调用处理器对象(是一个接口)*      在这里实现功能的增强(这里采用内部实现类,也可以将其独立出来)*/OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));orderServiceProxy.detail();orderServiceProxy.modify();orderServiceProxy.generate();}
}
package org.qiu.proxy.service;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-09:01* @since 1.0*/
public class TimeInvocatioinHandler implements InvocationHandler {private Object target;public TimeInvocatioinHandler(Object target) {this.target = target;}/*** invoke 什么时候被调用?*  当代理对象调用代理方法的时候,注册在 InvocationHandler 调用处理器当中的 invoke 方法被调用** @param proxy     代理对象的引用* @param method    目标对象上的目标方法* @param args      目标方法上的实际参数* @return          若代理对象代用代理方法之后需要返回结果的话,invoke方法必须将目标对象目标方法执行结果继续返回*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long begin = System.currentTimeMillis();// 调用目标对象上的目标方法Object resultValue = method.invoke(target, args);long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));return null;}
}

上面调用 JDK 自带的方法比较繁琐,这里可以封装一个工具类,方便使用:  

package org.qiu.proxy.util;import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.TimeInvocatioinHandler;import java.lang.reflect.Proxy;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.util* @date 2022-11-20-09:12* @since 1.0*/
public class ProxyUtil {/*** 获取代理对象(JDK动态代理)* @param target* @return*/public static Object newProxyInstance(Object target){return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));}}
package org.qiu.proxy.client;import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.TimeInvocatioinHandler;
import org.qiu.proxy.util.ProxyUtil;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.client* @date 2022-11-20-08:22* @since 1.0*/
public class Test {public static void main(String[] args) {OrderService target = new OrderServiceImpl();/*** 参数解析:* 参数一:Classloader loader 类加载器*      JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个* 参数二:Class<?>[] interfaces 代理类要实现的接口*      代理类和目标类要实现同一个接口或同一些接口* 参数三:InvocationHandler h 调用处理器对象(是一个接口)*      在这里实现功能的增强(这里采用内部实现类,也可以将其独立出来)*//*OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));*/// 工具类封装OrderService orderServiceProxy = (OrderService) ProxyUtil.newProxyInstance(target);orderServiceProxy.detail();orderServiceProxy.modify();orderServiceProxy.generate();}
}

 

CGLIB 动态代理

CGLIB 既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用 final 修饰。

使用CGLIB,需要引入它的依赖:

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

准备一个没有实现接口的类,如下:  

package org.qiu.proxy.service;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-09:22* @since 1.0*/
public class UserService {public void login(){System.out.println("用户正在登录系统....");}public void logout(){System.out.println("用户正在退出系统....");}
}

使用 CGLIB 在内存中为 UserService 类生成代理类,并创建对象:  

package org.qiu.proxy.client;import net.sf.cglib.proxy.Enhancer;
import org.qiu.proxy.service.TimerMethodInterceptor;
import org.qiu.proxy.service.UserService;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.client* @date 2022-11-20-09:23* @since 1.0*/
public class Client {public static void main(String[] args) {// 创建字节码增强器Enhancer enhancer = new Enhancer();// 告诉cglib要继承哪个类enhancer.setSuperclass(UserService.class);// 设置回调接口enhancer.setCallback(方法拦截器对象);// 生成源码,编译class,加载到JVM,并创建代理对象UserService userServiceProxy = (UserService)enhancer.create();userServiceProxy.login();userServiceProxy.logout();}
}

和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor

编写MethodInterceptor接口实现类:

package org.qiu.proxy.service;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-09:24* @since 1.0*/
public class TimerMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {return null;}
}

 

MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:

第一个参数:目标对象

第二个参数:目标方法

第三个参数:目标方法调用时的实参

第四个参数:代理方法

在MethodInterceptor的intercept()方法中调用目标以及添加增强:

package org.qiu.proxy.service;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-09:24* @since 1.0*/
public class TimerMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 前增强long begin = System.currentTimeMillis();// 调用目标Object retValue = methodProxy.invokeSuper(target, objects);// 后增强long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "毫秒");// 一定要返回return retValue;}
}

回调已经写完了,可以修改客户端程序了:  

package org.qiu.proxy.client;import net.sf.cglib.proxy.Enhancer;
import org.qiu.proxy.service.TimerMethodInterceptor;
import org.qiu.proxy.service.UserService;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.client* @date 2022-11-20-09:23* @since 1.0*/
public class Client {public static void main(String[] args) {// 创建字节码增强器Enhancer enhancer = new Enhancer();// 告诉cglib要继承哪个类enhancer.setSuperclass(UserService.class);// 设置回调接口enhancer.setCallback(new TimerMethodInterceptor());// 生成源码,编译class,加载到JVM,并创建代理对象UserService userServiceProxy = (UserService)enhancer.create();userServiceProxy.login();userServiceProxy.logout();}
}

对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:  

  • --add-opens java.base/java.lang=ALL-UNNAMED

  • --add-opens java.base/sun.net.util=ALL-UNNAMED

 

一  叶  知  秋,奥  妙  玄  心

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/11386.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

速盾:cdn转发循环攻击

CDN&#xff08;内容分发网络&#xff09;是一种用于加速和分发网络资源的技术。它通过将内容缓存到离用户最近的服务器上&#xff0c;从而减少响应时间和网络拥塞&#xff0c;提高用户体验。然而&#xff0c;CDN技术也存在一些安全风险&#xff0c;其中之一就是转发循环攻击。…

如何在 Python 中使变量不可继承

1. 问题背景 在 Python 中&#xff0c;子类可以继承父类的属性和方法。但是&#xff0c;有时我们希望子类不能继承父类的某些属性或方法。这种情况下&#xff0c;该如何做呢&#xff1f; 2. 解决方案 解决方案一&#xff1a;使用双下划线前缀 Python 中的双下划线前缀用于表…

仓库管理流程详解(附作业流程图)

仓库管理流程在企业的日常运营中至关重要。它不仅是物资流转的核心环节&#xff0c;更关乎着企业的运营效率、成本控制和客户服务水平。一个高效、规范的仓库管理流程能够确保货物从入库到出库的各个环节有序进行&#xff0c;减少资源浪费和时间成本&#xff0c;同时帮助企业实…

一般可以用哪些值作为key?如果用索引值作为key 会出现什么样的问题?

在React中&#xff0c;key是一个特殊的属性&#xff0c;它主要用于帮助React识别哪些元素发生了变化、被添加或被移除。当在列表&#xff08;如数组&#xff09;中渲染元素时&#xff0c;为每一个元素提供一个唯一的key会非常有帮助。 一般来说&#xff0c;key的值可以是以下几…

基于python的大麦网自动抢票工具的设计与实现

基于python的大麦网自动抢票工具的设计与实现 Design and Implementation of Da Mai Net Ticket Grabbing tool based on Python 完整下载链接:基于python的大麦网自动抢票工具的设计与实现 文章目录 基于python的大麦网自动抢票工具的设计与实现摘要第一章 引言1.1 研究背景…

Hive Sampling 抽样函数

Hive Sampling 抽样函数 1.random随机抽样 2.数据块抽样 3.分桶表抽样

bat脚本添加防火墙端口

echo off setlocal:: 设置需要开放的端口号 set PORT****:: 设置规则名称 set RULENAMEAllow_%PORT%:: 添加防火墙规则 netsh advfirewall firewall add rule name"%RULENAME%" dirin actionallow protocoltcp localport%PORT%echo Port %PORT% has been added to Wi…

vue3获取原始值

在 Vue 3 中&#xff0c;_rawValue 是 ref 内部的一个属性&#xff0c;它用来存储 ref 的原始值&#xff0c;也就是未经响应式处理的值。这个属性主要用于 Vue 的内部逻辑&#xff0c;以帮助区分 ref 的当前值 (value) 和原始输入值 (_rawValue)。对于大多数开发者来说&#xf…

LabVIEW控制多个同样的串口设备,有什么好的办法

如果所有的串口设备操作完全相同&#xff0c;且只涉及读取信息&#xff0c;那么可以采用更加简化的方法来组织你的LabVIEW程序&#xff0c;确保其易于编写、修改和维护。针对这种情况&#xff0c;推荐使用多线程处理与事件驱动编程结合的方式&#xff0c;或者直接采用并行循环&…

Python创建可点击网页

继完成静态网页后&#xff0c;引入java script制作动态交互网页&#xff0c;交互逻辑就两个按钮&#xff0c;用于学习网页编程。 from flask import Flask, render_template_string, jsonify, requestapp Flask(__name__)app.route(/) def index():# 使用内联模板渲染一个简单…

【静态分析】软件分析课程实验A2-常量传播和Worklist求解器

Tai-e官网&#xff1a; 概述 | Tai-e 参考&#xff1a; https://www.cnblogs.com/gonghr/p/17979609 -------------------------------------------------------- 1 作业导览 为 Java 实现常量传播算法。实现一个通用的 worklist 求解器&#xff0c;并用它来解决一些数据…

超便捷备忘录共享方法 文字文件都可共享

在这个信息爆炸的时代&#xff0c;备忘录已成为我们生活中不可或缺的小助手。它记录着我们的工作计划、待办事项、灵感闪现&#xff0c;甚至是那些温馨的家庭琐事。然而&#xff0c;当我们在不同的设备间穿梭&#xff0c;如何在手机、电脑&#xff0c;甚至是不同品牌的手机之间…

理解机器学习中的类别不平衡问题

大家好&#xff0c;实际世界的数据集通常是杂乱的,当不同类别之间的样本分布不均匀时&#xff0c;就会出现类别不平衡。或者说&#xff0c;某些类别的样本比其他类别多得多。例如&#xff0c;考虑一个信用卡违约数据集&#xff0c;信用卡违约是一个相对较少发生的事件&#xff…

速锐得深入解析吉利几何CAN总线数据通信网络的拓扑层级框架技术

在现代汽车工业中&#xff0c;车辆的电子控制单元&#xff08;ECU&#xff09;之间的通信至关重要。这种通信大多通过控制器局域网络&#xff08;CAN&#xff09;总线实现&#xff0c;它是德国BOSCH公司于20世纪80年代初开发的一种串行数据通信协议。随着技术的不断进步&#x…

读人工智能时代与人类未来笔记01_重塑人类社会秩序

1. AlphaZero 1.1. 2017年年底&#xff0c;由谷歌旗下DeepMind公司开发的人工智能程序AlphaZero击败了当时世界上最强大的国际象棋程序Stockfish 1.1.1. AlphaZero对Stockfish的百场战绩是28胜72平0负&#xff0c;可以说获得了压倒性的胜利 1.1.2. …

外贸网站优化为什么要布置内部链接?如何优化内链?

对于外贸人来说&#xff0c;外贸网站优化已然成为推广引流、获取询盘的重要的一环。而除了外部优化&#xff0c;内部链接&#xff0c;实际上也是提升SEO效果和用户体验的重要手段。为什么这么说呢&#xff1f;内部链接又该如何进行优化呢&#xff1f;接下来一起来看一看吧~ 什…

安卓手机数据恢复全攻略:从备份到专业软件一网打尽!

随着科技的飞速发展&#xff0c;我们的生活中越来越离不开手机。然而&#xff0c;在使用手机的过程中&#xff0c;我们可能会遇到数据丢失的问题。对于安卓手机用户来说&#xff0c;如何有效地恢复丢失的数据是一个值得探讨的问题。本文将为您介绍安卓手机数据恢复的全攻略&…

市政道路工程乙级资质的资质维护策略

人员配置与持续培训&#xff1a; 定期审查技术人员队伍&#xff0c;确保注册工程师及其他专业技术人员的数量、资质和专业分布满足乙级资质要求。实施持续的职业发展计划&#xff0c;包括内部培训、外部进修和专业认证&#xff0c;提升团队的技术水平和行业知识。 工程业绩记录…

java中简单工厂模式,工厂方法模式和抽象工厂模式的区别和联系?

在Java中&#xff0c;简单工厂模式、工厂方法模式和抽象工厂模式都是创建型设计模式&#xff0c;用于解耦对象的创建过程&#xff0c;提高系统的灵活性和可扩展性。它们之间既有相似之处也有明显的区别&#xff1a; 简单工厂模式&#xff08;Simple Factory Pattern&#xff0…

【Git LFS】Git管理大文件

要在Git中有效地管理大文件&#xff0c;你可以使用Git Large File Storage (Git LFS)。Git LFS 是一个Git扩展&#xff0c;它能够更好地处理大文件的版本控制&#xff0c;而不会使仓库变得庞大和笨重。下面是如何设置和使用Git LFS的步骤&#xff1a; 1. 安装 Git LFS 首先&a…