享元模式(Flyweight Pattern)是一种用于性能优化的设计模式,它通过共享尽可能多的相似对象来减少内存使用,尤其是在大量对象的情况下非常有效。这个模式是在对象数量多而对象状态大部分可共享的情况下实现的。
享元模式的核心在于区分内部状态和外部状态:
结构组成
优点
缺点
使用场景
实际应用举例
在Java中经常遇到的面试题其实就是Integer中用到的享元模式了。
Integer 缓存机制
为什么使用享元模式
特别注意
这里我们考虑实现一个简单的线程池来演示享元模式。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
// 任务接口
interface Task {
void execute();
}
// 工作线程类
class WorkerThread extends Thread {
private final BlockingQueue taskQueue;
public WorkerThread(BlockingQueue taskQueue) {
this.taskQueue = taskQueue;
}
@Override
public void run() {
while (true) {
try {
Task task = taskQueue.take();
task.execute();
} catch (InterruptedException e) {
break; // 允许线程退出
}
}
}
}
// 线程池类
class ThreadPool {
private final BlockingQueue taskQueue = new LinkedBlockingQueue<>();
private final WorkerThread[] workerThreads;
public ThreadPool(int numberOfThreads) {
workerThreads = new WorkerThread[numberOfThreads];
for (int i = 0; i < numberOfThreads; i++) {
workerThreads[i] = new WorkerThread(taskQueue);
workerThreads[i].start();
}
}
public void execute(Task task) {
try {
taskQueue.put(task);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 关闭线程池
public void shutdown() {
for (WorkerThread worker : workerThreads) {
worker.interrupt();
}
}
}
// 客户端使用
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPool pool = new ThreadPool(3); // 创建一个有3个线程的线程池
// 提交任务
for (int i = 0; i < 10; i++) {
int taskNo = i;
pool.execute(() -> System.out.println("执行任务 " + taskNo + " 在线程 " + Thread.currentThread().getName()));
}
pool.shutdown(); // 关闭线程池
}
}
组合模式
组合模式(Composite Pattern)是一种用于表示和管理对象的部分-整体层次结构的设计模式。其核心作用是允许客户端以统一的方式对待单个对象和对象的组合。这种模式特别适用于那些对象可以生成树形结构的场景,其中每个节点都可能是一个单独的对象或者是由多个对象组成的一个集合。
核心概念
作用和好处
实际应用场景
在文件系统中,我们有两种基本类型的项:文件和文件夹。文件是基本的数据单位,而文件夹可以包含多个文件或其他文件夹。这正好符合组合模式的结构,即部分-整体的层次结构。在这个例子中,文件和文件夹可以被视为树形结构的不同节点。
组件接口:文件系统项
叶子:文件
组合:文件夹
// 组件接口
interface FileSystemItem {
int getSize();
}
// 叶子:文件
class File implements FileSystemItem {
private int size;
public File(int size) {
this.size = size;
}
@Override
public int getSize() {
return size;
}
}
// 组合:文件夹
class Directory implements FileSystemItem {
private List children = new ArrayList<>();
public void add(FileSystemItem item) {
children.add(item);
}
@Override
public int getSize() {
int totalSize = 0;
for (FileSystemItem item : children) {
totalSize += item.getSize();
}
return totalSize;
}
}
// 客户端代码
public class FileSystemDemo {
public static void main(String[] args) {
File file1 = new File(100);
File file2 = new File(200);
Directory directory = new Directory();
directory.add(file1);
directory.add(file2);
System.out.println("文件夹的大小: " + directory.getSize()); // 输出文件夹的总大小
}
}
场景分析
委派模式是一种行为型设计模式,它允许对象将某些任务委托给其他对象来处理。在这种模式中,一个对象包含对另一个对象的引用,并将客户端的请求委派给这个对象处理。委派模式基于组合的原则,可以看作是一种特殊情况的静态代理,但与代理模式的目的不同。
工作机制
目的和作用
与代理模式的区别
虽然委派模式在结构上与静态代理模式类似,但它们的目的不同。代理模式通常用于控制对对象的访问,而委派模式则专注于任务的分发和委托。
委派模式最经典的两个使用就是:
1. Java Servlet的DispatcherServlet
在Spring MVC框架中,DispatcherServlet充当前端控制器(Front Controller),它接收所有的HTTP请求,并根据请求的URL将其委派给相应的处理器(Controller)。这里的DispatcherServlet使用委派模式来决定由哪个控制器来处理传入的请求,并将请求细节委派给选定的控制器。
2. JDK中的ClassLoader机制
Java的ClassLoader机制也是一个委派模式的例子。当请求加载一个类时,ClassLoader会首先委派给它的父类加载器尝试加载该类,只有在父类加载器无法加载时,它才尝试自己加载该类。这种委派机制确保了Java类加载的正确性和安全性。
这里我们就以DispatcherServlet为例,写一个简单的demo。
要模拟Java Servlet中的DispatcherServlet行为,我们可以创建一个简化版的前端控制器(Front Controller)模式,它将HTTP请求委派给不同的处理器(handler)。在这个简化的示例中,我们将模拟处理不同类型的请求(例如GET和POST请求)的逻辑。
实现思路
定义处理器接口:创建一个处理器接口,定义处理请求的方法。
实现具体的处理器:实现该接口,创建针对不同请求的处理器。
创建前端控制器:创建一个DispatcherServlet类,它决定哪个处理器应该处理给定的请求。
路由逻辑:DispatcherServlet根据请求类型(这里简化为一个字符串)来调用相应的处理器。
处理器接口
interface Handler {
void handleRequest(String requestType);
}
// 处理GET请求的处理器
class GetRequestHandler implements Handler {
@Override
public void handleRequest(String requestType) {
System.out.println("Handling GET request");
}
}
// 处理POST请求的处理器
class PostRequestHandler implements Handler {
@Override
public void handleRequest(String requestType) {
System.out.println("Handling POST request");
}
}
class DispatcherServlet {
private Map handlerMap = new HashMap<>();
public DispatcherServlet() {
handlerMap.put("GET", new GetRequestHandler());
handlerMap.put("POST", new PostRequestHandler());
}
public void doDispatch(String requestType) {
Handler handler = handlerMap.get(requestType.toUpperCase());
if (handler != null) {
handler.handleRequest(requestType);
} else {
System.out.println("Unsupported request type: " + requestType);
}
}
}
在DispatcherServlet中,我们预先注册了处理GET和POST请求的处理器。根据请求类型的不同,调用相应的处理器。
4. 模拟请求
public class DispatcherDemo {
public static void main(String[] args) {
DispatcherServlet dispatcher = new DispatcherServlet();
// 模拟不同类型的请求
dispatcher.doDispatch("GET");
dispatcher.doDispatch("POST");
dispatcher.doDispatch("PUT"); // 将输出"Unsupported request type: PUT"
}
}
在这个简化的示例中,我们实现了一个基本的前端控制器模式。DispatcherServlet充当请求的中央入口,根据请求的类型委派给相应的处理器。这种模式的优点在于集中了请求处理的决策逻辑,使得添加新的请求处理器或修改现有逻辑变得更加简单,同时也使系统的各个部分更加解耦。
这里其实会觉得委派模式和策略模式很像,但是他们还是有一些区别。
委派模式
策略模式
区别总结
装饰器模式是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持原类方法签名完整性的前提下提供了额外的功能。
基本原理:
优点:
缺点:
Java框架中的装饰器模式应用
装饰器模式的关键在于“包装”原有对象,并提供额外的功能,而不是修改原有对象本身。对于 HttpServletRequestWrapper 和 HttpServletResponseWrapper 的情况,它们并不是直接修改原始的 HttpServletRequest 或 HttpServletResponse 对象,而是通过“包装”这些对象,然后在调用它们的方法时提供额外的或改变的行为。
让我们用一个简单的例子来说明这一点。假设我们想要创建一个 HttpServletRequestWrapper 的子类,该子类能够修改请求参数:
步骤 1: 创建自定义的 HttpServletRequestWrapper
javaCopy code
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.HashMap;
import java.util.Map;
public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final Map modifiableParameters;
public CustomHttpServletRequestWrapper(HttpServletRequest request, Map additionalParams) {
super(request);
modifiableParameters = new HashMap<>();
modifiableParameters.putAll(request.getParameterMap());
modifiableParameters.putAll(additionalParams);
}
@Override
public String getParameter(String name) {
if (modifiableParameters.containsKey(name)) {
return modifiableParameters.get(name)[0];
}
return super.getParameter(name);
}
@Override
public Map getParameterMap() {
return modifiableParameters;
}
// 其他方法...
}
这个自定义的 CustomHttpServletRequestWrapper 类扩展了 HttpServletRequestWrapper 并允许传入一个包含额外请求参数的 Map。它重写了 getParameter 和 getParameterMap 方法,以返回修改后的请求参数。
步骤 2: 在过滤器中使用自定义的 HttpServletRequestWrapper
javaCopy code
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class CustomFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 添加或修改请求参数
Map additionalParams = new HashMap<>();
additionalParams.put("newParam", new String[]{"value1", "value2"});
// 使用自定义的 HttpServletRequestWrapper
CustomHttpServletRequestWrapper customRequest = new CustomHttpServletRequestWrapper(httpRequest, additionalParams);
// 将自定义的请求对象传递给过滤器链
chain.doFilter(customRequest, response);
}
// 其他方法...
}
在这个例子中,CustomFilter 使用了 CustomHttpServletRequestWrapper 来修改传入的 HTTP 请求。它添加了一个新的请求参数 newParam。当请求通过过滤器链时,任何后续的过滤器或 Servlet 都将看到这个修改后的请求。
如何生效
代理模式是一种结构型设计模式,它为其他对象提供了一种代理(或称为替身),以控制对这个对象的访问。在代理模式中,代理对象插入到实际对象和访问者之间,作为中介,执行某些操作(如访问控制、延迟初始化、日志记录等),然后将调用传递给实际对象。代理模式的主要作用如下:
静态代理
静态代理是指在编译期间就创建好代理类的一种代理模式。在这种模式下,代理类和目标对象实现相同的接口或继承相同的类,代理类持有目标对象的引用,并在调用目标对象的方法前后可以执行一些附加操作。
以一个简单的例子来说明,假设有一个接口和一个实现了这个接口的类,我们将创建一个代理类来增强这个实现类的功能:
javaCopy code
// 接口
interface Service {
void doSomething();
}
// 实现类
class RealService implements Service {
public void doSomething() {
System.out.println("Doing something in RealService");
}
}
// 静态代理类
class StaticProxy implements Service {
private Service realService;
public StaticProxy(Service realService) {
this.realService = realService;
}
public void doSomething() {
System.out.println("Before RealService doSomething");
realService.doSomething();
System.out.println("After RealService doSomething");
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Service service = new StaticProxy(new RealService());
service.doSomething();
}
}
优点
适用场景
缺点
静态代理模式在应用较为简单且目标对象稳定的情况下是非常有用的,但在需要大量动态代理的方法或目标对象经常变化的情况下,可能会导致代码的冗余和维护难度增加。在这种情况下,可以考虑使用动态代理模式。
JDK动态代理是Java提供的一种动态生成代理对象的机制,它允许开发者在运行时创建代理对象,而无需为每个类编写具体的代理实现。JDK动态代理主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。
实现原理
JDK动态代理工作原理是利用InvocationHandler来关联代理对象和实际对象,当通过代理对象调用方法时,这个调用会被转发到InvocationHandler的invoke方法。在invoke方法内,开发者可以在调用实际对象的方法前后添加自定义逻辑。
下面是一个使用JDK动态代理的例子:
package blossom.project.designmode.proxy;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import sun.misc.ProxyGenerator;
// 接口
interface Service {
void doSomething();
}
// 实现类
class RealService implements Service {
public void doSomething() {
System.out.println("Doing something in RealService");
}
}
// 调用处理器
class ServiceInvocationHandler implements InvocationHandler {
//被代理的实际对象
private Object target;
public ServiceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理前置处理
System.out.println("Before method " + method.getName());
//被代理对象的方法被调用
Object result = method.invoke(target, args);
//代理后置处理
System.out.println("After method " + method.getName());
return result;
}
}
// 使用示例
public class JdkProxy {
public static void main(String[] args) {
RealService realService = new RealService();
//创建代理对象 然后调用代理对象的方法即可
Service proxyService = (Service) Proxy.newProxyInstance(
RealService.class.getClassLoader(),
new Class>[] {Service.class},
new ServiceInvocationHandler(realService)
);
proxyService.doSomething();
//生成字节码文件
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Service.class});
// 保存到文件系统
try (FileOutputStream out = new FileOutputStream("D://desktop//" + "$Proxy.class")) {
out.write(classFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个例子中,Service是一个接口,RealService是它的一个实现。ServiceInvocationHandler是一个InvocationHandler,在invoke方法中添加了在执行方法前后的逻辑。我们使用Proxy.newProxyInstance方法创建了RealService的代理对象。
优点
缺点
适用场景
JDK动态代理适用于需要代理的对象实现了一个或多个接口的场景。它在AOP(面向切面编程)、事务代理、日志记录等场景中非常有用。
面试的时候有被问到JDK动态代理的底层实现源码,所以这里我也简单的介绍一下它的源码实现。
运行我们上面的代码并且进入debug状态,可以看到如下状态。

首先是我们的proxyService的内部的target就是我们一开始new出来的那个需要被代理的对象。
但是最后的这个proxyService可以发现它的类型是
P
r
o
x
y
,我们知道,使用
Proxy,我们知道,使用
Proxy,我们知道,使用代表的是他是一个子类或者内部类的意思。而这里用
P
r
o
x
y
开头代表的是他是一个代理类,在外面看不到只能在内存中看到。而这里的
0
代表的是
J
d
k
自增的一个序号。这里我们知道,动态代理的底层其实就是为我们再运行的时候生成了一个类,那么我们只需要看看这个类的代码,我们就能大概知道他是如何实现的了。接下来我们来获取动态代理生成的类的字节码文件,运行上面的代码:这里特别注意需要用
J
D
K
8
哦。此时我们会得到一个
c
l
a
s
s
文件,不用打开看了,你啥也看不懂的。
!
[
i
m
a
g
e
.
p
n
g
]
(
h
t
t
p
s
:
/
/
i
m
g
−
b
l
o
g
.
c
s
d
n
i
m
g
.
c
n
/
i
m
g
c
o
n
v
e
r
t
/
3791
d
546
d
c
83969
a
5
b
5910
f
d
b
e
3
a
b
e
4
a
.
p
n
g
)
然后我们使用
J
A
D
来进行反编译,如果没有下载
J
A
D
的可以下载一个,链接如下:
[
J
A
D
反编译工具
]
(
h
t
t
p
s
:
/
/
v
a
r
a
n
e
c
k
a
s
.
c
o
m
/
j
a
d
/
)
!
[
i
m
a
g
e
.
p
n
g
]
(
h
t
t
p
s
:
/
/
i
m
g
−
b
l
o
g
.
c
s
d
n
i
m
g
.
c
n
/
i
m
g
c
o
n
v
e
r
t
/
5041
a
0213554
b
37
c
99
a
4
b
f
f
b
c
515
f
41
b
.
p
n
g
)
之后就得到了反编译以后的文件了。上面的代码编译后如下:其中可以看到我们的被代理的方法,调用的是
s
u
p
e
r
.
h
.
i
n
v
o
k
e
方法,那么我们就得了解一下这个
h
到底是什么了。我们进入到
P
r
o
x
y
类的源码进行查看。因为这里
Proxy开头代表的是他是一个代理类,在外面看不到只能在内存中看到。而这里的0代表的是Jdk自增的一个序号。 这里我们知道,动态代理的底层其实就是为我们再运行的时候生成了一个类,那么我们只需要看看这个类的代码,我们就能大概知道他是如何实现的了。 接下来我们来获取动态代理生成的类的字节码文件,运行上面的代码: 这里特别注意需要用JDK8哦。 此时我们会得到一个class文件,不用打开看了,你啥也看不懂的。  然后我们使用JAD来进行反编译,如果没有下载JAD的可以下载一个,链接如下: [JAD反编译工具](https://varaneckas.com/jad/)  之后就得到了反编译以后的文件了。 上面的代码编译后如下: 其中可以看到我们的被代理的方法,调用的是super.h.invoke方法,那么我们就得了解一下这个h到底是什么了。我们进入到Proxy类的源码进行查看。 因为这里
Proxy开头代表的是他是一个代理类,在外面看不到只能在内存中看到。而这里的0代表的是Jdk自增的一个序号。这里我们知道,动态代理的底层其实就是为我们再运行的时候生成了一个类,那么我们只需要看看这个类的代码,我们就能大概知道他是如何实现的了。接下来我们来获取动态代理生成的类的字节码文件,运行上面的代码:这里特别注意需要用JDK8哦。此时我们会得到一个class文件,不用打开看了,你啥也看不懂的。然后我们使用JAD来进行反编译,如果没有下载JAD的可以下载一个,链接如下:[JAD反编译工具](https://varaneckas.com/jad/)之后就得到了反编译以后的文件了。上面的代码编译后如下:其中可以看到我们的被代理的方法,调用的是super.h.invoke方法,那么我们就得了解一下这个h到底是什么了。我们进入到Proxy类的源码进行查看。因为这里Proxy0是继承了Proxy的,所以进入Proxy类。

可以发现,这里的h应该就是我们传入的InvocationHadnler的对象了。也就是我们的代理类。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
import blossom.project.designmode.proxy.Service;
import java.lang.reflect.*;
public final class $Proxy0 extends Proxy
implements Service
{
public $Proxy0(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final void doSomething()
{
try
{
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m3 = Class.forName("blossom.project.designmode.proxy.Service").getMethod("doSomething", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
catch(NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
那么到此为止我们就大概知道JDK动态代理的实现了。
我们现在来手写一个JDK动态代理。
CGLIB(Code Generation Library)是一个功能强大的高性能代码生成库,它允许在运行时动态地扩展Java类和实现Java接口。在代理模式中,CGLIB常被用于实现动态代理,特别是当被代理的对象没有实现任何接口时。
实现原理
CGLIB动态代理通过继承要代理的类并在运行时生成子类来实现。CGLIB通过拦截所有对代理对象的方法调用,将这些调用转发到一个方法拦截器(Method Interceptor)中,从而实现代理逻辑。
要使用CGLIB,需要引入CGLIB库。如果你使用Maven,可以添加以下依赖:
cglib
cglib
3.3.0
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
// 被代理的类(不需要实现接口)
class RealService {
public void doSomething() {
System.out.println("Doing something in RealService");
}
}
// 方法拦截器
class ServiceMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method " + method.getName());
return result;
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealService.class);
enhancer.setCallback(new ServiceMethodInterceptor());
RealService proxyService = (RealService) enhancer.create();
proxyService.doSomething();
}
}
优点
缺点
适用场景
CGLIB代理非常适合以下场景:
在Spring框架中,当AOP需要代理没有实现接口的Bean时,通常会使用CGLIB代理。
我们知道项目开发的时候一般会用到多数据源,而多数据源的实现也是依赖于我们的代理模式。
对于多数据源的切换,我们得考虑如下几点:
按照如上的几点,我们可以写出如下的代码:
首先创建一个动态数据源类,其中使用ThreadLocal来实现线程隔离切换数据源。
public class DynamicDataSource {
public final static String DEFAULE_DATA_SOURCE = "DB_2022";
private final static ThreadLocal local = new ThreadLocal();
private DynamicDataSource(){}
public static String get(){return local.get();}
public static void reset(){
local.set(DEFAULE_DATA_SOURCE);
}
public static void set(String source){local.set(source);}
public static void set(int year){local.set("DB_" + year);}
}
然后我们实现InvocationHandler接口。
要做的就是在执行具体的被代理增强的方法之前切换一下数据源即可。
public class UserServiceDynamicProxy implements MyInvocationHandler {
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
Object proxyObj;
public Object getInstance(Object proxyObj) {
this.proxyObj = proxyObj;
Class> clazz = proxyObj.getClass();
return MyProxy.newProxyInstance(new MyClassLoader(),clazz.getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(args[0]);
Object object = method.invoke(proxyObj,args);
after();
return object;
}
private void after() {
System.out.println("Proxy after method");
//还原成默认的数据源
DynamicDataSource.reset();
}
//target 应该是订单对象Order
private void before(Object target) {
try {
//进行数据源的切换
System.out.println("Proxy before method");
//约定优于配置
Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据");
DynamicDataSource.set(dbRouter);
}catch (Exception e){
e.printStackTrace();
}
}
}
public class DbRouteProxyTest {
public static void main(String[] args) {
try {
User user = new User();
// user.setCreateTime(new Date().getTime());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date date = sdf.parse("2023/03/01");
user.setCreateTime(date.getTime());
IUserService orderService = (IUserService)new UserServiceDynamicProxy().getInstance(new UserService());
// IOrderService orderService = (IOrderService)new UserServiceStaticProxy(new UserService());
orderService.createUser(user);
}catch (Exception e){
e.printStackTrace();
}
}
}
JDK动态代理和CGLIB动态代理是Java中实现动态代理的两种主要方法,它们各有特点和适用场景。下面详细比较这两种动态代理方式:
JDK动态代理
底层实现
优点
缺点
CGLIB动态代理
底层实现
优点
缺点
总结
在实际的软件开发中,具体选择哪种代理方式取决于具体的应用场景和需求。在Spring框架中,当AOP代理需要被应用时,如果目标对象实现了接口,默认会使用JDK动态代理;如果目标对象没有实现接口,或者显式配置使用CGLIB代理,则会使用CGLIB。
外观模式(Facade Pattern)是一种使用频率较高的结构型设计模式,其主要目标是提供一个统一的接口来访问子系统中的一群接口。外观定义了一个更高层次的接口,使得子系统更容易使用。
优点:
缺点:
使用场景:
其实外观模式是我们开发过程中没有意识到但是用的最多的一种方法。比如我们的SpringMVC的三层开发结构就符合外观模式。
下面我简单列举一个我项目中的多OSS服务代码,它依靠与外观模式和策略模式。
首先是先展示策略模式的结构

在这里我让他们实现一个公共的接口,用来提供对应的OSS服务。
之后,我对外提供一个OssService用来统一提供Oss服务。

这样子再需要使用到Oss服务的地方引入依赖,然后使用通用接口,就可以完成文件的上传,而外部并不需要了解文件上传的细节,只需要根据参数提示进行使用即可,

适配器模式是一种结构型设计模式,它允许不兼容的接口之间进行交互。适配器模式通常用于使现有类的接口与其他类兼容,而不需要修改其源代码。
适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。
类适配器模式是适配器模式的一种实现方式,它通过继承来实现适配。在类适配器模式中,适配器直接继承被适配者,并实现目标接口。由于Java不支持多重继承,因此类适配器模式在Java中不常见,但可以在支持多重继承的语言(如C++)中实现。
实现机制
假设我们有一个已存在的类Adaptee和一个目标接口Target,我们的目标是使Adaptee适应Target接口。
// 被适配者
class Adaptee {
public void specificRequest() {
System.out.println("Specific request");
}
}
// 目标接口
interface Target {
void request();
}
// 类适配器
class ClassAdapter extends Adaptee implements Target {
@Override
public void request() {
specificRequest();
}
}
在这个例子中,ClassAdapter继承自Adaptee并实现了Target接口。在request()方法中,适配器调用了Adaptee的specificRequest()方法,从而使Adaptee能够适应Target接口。
public class AdapterDemo {
public static void main(String[] args) {
Target target = new ClassAdapter();
target.request(); // 输出: Specific request
}
}
类适配器适用于以下场景:
优点
缺点
对象适配器是适配器模式的另一种实现方式,它使用组合而非继承来实现适配功能。在对象适配器模式中,适配器包含一个被适配者的引用,并实现目标接口。
实现机制
假设我们有一个现存的类Adaptee和一个目标接口Target,我们的目标是使Adaptee适应Target接口。
// 被适配者
class Adaptee {
public void specificRequest() {
System.out.println("Specific request");
}
}
// 目标接口
interface Target {
void request();
}
// 对象适配器
class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
在这个例子中,ObjectAdapter实现了Target接口,并包含了一个Adaptee类型的成员变量。在request()方法中,适配器调用了Adaptee的**specificRequest()**方法。
public class AdapterDemo {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new ObjectAdapter(adaptee);
target.request(); // 输出: Specific request
}
}
对象适配器适用于以下场景:
优点
缺点
对象适配器是适配器模式的一种常用实现方式,它提供了良好的灵活性和扩展性,同时符合组合优于继承的设计原则。在实际应用中,对象适配器模式因其灵活性和应用广泛性而被广泛采用。
接口适配器模式,也称为缺省适配器模式,是适配器模式的一种特殊形式。这种模式常用于当你需要实现一个接口,但只需要其中一部分方法时。
实现机制
假设有一个接口TargetInterface,它定义了多个方法。我们不想在适配器类中实现所有这些方法,只对其中一部分感兴趣。
// 目标接口,包含多个方法
interface TargetInterface {
void methodA();
void methodB();
void methodC();
}
// 接口适配器/缺省适配器,为接口提供默认实现
abstract class InterfaceAdapter implements TargetInterface {
@Override
public void methodA() { }
@Override
public void methodB() { }
@Override
public void methodC() { }
}
// 具体实现类,只需覆盖感兴趣的方法
class ConcreteAdapter extends InterfaceAdapter {
@Override
public void methodA() {
System.out.println("Method A implementation");
}
// 方法B和C使用默认实现(无操作)
}
在这个例子中,InterfaceAdapter为TargetInterface中的所有方法提供了默认实现。ConcreteAdapter类继承自InterfaceAdapter,只重写了methodA,而methodB和methodC保留了默认实现。
public class AdapterDemo {
public static void main(String[] args) {
TargetInterface adapter = new ConcreteAdapter();
adapter.methodA(); // 输出: Method A implementation
adapter.methodB(); // 无输出
adapter.methodC(); // 无输出
}
}
适用场景
接口适配器适用于以下场景:
优点
缺点
接口适配器模式是一种实用的设计模式,特别适合于那些拥有多个方法的接口,但实现类只需要一部分方法的场景。通过提供一个中间的抽象类来简化实现过程,这种模式使得代码更加清洁和可维护。
在实际的应用中,处理不同登录方式通常涉及不同的流程和用户交互。例如,常规的用户名和密码登录可能直接通过HTTP请求处理,而第三方登录(如微信、QQ、抖音)可能涉及到OAuth授权、重定向到第三方登录页面、扫码等流程。为了处理这些不同的登录方式,我们可以在后端设计一个统一的入口(Controller),然后根据登录方式的不同,委托给不同的处理器(适配器)来完成具体的登录流程。
以下是基于适配器模式的设计和代码示例:
interface LoginService {
void login(Map parameters);
}
// 用户名密码登录服务
class UsernamePasswordLoginService implements LoginService {
@Override
public void login(Map parameters) {
String username = parameters.get("username");
String password = parameters.get("password");
System.out.println("Username and password login: " + username);
// 实现具体的登录逻辑
}
}
// 微信登录服务
class WeChatLoginService implements LoginService {
@Override
public void login(Map parameters) {
String token = parameters.get("token");
System.out.println("WeChat login with token: " + token);
// 实现微信登录逻辑,可能涉及重定向到微信登录页面等
}
}
// QQ登录服务
class QQLoginService implements LoginService {
// 类似地实现QQ登录逻辑
// ...
}
// 抖音登录服务
class TikTokLoginService implements LoginService {
// 类似地实现抖音登录逻辑
// ...
}
@RestController
@RequestMapping("/auth")
public class LoginController {
private Map loginServices;
public LoginController() {
// 初始化不同的登录服务
loginServices = new HashMap<>();
loginServices.put("usernamePassword", new UsernamePasswordLoginService());
loginServices.put("wechat", new WeChatLoginService());
loginServices.put("qq", new QQLoginService());
loginServices.put("tiktok", new TikTokLoginService());
}
@PostMapping("/login")
public ResponseEntity> login(@RequestParam String type, @RequestBody Map parameters) {
LoginService loginService = loginServices.get(type);
if (loginService == null) {
return ResponseEntity.badRequest().body("Unsupported login type: " + type);
}
loginService.login(parameters);
return ResponseEntity.ok().body("Login successful for type: " + type);
}
}
在这个示例中,LoginController定义了一个**/auth/login端点来处理登录请求。它支持多种登录类型,每种类型对应一个特定的LoginService实现。根据请求参数type**,Controller将委托给相应的LoginService来处理具体的登录逻辑。
桥接模式(Bridge Pattern)是一种结构型设计模式,用于将抽象部分与其实现部分分离,使它们都可以独立地变化。这种模式通过提供一个桥接结构,来实现两个或多个不同类或接口的合作。
优点
缺点
使用场景
我将使用操作系统(OS)和数据库(DB)作为不同的维度来构建这个例子。
首先,我们定义一个数据库接口,它将是我们桥接模式中的 Implementor。
public interface Database {
void connect();
void executeQuery(String query);
}
然后,我们为具体的数据库实现这个接口,这些类是 ConcreteImplementors。
// MySQL 数据库实现
public class MySQLDatabase implements Database {
@Override
public void connect() {
System.out.println("Connecting to MySQL database...");
}
@Override
public void executeQuery(String query) {
System.out.println("Executing MySQL Query: " + query);
}
}
// Oracle 数据库实现
public class OracleDatabase implements Database {
@Override
public void connect() {
System.out.println("Connecting to Oracle database...");
}
@Override
public void executeQuery(String query) {
System.out.println("Executing Oracle Query: " + query);
}
}
接下来,我们定义一个操作系统类(OperatingSystem),它将持有数据库的引用。这是桥接模式中的 Abstraction 部分。
public abstract class OperatingSystem {
// 持有数据库的引用
protected Database database;
public OperatingSystem(Database database) {
this.database = database;
}
public abstract void start();
public abstract void stop();
// 使用数据库执行查询
public void executeDatabaseQuery(String query) {
database.connect();
database.executeQuery(query);
}
}
现在,我们为特定的操作系统创建类,这些类是 RefinedAbstraction。
// Windows 操作系统
public class WindowsOS extends OperatingSystem {
public WindowsOS(Database database) {
super(database);
}
@Override
public void start() {
System.out.println("Starting Windows OS...");
}
@Override
public void stop() {
System.out.println("Stopping Windows OS...");
}
}
// Linux 操作系统
public class LinuxOS extends OperatingSystem {
public LinuxOS(Database database) {
super(database);
}
@Override
public void start() {
System.out.println("Starting Linux OS...");
}
@Override
public void stop() {
System.out.println("Stopping Linux OS...");
}
}
最后,我们创建一个客户端类来演示桥接模式的使用。
public class BridgePatternDemo {
public static void main(String[] args) {
// 创建一个 MySQL 数据库实例
Database mysql = new MySQLDatabase();
// 创建一个 Windows 操作系统实例,使用 MySQL 数据库
OperatingSystem windows = new WindowsOS(mysql);
windows.start();
windows.executeDatabaseQuery("SELECT * FROM users");
windows.stop();
// 创建一个 Oracle 数据库实例
Database oracle = new OracleDatabase();
// 创建一个 Linux 操作系统实例,使用 Oracle 数据库
OperatingSystem linux = new LinuxOS(oracle);
linux.start();
linux.executeDatabaseQuery("SELECT * FROM employees");
linux.stop();
}
}
这个简单的例子展示了桥接模式的核心概念:通过分离抽象和实现,使得两者可以独立发展,增加了代码的灵活性和可维护性。
其实我总结一下,可以发现桥接模式的使用方法有点像多对多的情况。
比如我们有一个OS,然后这个OS其实有多种不同的数据库可以选择去使用,那么这里如果我们让OS去实现(也可以用继承,如果用继承的话就要将DB类型同时都继承同一个父类)DB的接口,那么我们每次新出现一个DB类型,就要去修改OS,这很明显违法了开闭原则。
因此,我们尽可能的考虑将变化DB作为OS的属性,这样子就将DB和OS进行了关联,OS想要使用那个DB,只要在创建的时候进行选择即可。
桥接模式特别适合用于要将两个不同维度的东西用于建立链接。
如果还有一点抽象,我们就来举个例子:
Java中最经典的例子就是,我们的JDBC的例子。
我们知道,JDBC只是提供了一套连接数据库的接口,并没有提供具体的实现,而具体的实现则由厂商进行实现。
我们一般会编写如下代码来链接数据库:
//建桥
Class.forName("com.mysql.cj.jdbc.Driver");
//用桥
DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","root");
而就是这一段代码就使用了我们的桥接模式。
首先MySQL的Driver实现了java的Driver然后调用了DriverManager方法。

那么实际DriverManager就只是把你传进来的Driver进行封装保存。

然后实际上我们这里就是调用了MySQL的Driver的connection方法。

因此,MySQL的Driver就通过这样子的DriverManager与JDBC建立好了链接,从而我们可以方便的使用数据库。
这里我们的抽象维度其实就是我们的DriverManager,而我们的MySQL的Driver就是具体的实现维度,MySQL这边改变了没关系,只要你依旧符合我的DM的规范即可。从而使得他们都可以相互独立的变换。