面经——java后端第一天

1.java和c++区别是?

性能⾼:C++ 是⼀种编译型语⾔,可以⽣成⾼效的机器代码,所以在处理⼤量数据和对计算效率有要求的应⽤场景下具有优势;

C++ 和 Java 是两种编程语言,它们之间有很多区别,主要包括以下几个方面:

  1. 编程范式:

    • C++ 是一门多范式编程语言,支持面向对象编程、过程式编程、泛型编程等。
    • Java 主要是一门面向对象编程语言,更强调面向对象的思想。
  2. 编译与解释:

    • C++ 是一种编译型语言,源代码需要经过编译器编译成机器代码,然后才能运行。
    • Java 是一种半编译半解释型语言。Java源代码首先被编译成字节码,然后在Java虚拟机(JVM)上解释执行。
  3. 内存管理:

    • C++ 提供了显式内存管理,程序员需要手动分配和释放内存,这也可能导致内存泄漏和悬挂指针等问题。
    • Java 使用自动内存管理,具有垃圾回收机制,程序员无需手动管理内存。
  4. 平台依赖性:

    • C++ 代码通常具有更高的平台依赖性,因为生成的机器码直接运行在特定平台上。
    • Java 通过JVM实现了平台无关性,Java程序可以在支持JVM的各种平台上运行。
  5. 安全性:

    • Java 具有更严格的安全性,防止了一些常见的安全漏洞,如缓冲区溢出。
    • C++ 更容易受到一些安全漏洞的威胁,需要程序员更加谨慎地编写代码。
  6. 标准库:

    • C++ 的标准库(STL)提供了丰富的数据结构和算法,适用于各种编程任务。
    • Java 有自己的标准库,也提供了许多数据结构和类,用于不同的应用。
  7. 性能:

    • 通常情况下,C++ 在性能上具有优势,尤其是对于需要高计算效率的应用。
    • Java 的性能通常较 C++ 稍差,但由于虚拟机的不断改进,性能差距逐渐减小。

总之,C++ 和 Java 在语言设计、应用场景和特性上有很多不同之处,开发人员在选择语言时需要考虑项目的具体需求和优劣势。

2.Go的轻量级线程在企业级应用中有什么体现?

并发能⼒强:Go 具有轻量级的线程(goroutine)和基于消息传递的通道(channel),可以⽅便地实现并发编程;

Go 语言的轻量级线程(goroutine)和通道(channel)使并发编程变得更加容易和高效,这些特性在企业级应用中有许多体现。以下是一个简单的示例,演示如何在 Go 中使用 goroutine 和 channel 处理并发任务。

假设你有一个需求:从多个数据源获取数据,然后对这些数据进行处理并将结果发送到另一个地方。你可以使用 goroutine 和 channel 来并发执行这些任务。

package mainimport ("fmt""time"
)func fetchData(source string, ch chan string) {// 模拟从数据源获取数据的操作time.Sleep(2 * time.Second)data := fmt.Sprintf("Data from %s", source)ch <- data // 将数据发送到通道
}func main() {startTime := time.Now()ch := make(chan string) // 创建一个字符串通道dataSources := []string{"Source1", "Source2", "Source3"}// 启动多个goroutine并发获取数据for _, source := range dataSources {go fetchData(source, ch)}// 从通道接收数据for range dataSources {result := <-chfmt.Println(result)}elapsedTime := time.Since(startTime)fmt.Printf("Total time taken: %s\n", elapsedTime)
}

在这个示例中,我们创建了一个字符串通道 ch,然后使用 go 关键字启动了三个并发的 fetchData 函数,每个函数模拟从不同数据源获取数据。主函数中通过 <-ch 从通道中接收数据,并将数据打印出来。

这个示例演示了如何在 Go 中使用轻量级的 goroutine 实现并发任务,每个 goroutine 在不同的数据源上执行任务,然后通过 channel 将结果发送给主函数。这种并发编程模型在处理大规模数据、高并发的情况下非常有用,可以充分利用多核 CPU 和提高应用性能。

对比 JAVA

在 Java 中,你也可以执行类似的并发任务,但与 Go 不同,Java 的并发编程通常涉及线程和对象之间的协同工作,因此可能需要更多的代码和复杂性。下面是一个使用 Java 的示例,执行与前面 Go 示例相似的任务。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class JavaConcurrentExample {public static void main(String[] args) {long startTime = System.currentTimeMillis();int numDataSources = 3;// 创建一个固定大小的线程池ExecutorService executor = Executors.newFixedThreadPool(numDataSources);// 使用阻塞队列来存储结果BlockingQueue<String> results = new ArrayBlockingQueue<>(numDataSources);for (int i = 1; i <= numDataSources; i++) {final int sourceNumber = i;executor.submit(() -> {// 模拟从数据源获取数据的操作try {Thread.sleep(2000); // 模拟2秒的延迟} catch (InterruptedException e) {e.printStackTrace();}String data = "Data from Source" + sourceNumber;results.offer(data); // 将数据放入队列});}// 关闭线程池executor.shutdown();try {executor.awaitTermination(10, TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}while (!results.isEmpty()) {String result = results.poll();System.out.println(result);}long elapsedTime = System.currentTimeMillis() - startTime;System.out.println("Total time taken: " + elapsedTime + " ms");}
}

这个 Java 示例使用 ExecutorService 创建一个线程池,然后启动三个任务来模拟从不同数据源获取数据。获取到的数据存储在 BlockingQueue 中,并且主线程等待所有任务完成后从队列中获取结果。

需要注意的是,Java 中的并发编程需要更多的线程管理和同步机制,相对于 Go 来说可能显得更加复杂。此外,Java 的线程和内存管理通常需要更多的注意和谨慎,以避免潜在的问题。

总之,虽然 Java 可以实现类似的任务,但在某些情况下,Go 提供了更加轻量级和直观的并发编程模型,适用于大规模的并发处理。但 Java 依然是一种强大的编程语言,用于处理复杂的企业级应用。

相关问题

这段 Java 代码演示了如何使用线程池和阻塞队列来处理并发任务。让我来详细解释一下代码的原理:

  1. ExecutorService 和 线程池:

    • ExecutorService 是 Java 中用于管理线程池的接口。
    • ExecutorService 提供了一个高层次的接口,用于管理和执行多线程任务。线程池在企业应用中非常有用,因为它们可以提高多线程任务的效率。
    • 在示例中,Executors.newFixedThreadPool(numDataSources) 创建了一个固定大小的线程池,该线程池可以同时执行 numDataSources 个任务。
  2. 阻塞队列(BlockingQueue):

    • 阻塞队列是一种线程安全的队列,它支持在队列为空时阻塞等待数据或在队列已满时阻塞等待空间可用。
    • 在这个示例中,BlockingQueue<String> results = new ArrayBlockingQueue<>(numDataSources) 创建了一个阻塞队列,用于存储任务的结果。
    • 阻塞队列是一种常用的并发编程工具,它可以帮助协调多个线程之间的数据交换。
  3. 线程执行任务:

    • 使用 executor.submit(...) 提交任务到线程池中执行。在示例中,我们提交了三个任务,每个任务都是一个独立的线程。
    • 每个任务模拟从数据源获取数据的操作,然后将数据存储在 results 阻塞队列中。
  4. 关闭线程池:

    • executor.shutdown() 用于关闭线程池。一旦线程池被关闭,它将不再接受新的任务。
    • executor.awaitTermination(10, TimeUnit.SECONDS) 是一个阻塞调用,等待线程池中的任务完成。它会等待最多 10 秒,如果超过 10 秒仍有任务未完成,它将继续等待,直到所有任务完成。
  5. 结果提取:

    • 最后,我们从 results 阻塞队列中提取任务的结果,并将其打印出来。

这段代码的主要目的是展示如何使用线程池来管理并发任务,以及如何使用阻塞队列来存储任务的结果。它在多线程环境中执行三个任务,模拟了从不同数据源获取数据并将数据存储在一个结果队列中的过程。

trycatch 是 Java 中用于处理异常的语句块。try 中的代码可能会引发异常,而 catch 中的代码用于捕获和处理异常。在这个示例中,我们使用 trycatch 来处理可能的 InterruptedException 异常,该异常可能在等待任务完成时抛出。
在上面的代码中,Thread.sleep(2000)是用来模拟从数据源获取数据的操作时的延迟。Thread.sleep方法可以让当前线程休眠指定的时间,以模拟等待数据源响应或执行其他任务时的等待时间。

在Java中,Thread.sleep方法声明了一个可能会抛出InterruptedException的异常。这是因为线程在休眠的过程中,其他线程可能会中断休眠的线程。如果在休眠过程中发生了中断操作,InterruptedException异常将被抛出。因此,在使用Thread.sleep时,为了处理潜在的异常情况,需要进行异常捕获。

在上述代码中,捕获InterruptedException异常后,使用e.printStackTrace()语句打印异常信息。这是一个良好的做法,因为它可以帮助开发人员在出现异常时追踪问题,即使这里的异常处理方式比较简单,只是打印异常信息。在实际应用中,你可以选择更复杂的异常处理逻辑,如记录日志、释放资源等。总之,捕获InterruptedException异常是一种良好的编程实践,以确保在线程休眠时能够处理潜在的中断情况,使代码更加健壮和可靠。

这段代码演示了一种并发编程模型,使用线程池来管理任务的执行,同时通过阻塞队列实现了线程间的协同工作,以便高效地处理并发任务。

阻塞队列理解:

阻塞队列就像一个装满食物的餐盘,多个人(线程)在等待吃饭。这里有两个关键点:

  1. 线程安全:阻塞队列是线程安全的,就像餐盘上有食物一样,多个人可以安全地同时尝试拿取食物。

  2. 阻塞等待:如果餐盘是空的,那么拿取食物的人会被阻塞,直到有食物可用。类似地,如果队列是空的,尝试从中获取数据的线程将会被阻塞,直到有数据可用。同样,如果餐盘已满,那么添加食物的人也会被阻塞,直到有更多的空间。这就是阻塞队列的核心特性:在合适的时候等待,以保证数据安全和协同工作。

阻塞队列在多线程编程中非常有用,因为它可以帮助线程之间安全地交换数据,而不需要复杂的手动同步机制。阻塞队列的主要作用是确保线程之间的协同工作,使它们能够有序、安全地访问共享的数据,从而避免了竞态条件和死锁等问题。这就像多个人在餐桌前有秩序地等待食物,以确保每个人都能满足自己的需求,而没有人会争抢或饥饿。

因此,阻塞队列是多线程编程中的一个强大工具,它使线程协同工作变得更加容易和安全。

如果没有阻塞队列,多线程编程可能会变得更加复杂和容易出现问题,因为线程需要手动实现同步和协调机制。以下是一些可能发生的情况:

  1. 竞态条件:多个线程同时访问共享数据,没有适当的同步机制,可能导致数据竞争和不确定的行为。

  2. 死锁:如果线程之间的同步不正确,可能导致死锁,其中线程相互等待对方释放资源,导致所有线程被阻塞。

  3. 饥饿:某些线程可能会因为没有机会获得共享资源而陷入饥饿状态,无法继续执行。

  4. 编程复杂性:手动实现同步和协调逻辑通常需要复杂的编程,容易出现错误,调试困难。

  5. 性能问题:手动实现同步逻辑可能会导致性能开销,如频繁的锁竞争。

阻塞队列作为一种线程安全的数据结构,提供了可靠的数据共享和协同工作机制。它消除了竞态条件、死锁和饥饿的问题,减少了编程复杂性,提高了性能。因此,阻塞队列是多线程编程中的有力工具,使并发编程更加容易和安全。

基本逻辑和代码示例

以下是阻塞队列的基本逻辑和相关代码示例:

  1. 初始化阻塞队列: 创建一个阻塞队列对象,通常通过实现类来实例化。Java提供了多种阻塞队列实现,例如ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueue等。

  2. 添加数据到队列: 在生产者线程中,使用put()offer()方法将数据添加到队列。如果队列已满,put()将会阻塞等待空间可用,而offer()会返回false

  3. 从队列获取数据: 在消费者线程中,使用take()poll()方法从队列中获取数据。如果队列为空,take()将会阻塞等待数据到来,而poll()会返回null

下面是一个简单的示例代码,演示了使用ArrayBlockingQueue实现的阻塞队列:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class BlockingQueueExample {public static void main(String[] args) {// 创建一个容量为3的ArrayBlockingQueueBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);// 生产者线程new Thread(() -> {try {queue.put("Data 1");queue.put("Data 2");queue.put("Data 3");System.out.println("Producer: Data produced");} catch (InterruptedException e) {e.printStackTrace();}}).start();// 消费者线程new Thread(() -> {try {Thread.sleep(2000); // 模拟等待String data = queue.take();System.out.println("Consumer: Data consumed - " + data);} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}

在上述示例中,生产者线程使用put()方法将数据添加到队列,而消费者线程使用take()方法从队列中获取数据。如果队列已满或为空,相应的操作将会阻塞等待,以确保线程安全的数据交换。

这个示例展示了阻塞队列的典型逻辑,它在多线程编程中非常有用,可以协调线程之间的数据交换,从而确保数据的安全性和可靠性。

3、JDK 动态代理和 CGLIB 动态代理的区别是什么?

Java JDK动态代理是一种代理模式,它允许你在运行时为一个或多个接口动态创建代理实例,而不需要提前知道这些接口的具体实现类。在这个示例中,我们将使用JDK动态代理来为UserService接口创建代理,并在代理方法中添加日志记录。

下面是代码的逐行注释和代码原理:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 1. 创建一个接口,这将是代理的目标接口
interface UserService {void saveUser(String username);
}// 2. 创建一个实现目标接口的具体类
class UserServiceImpl implements UserService {public void saveUser(String username) {System.out.println("Saving user: " + username);}
}// 3. 创建一个实现InvocationHandler接口的代理处理器类
class LogHandler implements InvocationHandler {private Object target;// 4. 构造函数,接收目标对象public LogHandler(Object target) {this.target = target;}// 5. invoke方法在代理对象上调用被代理的方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 6. 在代理方法前执行日志记录System.out.println("Entering method: " + method.getName());// 7. 调用被代理对象的方法Object result = method.invoke(target, args);// 8. 在代理方法后执行日志记录System.out.println("Exiting method: " + method.getName());// 9. 返回方法的结果return result;}
}public class JdkDynamicProxyExample {public static void main(String[] args) {// 10. 创建目标对象UserService userService = new UserServiceImpl();// 11. 创建代理处理器对象,并传入目标对象LogHandler logHandler = new LogHandler(userService);// 12. 使用Proxy类的newProxyInstance方法创建代理对象//    参数包括类加载器、目标接口、和代理处理器UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),logHandler);// 13. 调用代理对象的方法proxy.saveUser("Alice");}
}

在这个示例中,JDK动态代理通过Proxy.newProxyInstance方法创建了代理对象,该代理对象实现了UserService接口,并在代理方法中通过LogHandler处理器实现了日志记录。当调用代理对象的saveUser方法时,代理处理器的invoke方法会执行前后的日志记录,然后调用目标对象的方法。这允许你在不修改目标对象的情况下添加额外的行为,例如日志记录。

代理模式是什么?是设计模式吗?创建代理对象的目的是什么?

Java JDK动态代理是一种代理模式,它允许你在运行时为一个或多个接口动态创建代理实例,而不需要提前知道这些接口的具体实现类。在这个示例中,我们将使用JDK动态代理来为UserService接口创建代理,并在代理方法中添加日志记录。

下面是代码的逐行注释和代码原理:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 1. 创建一个接口,这将是代理的目标接口
interface UserService {void saveUser(String username);
}// 2. 创建一个实现目标接口的具体类
class UserServiceImpl implements UserService {public void saveUser(String username) {System.out.println("Saving user: " + username);}
}// 3. 创建一个实现InvocationHandler接口的代理处理器类
class LogHandler implements InvocationHandler {private Object target;// 4. 构造函数,接收目标对象public LogHandler(Object target) {this.target = target;}// 5. invoke方法在代理对象上调用被代理的方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 6. 在代理方法前执行日志记录System.out.println("Entering method: " + method.getName());// 7. 调用被代理对象的方法Object result = method.invoke(target, args);// 8. 在代理方法后执行日志记录System.out.println("Exiting method: " + method.getName());// 9. 返回方法的结果return result;}
}public class JdkDynamicProxyExample {public static void main(String[] args) {// 10. 创建目标对象UserService userService = new UserServiceImpl();// 11. 创建代理处理器对象,并传入目标对象LogHandler logHandler = new LogHandler(userService);// 12. 使用Proxy类的newProxyInstance方法创建代理对象//    参数包括类加载器、目标接口、和代理处理器UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),logHandler);// 13. 调用代理对象的方法proxy.saveUser("Alice");}
}

在这个示例中,JDK动态代理通过Proxy.newProxyInstance方法创建了代理对象,该代理对象实现了UserService接口,并在代理方法中通过LogHandler处理器实现了日志记录。当调用代理对象的saveUser方法时,代理处理器的invoke方法会执行前后的日志记录,然后调用目标对象的方法。这允许你在不修改目标对象的情况下添加额外的行为,例如日志记录。

代理模式是一种设计模式,它允许一个对象(代理对象)代表另一个对象(真实对象)来控制对该对象的访问。代理模式是一种结构型设计模式,它通常涉及到创建一个代理对象,该代理对象充当客户端和真实对象之间的中介,从而可以在访问真实对象时添加额外的功能或控制。

代理模式的主要目的包括:

  1. 控制访问:代理对象可以限制客户端对真实对象的直接访问,以便进行权限检查或认证。

  2. 增加额外功能:代理对象可以在调用真实对象的方法前后添加额外的操作,如日志记录、性能监视、事务管理等。

  3. 延迟加载:代理对象可以延迟加载真实对象,只有在需要时才实际创建和初始化真实对象,从而提高性能。

  4. 实现懒加载:代理对象可以在真正需要时才加载真实对象,以减少资源消耗。

  5. 实现远程代理:代理对象可以代表远程对象,允许客户端通过网络访问远程服务。

  6. 实现虚拟代理:代理对象可以代表大对象,只在需要时加载和显示部分数据,以减少内存占用。

代理模式通常涉及两种角色:

  1. 客户端:客户端是与代理对象交互的类,它不直接访问真实对象,而是通过代理对象来访问真实对象。

  2. 代理对象:代理对象实现了与真实对象相同的接口或继承相同的父类,它充当了客户端和真实对象之间的中介。代理对象通常包含对真实对象的引用,并根据需要在调用真实对象的方法前后执行一些额外操作。

  3. 真实对象:真实对象是代理对象的背后实现,它执行真正的业务逻辑。客户端在访问真实对象时通过代理对象进行间接访问。

代理模式有多种变体,包括静态代理、动态代理、远程代理、虚拟代理等,可以根据不同的需求选择合适的变体。

总之,代理模式是一种有助于控制对对象访问并添加额外功能的设计模式,它在很多应用中都有广泛的用途,如日志记录、权限管理、性能监视等。

远程代理实例

代理模式在现实世界中的应用非常广泛。一个常见的例子是网络代理服务器。网络代理服务器充当客户端和互联网服务器之间的中介,用于执行各种功能,如缓存、安全过滤、加密、日志记录等。下面是一个简单的 Java 示例,演示如何创建一个网络代理服务器的代理模式应用:

// 接口:定义网络访问的方法
interface Network {void connect(String serverHost);
}// 真实网络连接类
class RealNetwork implements Network {@Overridepublic void connect(String serverHost) {System.out.println("连接到服务器:" + serverHost);}
}// 代理类:网络代理服务器
class NetworkProxy implements Network {private Network realNetwork;private String proxyServer;public NetworkProxy(String proxyServer) {this.proxyServer = proxyServer;this.realNetwork = new RealNetwork();}@Overridepublic void connect(String serverHost) {if (isBlocked(serverHost)) {System.out.println("访问被拒绝,代理服务器阻止连接到 " + serverHost);} else {realNetwork.connect(proxyServer); // 通过代理服务器连接System.out.println("代理服务器连接到 " + serverHost);}}private boolean isBlocked(String serverHost) {// 在实际应用中,可以实现更复杂的逻辑来检查是否阻止访问特定服务器return serverHost.contains("blocked");}
}public class ProxyPatternExample {public static void main(String[] args) {NetworkProxy proxy = new NetworkProxy("proxy-server.com");proxy.connect("example.com");  // 正常连接proxy.connect("blocked-server.com");  // 阻止连接}
}

在这个示例中,我们定义了一个 Network 接口,包括 connect 方法。然后,我们创建了一个 RealNetwork 类来实现真正的网络连接,以及一个 NetworkProxy 类来实现代理服务器。

当客户端通过代理服务器连接到不同的服务器时,代理服务器会检查服务器的主机名是否被阻止。如果被阻止,代理服务器将拒绝连接。否则,它会通过代理服务器连接到目标服务器。

这个示例演示了代理模式在网络代理服务器中的应用,充分说明了代理模式如何充当中介来控制访问和添加额外功能。请注意,在实际网络代理中,代理服务器通常会执行更多复杂的操作,如安全认证、缓存和数据压缩。

如果还是不明白的宝宝们,可以看看下面的类比法:
当使用JDK动态代理时,你需要了解它的实现原理,即如何通过InvocationHandler接口、Proxy类和反射机制来创建动态代理。让我通过一个比喻和示例来解释它:

比喻
想象一家快递公司(代理工厂),你作为客户(客户端代码),需要发送包裹(方法调用请求)。这家快递公司没有自己的送货员(实际的业务逻辑类),但他们可以雇佣送货员来为你送货。为了使用这家快递公司,你需要提供一份送货清单(接口),快递公司会根据这份清单雇佣合适的送货员来完成送货任务。

示例

  1. 客户(客户端代码)需要一个送货服务,但他们不关心送货员是谁。
  2. 客户(客户端代码)提供了一份送货清单(接口),描述了送货员应该提供的送货服务(方法)。
  3. 代理工厂(JDK动态代理)通过使用这份清单(接口)来雇佣一个送货员(代理对象),并为送货员指定了一个送货地址(InvocationHandler)。
  4. 当客户(客户端代码)需要送货服务时,他们将请求发给快递公司(代理对象),代理对象将请求传递给送货员(实际的业务逻辑类)。
  5. 送货员(实际的业务逻辑类)执行送货任务,并在送货前后添加特殊服务,如记录送货时间。
  6. 送货员(实际的业务逻辑类)完成送货任务后,代理对象返回结果给客户(客户端代码)。

这个比喻可以帮助你理解JDK动态代理的工作原理。客户端代码定义了接口,代理工厂根据这个接口创建代理对象,并将方法调用委托给实际的业务逻辑类。代理对象在调用前后可以执行额外的操作,就像快递公司可以在送货前后添加特殊服务。

以下是Java代码示例,演示了如何使用JDK动态代理:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 定义接口,代表送货服务
interface DeliveryService {void deliverPackage(String address);
}// 实际的送货服务类,实现了DeliveryService接口
class RealDeliveryService implements DeliveryService {public void deliverPackage(String address) {System.out.println("Delivering package to " + address);}
}// 代理处理器,负责在送货前后提供特殊服务
class SpecialServiceHandler implements InvocationHandler {private DeliveryService realDeliveryService;public SpecialServiceHandler(DeliveryService realDeliveryService) {this.realDeliveryService = realService;}// 这个方法在代理对象的方法调用时被调用public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 在送货前提供特殊服务System.out.println("Providing special service before delivery.");// 调用实际送货服务类的相应方法Object result = method.invoke(realDeliveryService, args);// 在送货后提供特殊服务System.out.println("Providing special service after delivery.");return result;}
}public class ProxyExample {public static void main(String[] args) {// 创建实际的送货服务对象DeliveryService realService = new RealDeliveryService();// 创建代理处理器,传入实际的送货服务对象InvocationHandler handler = new SpecialServiceHandler(realService);// 创建代理对象,将会在送货前后提供特殊服务DeliveryService proxy = (DeliveryService) Proxy.newProxyInstance(DeliveryService.class.getClassLoader(),new Class[] { DeliveryService.class },handler);// 使用代理对象调用送货方法proxy.deliverPackage("123 Main Street");}
}

在这个示例中,DeliveryService接口充当了送货清单,RealDeliveryService是实际的送货员,SpecialServiceHandler是代理对象的送货员,代理对象在送货前后提供特殊服务。客户端代码通过代理对象调用deliverPackage方法,代理对象在实际的业务逻辑类上执行这个方法,然后添加特殊服务。这就是JDK动态代理的实现方式。

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

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

相关文章

计算机网络常识

文章目录 1、HTTP2、HTTP状态码1xx&#xff08;信息性状态码&#xff09;&#xff1a;2xx&#xff08;成功状态码&#xff09;&#xff1a;3xx&#xff08;重定向状态码&#xff09;&#xff1a;4xx&#xff08;客户端错误状态码&#xff09;&#xff1a;5xx&#xff08;服务器…

ActiveMQ

ActiveMQ 安装 下载网址&#xff1a;ActiveMQ 一定要和自己安装的jdk版本匹配&#xff0c;不然会报错 下载到本地之后解压缩 有可能端口号被占用 解除端口号占用&#xff0c;参考&#xff1a;Windows_端口被占用 打开cmd 查询所有的端口号 netstat -nao查询指定端口号 n…

数组元素逆序交换

数组元素逆序交换 例如&#xff1a; 输入&#xff1a; 1,2,3,4,5 输出&#xff1a; 5,4,3,2,1 代码&#xff1a; #include<stdio.h> #define N 5 int main() {int a[N], i, temp;for (i 0; i < N; i) {scanf("%d", &a[i]);}for (i 0; i < N…

C语言选择排序

1.选择排序(Selection sort) 选择排序是简单直观的排序算法。 基本思想&#xff1a;从首元素开始&#xff0c;首元素与它后面的所有元素进行比较&#xff0c;找到数列中最小的元素&#xff0c;与首元素值交换。然后下一个元素与它后面的元素比较&#xff0c;得到第二小的元素…

对Linux线程的理解(什么是线程,线程的创建终止等待分离,线程互斥,Linux常见的锁,线程同步),两万字总结,有这一篇文章就够了!

文章目录 一、什么是线程1.线程是怎样描述的2.线程与进程的区别3.线程的优缺点4.理解Linux的轻量级进程 二、Linux线程控制1.线程创建:pthread_create()2.线程终止:pthread_exit()3.线程等待:pthread_join()4.分离线程:pthread_detach() 三、Linux线程互斥1.互斥量2.线程安全与…

移动端自动化-Appium元素定位

文章目录 Appium元素定位第一类&#xff1a;属性定位第二类&#xff1a;路径定位 常见问题理解appium server 和 appium inspector 以及 appium-python-client的关系 appium是跨平台的&#xff0c;支持OSX&#xff0c;Windows以及Linux系统。它允许测试人员在不同的平台&#x…

我在Vscode学OpenCV 初步接触

OpenCV是一个开源的计算机视觉库&#xff0c;可以处理图像和视频数据。它包含了超过2500个优化过的算法&#xff0c;用于对图像和视频进行处理&#xff0c;包括目标识别、面部识别、运动跟踪、立体视觉等。OpenCV支持多种编程语言&#xff0c;包括C、Python、Java等&#xff0c…

在HTML当中引入Vue控件,以element-ui为例

前情&#xff1a;需要实现一个同时满足按天、按周、按月选择的时间选择器&#xff0c;但是以HTML为基础写的都不太满足我的要求&#xff0c;要么只能按天选择&#xff0c;要么就是想选择久远的时间得点很久&#xff0c;除非自己写捷径&#xff0c;所以就看上了element-ui的这个…

动静分离技术

一、HAproxy 动静分离 1、概念&#xff1a; HAproxy 动静分离技术是一种用于优化 Web 服务器性能和提高用户体验的策略&#xff0c;它通过将动态内容和静态内容分别路由到不同的后端服务器来实现&#xff0c;减轻服务器负载&#xff0c;提高网站的响应速度。 动态内容包括由…

MySQL篇---第二篇

系列文章目录 文章目录 系列文章目录一、数据库的事务二、索引是什么三、SQL优化手段有哪些一、数据库的事务 什么是事务?: 多条sql语句,要么全部成功,要么全部失败。 事务的特性: 数据库事务特性:原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)、持久性 (Du…

SylixOS BSP开发(七)

实现系统调试信息打印接口 当系统出错时或者使用内核日志时会输出一些打印信息&#xff0c;这最终都是调用到bspLib.c中的bspDebugMsg 这个接口来实现的&#xff0c;所以我们在开发BSP时&#xff0c;第一个要做的工作就是实现这个接口。 一般的调试信息都是通过串口来输出的&am…

linux-tools-$(uname -r) linux-headers-$(uname -r)工具安装:

linux-tools-$(uname -r) linux-headers-$(uname -r)工具安装: ebpfebpf-virtual-machine:~$ sudo apt-get install linux-tools-$(uname -r) [sudo] ebpf 的密码&#xff1a; 正在读取软件包列表... 完成 正在分析软件包的依赖关系树... 完成 正在读取状态信息... 完成 linux…

经典目标检测神经网络 - RCNN、SSD、YOLO

文章目录 1. 目标检测算法分类2. 区域卷积神经网络2.1 R-CNN2.2 Fast R-CNN2.3 Faster R-CNN2.4 Mask R-CNN2.5 速度和精度比较 3. 单发多框检测&#xff08;SSD&#xff09;4. YOLO 1. 目标检测算法分类 目标检测算法主要分两类&#xff1a;One-Stage与Two-Stage。One-Stage与…

虚拟机Ubuntu下运行vue-element-admin项目

一.环境搭建 1.安装nodejs sudo apt install nodejs 安装完成后&#xff0c;查看对应的版本号 nodejs -v没有问题&#xff0c;会输出对应版本号&#xff0c;我这里是10.19.0 v10.19.0 2.安装npm sudo apt install npm安装完成查看对应的版本号&#xff0c;确认OK npm -…

linux中nginx配置https

一、版本适配 版本一定要适配&#xff0c;否则会报错各种参数定位不到不识别的错误&#xff0c;以下是版本适配信息&#xff0c;各位观客自行按照以下信息匹配版本。 Nginx 1.11.5及以上版本与OpenSSL 1.0.2及以上版本兼容。Nginx 1.15.2及以上版本与OpenSSL 1.1.1及以上版本兼…

代码随想录图论 第四天| 827.最大人工岛 127. 单词接龙

代码随想录图论 第四天 | 827.最大人工岛 127. 单词接龙 一、827.最大人工岛 题目链接&#xff1a;https://leetcode.cn/problems/making-a-large-island/ 思路&#xff1a; class Solution {int[][] position {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};int dfs(int[][] grid, i…

JavaScript_Pig Game保存当前分数

上个文章我们基本上完成了摇色子和切换当前玩家的功能。 现在我们开始写用户选择不再摇骰子的话&#xff0c;我们将用户的当前分数存入到持有分数中&#xff01; ● 首先我们应该利用一个数组去存储两个用户的分数 const scores [0, 0];● 接着我们利用数组来对分数进行累…

用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程

&#x1f9f8;注&#xff1a;不要看我的文件多&#xff0c;那是我的其他项目&#xff0c;这个项目所用的文件我会全部用红框框起来&#xff0c;没框的部分不用管&#xff0c;前端两个文件&#xff0c;后端一个文件 &#x1f4dc; 目录 首先&#xff0c;定义前后端交互接口 然…

为什么axios会有params和data两个参数

不知道大家有没有过这种感觉&#xff0c;突然一个问题百思不得其解&#xff0c;然后突然有一天就明白了。然后就感觉这个问题原来这么简单&#xff0c;本来想记录下来&#xff0c;但是又感觉这么简单的问题记录下来没啥意义。但是回过头来想一想&#xff0c;这个问题之前其实困…

由浅入深C系列八:如何高效使用和处理Json格式的数据

如何高效使用和处理JSON格式的数据 问题引入关于CJSON示例代码头文件引用处理数据 问题引入 最近的项目在用c处理后台的数据时&#xff0c;因为好多外部接口都在使用Json格式作为返回的数据结构和数据描述&#xff0c;如何在c中高效使用和处理Json格式的数据就成为了必须要解决…