设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结,其中最出名的当属 Gang of Four (GoF) 的分类了,他们将设计模式分类为 23 种经典的模式,根据用途我们又可以分为三大类,分别为创建型模式、结构型模式和行为型模式。

结构型模式(Structural Patterns):主要用于描述对象之间的组合关系,包括多个不同的模式,如“代理模式”、“适配器模式”、“桥接模式”、“装饰者模式”、“外观模式”、“享元模式”和“组合模式”等。这些模式可以帮助我们更好地设计程序结构,提高代码灵活性和可维护性。

参考文章:

设计模式——代理模式

【设计模式】结合Tomcat源码,分析外观模式/门面模式的特性和应用场景

代理模式

代理模式的基本介绍

代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象

代理模式是结构型设计模式(用于描述对象之间的组合关系)。

好处:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能

被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象(代理时对目标对象进行安全控制)

我们并不希望客户端直接调用目标对象,而是通过代理对象对目标对象实现安全控制或增强功能。所以客户端直接依赖代理对象,代理对象依赖目标对象.

代理模式有不同的形式,主要有三种:静态代理、JDK动态代理、Cglib 代理

静态代理:

目标对象与代理对象实现租同的接口或继承相同的父类,在编译时生成代理对象。

目标对象实现代理接口,代理对象实现并聚合代理接口,重写方法编写增强后逻辑。

JDK动态代理:

通过Java反射机制在运行时动态地在内存中生成代理对象。 目标对象需要实现代理接口。目标对象实现代理接口,代理工厂通过Proxy类的静态方法newProxyInstance(),利用反射机制返回代理对象实例。

newProxyInstance()三个参数:目标对象的类加载器、目标对象的接口、实现InvocationHandler接口并重写invoke()方法,编写代理对象逻辑。

应用Spring AOP采用了动态代理的方式,在运行时动态的创建代理对象来实现增强。

Cglib 代理:

内存中构建一个子类对象以实现对目标对象功能扩展。目标对象不需要实现代理接口。底层通过ASM框架转换字节码并生成新的类。

代理工厂类实现MethodInterceptor接口并重写intercept()方法编写代理逻辑,通过cglib包Enhancer类设置父类字节码文件和创建子类对象来返回代理对象实例。

ASM框架是一个强大的Java字节码操作框架,可以让程序员通过代码生成和转换现有字节码来操作Java类。ASM可以直接生成字节码,也可以通过访问现有字节码来修改它。

静态代理

静态代理在使里时,需要定义接口或者父类,目标对象与代理对象实现租同的接口或继承相同的父类

实现方式:目标对象实现代理接口,代理对象实现并聚合代理接口,重写方法编写增强后逻辑。

优缺点

  • 优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展

  • 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。耦合性较高,一旦接口增加方法,目标对象与代理对象都要维护

案例

  1. 定义一个接口:ITeacherDao
  2. 目标对象TeacherDao实现接口ITeacherDao
  3. 使用静态代理方式,就需要在代理对象TeacherDaoProxy中也实现ITeacherDao
  4. 调用的时候通过调用代理对象的方法来调用目标对象
  5. 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 代理接口
public interface ITeacherDao {
void teach(); // 代理对象和原始对象重写这个方法
}
// 目标对象,即被代理对象。实现代理借口
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师授课中...");
}
}
// 代理对象。实现代理借口
public class TeacherDaoProxy implements ITeacherDao {
private ITeacherDao iTeacherDao; // 接口引用
public TeacherDaoProxy(ITeacherDao iTeacherDao) {
this.iTeacherDao = iTeacherDao;
}
@Override
public void teach() {
System.out.println("准备授课...");
iTeacherDao.teach();
System.out.println("结束授课...");
}
}

客户端代理

1
2
3
4
5
6
//创建被代理对象
TeacherDao teacherDao = new TeacherDao();
//创建代理对象,聚合被代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
//通过代理对象,调用被代理对象的方法
teacherDaoProxy.teach();

JDK动态代理

Java中的动态代理是一种机制,它通过在程序运行时动态地生成代理对象,并在代理对象上进行方法调用,实现对目标对象方法的拦截与控制。动态代理是代理设计模式的一种实现方式,与静态代理不同的是,它不需要显示地编写代理类来代理被代理对象,而是通过Java反射机制在运行时动态生成代理类和代理对象。

实现方法:

目标对象实现代理接口,代理工厂通过Proxy类的静态方法newProxyInstance(),利用反射机制返回代理对象实例。

newProxyInstance()三个参数:目标对象的类加载器、目标对象的接口、实现InvocationHandler接口并重写invoke()方法,编写代理对象逻辑。

  • 代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
  • 代理对象的生成,是利用 JDK 的 APl,动态的在内存中构建代理对象
  • 动态代理也叫做:JDK 代理、接口代理

JDK提供了java.lang.reflect.Proxy类,可以通过它创建基于接口的动态代理对象。使用Proxy类的newProxyInstance静态方法,该方法需要接收三个参数:

1
static Object newProxylnstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 代理接口和目标对象同上,目标对象需要实现代理接口
// ITeacherDao与TeacherDao同上
// 代理工厂
public class TeacherFactory {
// 目标对象
private Object target;
public TeacherFactory(Object target) {
this.target = target;
}
public Object newProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理授课开始...");
Object returnVal = method.invoke(target, args);
System.out.println("JDK代理授课结束...");
return returnVal;
}
});
}
}

创建代理对象

1
2
3
4
5
6
7
8
//创建目标对象
ITeacherDao target = new TeacherDao();
//给目标对象,创建代理对象,可以转成 ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
// proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
System.out.println("proxyInstance=" + proxyInstance.getClass());
//通过代理对象,调用目标对象的方法
proxyInstance.teach();

其中几个参数

1)ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法固定
2)Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方法确认类型
3)InvocationHandler h:事情处理,执行目标对象的方法时触发事情处理器方法,把当前执行的目标对象方法作为参数传入

Cglib 代理

Cglib 代理

静态 代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——这就是 Cglib 代理

Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将 Cglib 代理归属到动态代理。

使用方法:

代理工厂类实现MethodInterceptor接口并重写intercept()方法编写代理逻辑,通过cglib包的Enhancer类设置父类字节码文件和创建子类对象来返回代理对象实例。

Cglib包

是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截。

Cglib 包的底层是通过使用ASM框架转换字节码并生成新的类

ASM框架

一个强大的Java字节码操作框架,可以让程序员通过代码生成和转换现有字节码来操作Java类。ASM可以直接生成字节码,也可以通过访问现有字节码来修改它。我们可以使用ASM来生成新的类、新的方法、字段、注解等。同时,ASM还允许我们在运行时改变现有的Java类的字节码,从而实现动态的Java类修改,例如添加方法、添加字段等。

在 AOP 编程中如何选择代理模式:

  • 目标对象需要实现接口,用 JDK 代理
  • 目标对象不需要实现接口,用 Cglib 代理

案例:

  1. 引入cglib的 jar 文件(asm.jar,asm-commons.jar,asm-tree.jar,cglib-2.2jar)
  2. 在内存中动态构建子类,注意**代理的类不能为final**,否则报错java.lang.IllegalArgumentException
  3. 标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 被代理对象
public class TeacherDao {
public String teach() {
System.out.println("老师授课中...");
return "Good";
}
}
// 代理工厂类,实现MethodInterceptor 接口
public class ProxyFactory implements MethodInterceptor {
// 目标对象
private Object target;
// 构造函数
public ProxyFactory(Object target) {
this.target = target;
}
// 返回代理对象实例。不是静态方法
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer(); // 1、创建工具类
enhancer.setSuperclass(target.getClass()); // 2、设置父类
enhancer.setCallback(this); // 3、设置回调函数
return enhancer.create(); // 4、创建子类对象,即代理对象
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
System.out.println("cglib代理开始...");
Object retVal = method.invoke(target, args);
System.out.println("cglib代理结束...");
return retVal;
}
}

代理模式的变体(应用场景)

几种常见的代理模式一几种变体

  • 防火墙代理:内网通过代理穿透防火墙,实现对公网的访问
  • 缓存代理:比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok;如果取不到资源,再到公网或者数据库取,然后缓存
  • 远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息(RPC)
  • 同步代理:主要使用在多线程编程中,完成多线程间同步工作

外观模式

经典的组建家庭影院流程

问题描述:

  • 组建家庭影院过程:
    1. 直接用遥控器:统筹各设备开关
    2. 开爆米花机
    3. 放下屏幕
    4. 开投影仪
    5. 开音响
    6. 开DVD,选dvd
    7. 去拿爆米花
    8. 调暗灯光
    9. 播放
    10. 观影结束后,关闭各种设备
  • 需求:组建一个电影院,要求完成上述流程

传统方式解决影院管理

实现方案:客户端直接调用各流程

客户端直接使用各个子系统

各个电器类(子系统):包括打开、关闭等功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// DVD类
public class DVDPlayer{
// 播放
public void play(){ System.out.println("播放DVD"); }
// 打开
public void on(){ System.out.println("打开DVD"); }
// 关闭
public void off(){ System.out.println("关闭DVD"); }
}
// 投影仪
public class Projector{
public void on() {
System.out.println("打开投影仪");
}
}
// ...

客户端

1
2
3
4
5
6
7
8
9
// 客户端测试类
public class ClientTest{
public static void main(String[] args){
// 1、创建对象:创建各电器类对象
// 2、打开电器:调用各对象的on()方法
// 3、放映:调用DVDPlayer对象的play方法
// 4.关闭:关闭所有电器
}
}

优缺点和改进思路

优点:比较好理解,简单易操作

缺点:

  • 调用过程混乱:客户端创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程
  • 无法直接维护子系统:不利于在 ClientTest 中去维护对子系统的操作

改进思路分析: 抽取界面类,定义一个高层接口(界面类),给子系统接口提供一致的界面(例如ready,play,pause,end等方法),屏蔽内部子系统的细节。

外观模式

外观模式/门面模式

外观模式(Facade):也叫过程模式/门面模式,是一种结构型设计模式。外观模式通常创建一个外观类,将子系统类的多个流程方法组装起来。

外观模式通过封装复杂的子系统接口,提供一个简化的统一接口,减少了客户端与子系统的直接依赖,降低了耦合性。

结构型模式(Structural Patterns):
主要用于描述对象之间的组合关系包括“代理模式”、“适配器模式”、“桥接模式”、“装饰者模式”、“外观模式”、“享元模式”和“组合模式”等。这些模式可以帮助我们更好地设计程序结构,提高代码的灵活性和可维护性。
外观模式将多个子系统通过界面接口统一访问,改变了各个子系统与客户端之间的组合关系,所以是结构型设计模式。

外观类、子系统和客户端

外观模式的三个角色

  • 外观类(Facade):提供统一的界面类,组装子系统的各个流程。例如软件安装外观类有个“一键安装”方法,组装了选择安装目录、选择组件、开启开机自启动等子流程。
  • 子系统:各功能的实际执行者。例如系统服务类设置开机自启动。
  • 客户端(Client):调用者。客户端通过调用外观类提供的简化接口,与各子系统交互,从而实现功能。例如点击外观对象的“一键安装”方法,一键安装软件。

举例

  • 一键安装:在 PC 上安装软件的时候经常有一键安装选项,省去选择安装目录、安装的组件、选择开机自启动、选择是否生成快捷方式等步骤
  • 重启手机:手机的关机选项,就是把关机和启动组合为一个操作

优缺点和适用场景

优点:

  • 简化用户操作:将具体的各个流程组装到外观类中,这样客户端调用时,直接调用外观类的方法即可。

  • 高内聚低耦合:将子系统的实现细节隐藏在外观类之后,客户端只与外观类交互,降低了系统各部分之间的耦合性。

  • 分层结构:外观模式可以作为上层系统与下层子系统交互的中间层,简化每一层之间的依赖。

  • 性能高:将各个子系统流程的分别调用,改为外观类一次性调用,减少网络通信成本,提高 了客户端的响应速度。

  • 易于维护:当需要修改流程时,只需要维护外观类的方法,调换各个流程,不需要关注各个子系统的具体实现。

缺点:

  • 掩盖子系统复杂性:将子系统的实现细节隐藏在外观类之后,开发者可能对各个子系统的具体实现细节了解不够深刻。
  • 过度设计:一些场景子系统并不多,而且流程简单,未来也不需要扩展新的流程,用外观模式就会有些过度设计。这也是众多设计模式的缺点,需要充分考虑适用场景

使用场景:

  • 子系统各流程复杂:当子系统很多时,为了防止遗留流程,并且方便管理各个流程的前后顺序,可以使用外观模式
  • 兼容旧系统功能:在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个 Facade 类,来提供遗留系统的比较清晰简单的接口,让新系统与 Facade 类交互,提高复用性
  • 三层架构:Java项目中,我们常常将Dao注入到Service,Service将多个Dao组合在一起,这其实也用到了外观模式。

外观模式解决影院管理

实现方案:将各流程组装到外观类

核心代码

子系统:投影仪、DVD等类

子系统类主要包括投影仪、DVD等类,拥有打开、关闭等功能(普通方法) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 投影仪
public class Projector {
private static Projector projector = new Projector();
public static Projector getInstance() {
return projector;
}
public void on() { System.out.println("打开投影仪..."); }
public void off() { System.out.println("关闭投影仪..."); }
public void focus() { System.out.println("投影仪聚焦..."); }
public void zoom() {System.out.println("投影仪放大..."); }
}
// DVD类
public class DVDPlayer {
private static DVDPlayer player = new DVDPlayer();
public static DVDPlayer getInstance() {
return player;
}
public void on() { System.out.println("打开DVD播放器..."); }
public void off() { System.out.println("关闭DVD播放器..."); }
public void play() { System.out.println("播放DVD播放器..."); }
public void pause() { System.out.println("暂停DVD播放器..."); }
public void setDvd(String dvd) {
System.out.println("选dvd:" + dvd + "...");
}
}
// 荧幕类
public class Screen {
private static Screen screen = new Screen();
public static Screen getInstance() {
return screen;
}
public void up() { System.out.println("升起荧幕..."); }
public void down() { System.out.println("拉下荧幕..."); }
}
// 音响类
public class Stereo {
private static Stereo stereo = new Stereo();
public static Stereo getInstance() {
return stereo;
}
public void on() { System.out.println("打开立体声..."); }
public void off() { System.out.println("关闭立体声..."); }
public void setVolume(Integer volume) {
System.out.println("立体声音量+" + volume + "...");
}
}
// 灯光类
public class TheaterLights {
private static TheaterLights lights = new TheaterLights();
public static TheaterLights getInstance() {
return lights;
}
public void on() { System.out.println("打开灯光..."); }
public void off() { System.out.println("关闭灯光..."); }
public void dim() { System.out.println("调暗灯光..."); }
public void bright() { System.out.println("调亮灯光..."); }
}
// 爆米花机
public class Popcorn {
private static Popcorn popcorn = new Popcorn();
public static Popcorn getInstance() {
return popcorn;
}
public void on() { System.out.println("打开爆米花机器..."); }
public void off() { System.out.println("关闭爆米花机器..."); }
public void pop() { System.out.println("取出爆米花..."); }
}

外观类:家庭影院外观类

外观类包括一个或多个组装方法,将各个流程(子系统类的方法)组装起来。

家庭影院 Facade

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 外观类:家庭影院外观类,包括准备、观看、暂停、结束等大流程
public class HomeTheaterFacade {
private Popcorn popcorn;
private Screen screen;
private Stereo stereo;
private TheaterLights lights;
private Projector projector;
private DVDPlayer player;

public HomeTheaterFacade() {
this.popcorn = Popcorn.getInstance();
this.screen = Screen.getInstance();
this.stereo = Stereo.getInstance();
this.lights = TheaterLights.getInstance();
this.projector = Projector.getInstance();
this.player = DVDPlayer.getInstance();
}

// 准备看电影:调用开灯、放屏幕、开投影仪、调暗灯等流程
public void ready() {
// 打开灯光
lights.on();
// 开爆米花机
popcorn.on();
// 放下屏幕
screen.down();
// 开投影仪,聚焦、放大
projector.on();
projector.focus();
projector.zoom();
// 开音响,设置音量
stereo.on();
stereo.setVolume(8);
// 开DVD,选dvd
player.on();
player.setDvd("坦塔尼克号");
// 取爆米花,关闭爆米花机器
popcorn.pop();
popcorn.off();
// 调暗灯光
lights.dim();
}

// 看电影
public void play() {
player.play();
}
// 暂停电影
public void pause() {
player.pause();
}
// 关闭电影:调用关投影仪、关音响、开灯、收屏幕等流程
public void end() {
player.off();
projector.off();
stereo.off();
lights.bright();
screen.up();
}
}

客户端:家庭影院的准备、观看、结束

客户端类创建外观类对象,调用各个组装方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 客户端:调用影院外观类的准备、观看、结束等流程
public class ClientTest {
public static void main(String[] args) throws InterruptedException {
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
System.out.println("===========家庭影院初始化============");
homeTheaterFacade.ready();
System.out.println("===========家庭影院沉浸式播放============");
homeTheaterFacade.play();
Thread.sleep(1000);
System.out.println("===========家庭影院暂停============");
homeTheaterFacade.pause();
Thread.sleep(1000);
System.out.println("===========家庭影院沉浸式播放============");
homeTheaterFacade.play();
Thread.sleep(1000);
System.out.println("===========家庭影院结束============");
homeTheaterFacade.end();
}
}

外观模式在三层架构中的应用

三层架构和MVC设计模式

三层架构

开发过程中,我们把后端服务器Servlet拆分成三层,分别是web、service和dao,这也是程序员常提到的“Java味”

  • web层(表现层):直接与用户交互,负责接收用户输入和呈现数据
  • service层(业务层):处理具体的业务逻辑。也称为服务层或应用层。
  • dao层(数据访问层):负责与数据库交互,执行数据的增删改查

优点

  • 低耦合:各层的职责明确,页面交互、业务逻辑、数据库操作三层分离,降低了系统模块间的耦合。并且各个类功能一目了然,例如OrderController可以直接看出它是控制器。
  • 易维护:每层的功能独立,业务逻辑更改后只需修改Service,数据库更改后只需修改dao。
  • 可扩展:当增加新功能后,可以在各层扩展相关功能的类。

MVC设计模式

MVC设计模式:将后端Servlet设计为控制器controller、视图view、业务模型Model。

  • 视图(View):显示UI页面数据,并与用户交互。前后端分离项目中视图就是前端代码,一体化项目中视图是JSP、Thymeleaf 等框架渲染成的HTML。
  • 控制器(Controller):负责接收浏览器发送过来的请求,然后响应给浏览器
  • 模型(Model):封装后端业务逻辑和应用的核心数据,与数据层交互。

流程

  1. 控制器(例如serlvlet)用来接收浏览器发送过来的请求
  2. 控制器调用模型(例如JavaBean)来获取数据,比如从数据库查询数据;
  3. 控制器获取到数据后再交由视图(例如JSP)进行数据展示。

image-20250728170118400

优点

  • 低耦合:将数据、UI 和控制逻辑分离,便于开发、维护和扩展。
  • 可扩展性:将逻辑处理和视图渲染分开,各个小组件能直接复用。

外观模式在三层架构中的应用

外观模式像模板模式一样,都是很通用的设计模式,不只是JDK和常用框架的源码,我们开发过程中也会经常用到他们。

例如写Service时候,将Dao注入到Service,然后调用Dao的各个方法,这其实也用到外观模式的思想。

下面订单Service,注入了订单dao、商品dao和客户dao,然后对流程进行组装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 订单业务实现
@Service
public class OrderServiceImpl implements OrderService{
@Autowired
private OrderDao orderDao; // 订单dao
@Autowired
private ProductDao productDao; // 商品dao
@Autowired
private CustomerDao customerDao; // 客户dao

public Order placeOrder(Customer customer, Product product) {
// 外观模式思想:调用多个DAO方法,完成下单业务逻辑
// 1.保存客户信息
customerDao.save(customer);
// 2.更新商品库存
productDao.updateStock(product);
Order order = new Order(customer, product);
// 3.保存订单
orderDao.save(order);
return order;
}
}

外观模式在Tomcat源码中的应用

Tomcat源码使用了外观模式,以ApplicationContextFacade为例

外观类:ApplicationContextFacade

ApplicationContextFacade 是 Apache Tomcat 框架中的外观类,位于 org.apache.catalina.core 包下。是 ApplicationContext 的代理接口,主要用于屏蔽 ApplicationContext 的复杂内部实现。

其中,initClassCache()方法用于初始化classCache变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package org.apache.catalina.core;
// 外观类:用于屏蔽 ApplicationContext 的复杂内部实现。
public class ApplicationContextFacade implements ServletContext {
// ---------------------------------------------------------- Attributes
private final Map<String, Class<?>[]> classCache; // 缓存类对象:用于后续通过反射获取类的信息
private final Map<String, Method> objectCache; // 缓存方法对象

// ----------------------------------------------------------- Constructors

/**
* 构造方法:构造一个新对象实例,并初始化类对象缓存
* @param context The associated Context instance
*/
public ApplicationContextFacade(ApplicationContext context) {
super();
this.context = context;
classCache = new HashMap<>();
objectCache = new ConcurrentHashMap<>();
initClassCache();
}
// 初始化类缓存:将ApplicationContext各方法加入类缓存中,value统一初始化成String类对象
private void initClassCache() {
// 1.创建String的类对象
Class<?>[] clazz = new Class[] { String.class };
// 2.将该类对象作为value,key是ApplicationContext各方法,统一存到类缓存中
// 获取上下文
classCache.put("getContext", clazz);
// 获取 MIME 类型
classCache.put("getMimeType", clazz);
// 获取资源路径
classCache.put("getResourcePaths", clazz);
// 获取资源
classCache.put("getResource", clazz);
// 获取资源的输入流
classCache.put("getResourceAsStream", clazz);
// 获取请求分发器
classCache.put("getRequestDispatcher", clazz);
// 获取指定名称的分发器
classCache.put("getNamedDispatcher", clazz);
// 获取 Servlet
classCache.put("getServlet", clazz);

// 设置初始化参数
classCache.put("setInitParameter", new Class[] { String.class, String.class });
// 创建 Servlet 实例
classCache.put("createServlet", new Class[] { Class.class });
// 添加 Servlet
classCache.put("addServlet", new Class[] { String.class, String.class });

// 创建过滤器实例
classCache.put("createFilter", new Class[] { Class.class });
// 添加过滤器
classCache.put("addFilter", new Class[] { String.class, String.class });
// 创建监听器
classCache.put("createListener", new Class[] { Class.class });
// 添加监听器
classCache.put("addListener", clazz);
// 获取过滤器注册信息
classCache.put("getFilterRegistration", clazz);
// 获取 Servlet 注册信息
classCache.put("getServletRegistration", clazz);
// 获取初始化参数
classCache.put("getInitParameter", clazz);
// 设置属性
classCache.put("setAttribute", new Class[] { String.class, Object.class });
// 移除属性
classCache.put("removeAttribute", clazz);
// 获取资源的真实路径
classCache.put("getRealPath", clazz);
// 获取属性
classCache.put("getAttribute", clazz);
// 记录日志
classCache.put("log", clazz);
// 设置会话跟踪模式
classCache.put("setSessionTrackingModes", new Class[] { Set.class });
// 添加 JSP 文件
classCache.put("addJspFile", new Class[] { String.class, String.class });
// 声明角色
classCache.put("declareRoles", new Class[] { String[].class });
// 设置会话超时时间
classCache.put("setSessionTimeout", new Class[] { int.class });
// 设置请求字符编码
classCache.put("setRequestCharacterEncoding", new Class[] { String.class });
// 设置响应字符编码
classCache.put("setResponseCharacterEncoding", new Class[] { String.class });
}
// ...
}

Tomcat和Spring的ApplicationContext区别

相同点: 两者都是应用程序上下文,用于管理和获取容器的环境信息。

不同点:

  • Tomcat的ApplicationContext:Web容器相关的应用上下文,用于获取和管理Web容器的环境信息;
  • Spring的ApplicationContext:Spring的IOC容器相关的应用上下文,用于获取和管理IOC容器的环境信息;

IOC容器

  • IOC控制反转思想:创建对象的控制权由内部(即new实例化)反转到外部(即IOC容器)。
  • Bean:IOC容器中存放的一个个对象
  • DI依赖注入:绑定IOC容器中bean与bean之间的依赖关系。例如将dao层对象注入到service层对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@SpringBootTest
class DemoApplicationTests {
@Autowired
private ApplicationContext applicationContext;
// web容器:ApplicationContext是ServletContext接口的实现类
@Autowired
private ServletContext servletContext;
// 获取所有Bean
@Test
void contextLoads() {
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
// 获取所有web容器的属性
@Test
void servletTest() {
// 获取所有web容器的属性
Enumeration<String> attributeNames = servletContext.getAttributeNames();
while (attributeNames.hasMoreElements()) {
String name = attributeNames.nextElement();
System.out.println(name);
}
}

}