[Angular 基础] - routing 路由(下)

[Angular 基础] - routing 路由(下)


之前部分 Angular 笔记:

  • [Angular 基础] - 自定义指令,深入学习 directive

  • [Angular 基础] - service 服务

  • [Angular 基础] - routing 路由(上)


使用 route

书接上回,继续折腾 routing

按照最初的 wireframe,它的实现是这样的:

在这里插入图片描述

之前为了简化一些实现,就直接采取了在 routes 下面声明一个新的路径,去采用重新渲染子组件的方式去进行重定向。这也会有几个比较麻烦的点:

  1. 数据渲染不完全
  2. servers/:id 返回到 servers 很麻烦

如果想要解决这个问题,将子组件重新渲染:

<div class="col-xs-12 col-sm-4"><app-edit-server></app-edit-server><hr /><app-server></app-server>
</div>

又会遇到下面这些问题:

  1. 在主界面时代码抛出异常

    在这里插入图片描述

    有些情况 Cannot read properties of undefined 是会 break 页面,从而导致内容无法渲染的情况

    会有这个问题也是因为子页面尚未被选中,路径还是 http://localhost:4200/servers,自然无法获取对应的 server 数据

  2. 渲染了额外数据

    理想条件下,当在 /servers 这个路径下,代表着没有任何的服务器被选中,那么也不应该渲染对应的组件

为了更加优雅的解决这个问题,而不是使用大量的 ngIf 去进行条件控制,Angular 提供了 child/nested routes 的解决方案

child route

突然想到, react-router v6 也实现了这个功能……果然这日子还是到了前端框架互相抄的日子了……

这里具体修改的配置如下:

  • routing module

    const appRoutes: Routes = [{path: 'servers',component: ServersComponent,children: [{path: ':id',component: ServerComponent,},{path: ':id/edit',component: EditServerComponent,},],},
    ];
    
  • servers V 层

    <div class="row"><div class="col-xs-12 col-sm-4"><div class="list-group"><a[routerLink]="['/servers', server.id]"[queryParams]="{ allowEdit: server.id === 3 ? '1' : '0' }"fragment="loading"class="list-group-item"*ngFor="let server of servers">{{ server.name }}</a></div></div><div class="col-xs-12 col-sm-4"><router-outlet></router-outlet></div>
    </div>
    

实现后的效果:

在这里插入图片描述

就像是在 appRoutes 中定义的一样,一旦路径为 servers/:id,那么就会渲染单独的 server component,如果路径是 servers/:id/edit,就会渲染 edit-server component

我的理解是,一旦将对应的组件放到了 children 中,就会形成以父组件为基础的一个单独模块。Angular 就像处理其他模块一样,都需要通过一个 router-outlet 去进行监听,才能做出合适的反应

如果 V 层没有添加 router-outlet 的话,那么 children 中的模块就不会被渲染

更多 query param 的部分

上面的动图其实还有一个问题,那就是当从 servers/:id 定向到 servers/:id/edit 时,后置的 query parameters 全都丢了,这也是 Angular 的默认行为,想要改变这个行为,需要在 click handler 中进行处理,下面是对于 edit server 按钮的事件处理:

export class ServerComponent implements OnInit {onEdit() {this.router.navigate(['edit'], {relativeTo: this.route,queryParamsHandling: 'preserve',});}
}

这里的 queryParamsHandling 有三个值:

export declare type QueryParamsHandling = 'merge' | 'preserve' | '';
  • merge

    会将当前组件有的 query parameters 与已经存在的 query parameter 进行合并

  • preserve

    会保留已经存在的 query parameter

这时候的效果如下:

在这里插入图片描述

可以看到,allowEdit 这个参数被保留下来了,对应的 EditServerComponent 也可以通过当前的 query parameter 进行正常的逻辑处理

⚠️:EditServerComponent 有两个 subscription,一个 subscribe queryParams,另一个则是 subscribe 当前传来的 server id:

export class EditServerComponent implements OnInit {ngOnInit() {this.route.queryParams.subscribe((queryParams: Params) => {this.allowEdit = queryParams.allowEdit === '1';});this.route.fragment.subscribe();const id = parseInt(this.route.snapshot.params.id);// subscribe route params to update the id if params changethis.server = this.serversService.getServer(id);this.serverName = this.server.name;this.serverStatus = this.server.status;this.route.params.subscribe((params: Params) => {this.server = this.serversService.getServer(parseInt(params.id));this.serverName = this.server.name;this.serverStatus = this.server.status;});}
}

添加 not found 页面

根据当前的实现,如果用户意外输入了一些错误的路径,那么就会重新导航到首页,并且在 console 中输出报错信息:

在这里插入图片描述

这也是一个相对而言比较粗暴的处理方式,大多数的应用都会渲染一个 not found 页面,而不是直接显示一个空白屏幕。接下来就更新 app routing module 进行实现:

const appRoutes: Routes = [// 其余处理不变{ path: 'not-found', component: PageNotFoundComponent },{ path: '**', redirectTo: '/not-found' },
];

⚠️:这里假设 Not Found 页面已经通过 ng generate component 被创建了,也实现了对应的 V 层

显示效果如下:

在这里插入图片描述

这里业务实现的逻辑也比较简单,首先是确定一个为 not-found 的路径,将对因渲染的组件设置为 PageNotFoundComponent。接下来就是终点的实现了,也就是一个 wildcard match,这个 match 必须 放在最下面,这样无法被之前路由 capture 的变化会落到这里,否则所有的页面都会渲染为 PageNotFoundComponent 组件。

当当前路由找不到当前提供的路径时,它就会被重新定向到 /not-found 页面,从而渲染 PageNotFoundComponent 组件

另一个稍微简单一点的处理方式就是直接将 **component: PageNotFoundComponent 进行绑定:{ path: '**', component: PageNotFoundComponent,这样就可以保留当前的路径,并渲染 PageNotFoundComponent 组件

这里有一点像是 React Router Dom v5 里的 Switch,不过 React Router Dom 里面的 wildcard 时 *,这里是 **

⚠️:Agular 的路径 match 是通过前缀实现的,在 not found page 这个案例的情况是 wildcard,不会造成任何的问题。

👀:一个使用情况可以从 '' 导航到 '/home,这时候配置可以这么写:{ path: '', redirectTo: 'home', patchMatch: 'full' },

Guards

guards 是用来控制路由的,博啊哭哦权限控制、是否能被访问等一些行为,每个 guard 都有不同的功能

canActive

这个 guard 用来控制当前路由是否可以被访问

这里以 servers 为例,条件控制为只有登录的用户才可以访问 /servers,变动如下:

  • routing module

    const appRoutes: Routes = [{path: 'servers',canActivate: [AuthGuardService],component: ServersComponent,},
    ];
    
  • 新增 AuthGuardService

    @Injectable({
    providedIn: 'root',
    })
    export class AuthGuardService implements CanActivate, CanActivateChild {
    constructor(private authService: AuthService, private router: Router) {}canActivate(route: ActivatedRouteSnapshot,state: RouterStateSnapshot
    ): Observable<boolean> | Promise<boolean> | boolean {return this.authService.isAuthenticated().then((authenticated: boolean) => {if (authenticated) {console.log('authenthenciated');return true;} else {console.log('not authenthenciated');this.router.navigate(['/']);return false;}});
    }
    

其中 isAuthenticated 的实现如下:

  isAuthenticated() {const promise = new Promise((resolve, reject) => {setTimeout(() => {resolve(this.login);}, 1000);});return promise;}

展示效果如下:

在这里插入图片描述

这里 isAuthenticated 返回的永远是一个 promise,并且会在一秒钟后完成登陆,所以永远不会被重定向到首页。稍微代码,将 resolve 中改成 false,并修改一下冲定向的路径也可以实现一下效果:

在这里插入图片描述

这里都有一个 1s 的等待时间,这个是 setTimeout 决定的


需要注意的是 CanActivate 已经 deprecated 了,原因似乎也是 Angular 要走走 functional guard,而 functional guard 的实现如下:

这里的实现稍微复杂一些,

  1. 首先将 then 那一块代码抽出来,放到一个新的 service 中:

    @Injectable({providedIn: 'root',
    })
    export class PermissionsService {constructor(private authService: AuthService, private router: Router) {}canActivate(route: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {return this.authService.isAuthenticated().then((authenticated: boolean) => {if (authenticated) {console.log('authenthenciated');return true;} else {console.log('not authenthenciated');this.router.navigate(['/forbidden']);return false;}});}
    }
    

    ⚠️:这里使用 canActivate 只是一个约定俗成的规范,它可以改成任何一个名称,不是一定要用 canActivate

  2. 使用 functional guard

    export const authGuardFunc: CanActivateFn = (route, state) => {return inject(PermissionsService).canActivate(route, state);
    };
    

    ⚠️:直接实现下面的代码,ide 不会报错,但是浏览器会报错:

    export const authGuardFunc: CanActivateFn = (route, state) => {return inject(AuthService).isAuthenticated().then((authenticated: boolean) => {if (authenticated) {console.log('authenthenciated');return true;} else {console.log('not authenthenciated');inject(PermissionsService).canActivate(route, state);return false;}});
    };
    

    在这里插入图片描述

canActiveChild

有一个情况就是,用户可以访问 servers,但是不能访问 servers 的 children。一个解决方法是将 canActivate: [AuthGuardService], cv 到每一个 child component,不过当 child component 变多的时候,管理就会变得非常的麻烦。

如果当前逻辑应用于所有的 child component,就可以使用 canActiveChild 去实现:

@Injectable({providedIn: 'root',
})
export class AuthGuardService implements CanActivate, CanActivateChild {constructor(private authService: AuthService, private router: Router) {}// 新增部分,可以直接调用之前实现的 canActivatecanActivateChild(childRoute: ActivatedRouteSnapshot,state: RouterStateSnapshot): boolean | Observable<boolean> | Promise<boolean> {return this.canActivate(childRoute, state);}
}

并在 routing module 中实现对应的修改:

const appRoutes: Routes = [{path: 'servers',canActivateChild: [AuthGuardService],// 其余部分省略},
];

效果如下:

在这里插入图片描述


同样,对应的 functional guard 的修改如下:

  1. permissions service 的修改:

    export class PermissionsService {constructor(private authService: AuthService, private router: Router) {}canActiveChild(route: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {return this.canActivate(route, state);}
    }
    
  2. func guard 的修改:

    export const authGuardFunc: CanActivateFn = (route, state) => {return inject(PermissionsService).canActivate(route, state);
    };export const authGuardChildFunc: CanActivateChildFn = (route, state) => {return inject(PermissionsService).canActiveChild(route, state);
    };
    

调用的时候则是直接在 canActivateChild 中放入 authGuardChildFunc 即可

鉴于代码完全一致,放入 authGuardFunc 也没问题啦……

canDeactivate

canDeactivate 是一个在离开当前页面时会触发的 guard,一般可以用来检查未保存的内容,防止用户提前离开

具体实现方式如下:

  1. 创造一个 service,具体实现如下:

    export interface CanComponentDeactivate {canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
    }@Injectable({providedIn: 'root',
    })
    export class CanDeactivateGuardimplements CanDeactivate<CanComponentDeactivate>
    {canDeactivate(component: CanComponentDeactivate,currentRoute: ActivatedRouteSnapshot,currentState: RouterStateSnapshot,nextState: RouterStateSnapshot): boolean | Observable<boolean> | Promise<boolean> {return component.canDeactivate();}
    }
    
  2. 在 routing module 中添加 guard:

    const appRoutes: Routes = [{path: 'servers',children: [{path: ':id/edit',component: EditServerComponent,canDeactivate: [CanDeactivateGuard],},],},
    ];
    
  3. EditServerComponent 实现 canDeactivate 函数

    如果不实现的话,在离开当前页面会报错,也就 break project 了:

    在这里插入图片描述

    这也是为什么 Angular 的实现这么复杂……主要还是为了类型检查,以及 添加的功能都必须实现 这样的检查

    具体实现如下:

    @Component({selector: 'app-edit-server',templateUrl: './edit-server.component.html',styleUrls: ['./edit-server.component.css'],
    })
    export class EditServerComponent implements CanComponentDeactivate {serverName = '';serverStatus = '';allowEdit = false;changesSaved = false;canDeactivate(): boolean | Promise<boolean> | Observable<boolean> {if (!this.allowEdit) {return true;}if ((this.serverName !== this.server.name ||this.serverStatus !== this.server.status) &&!this.changesSaved) {return confirm('Do you want to discard the changes?');} else {return true;}}
    }
    

实现完成后的效果:

在这里插入图片描述

这里有 3 种情况:

  1. 当用户没有编辑权限

    ✅ 直接允许重定向

  2. 当用户有编辑权限,但是用户 没有 编辑内容

    ✅ 直接允许重定向

  3. 当用户有编辑权限,并且用户 已经 编辑内容

    ❌ 不允许直接冲定向

    这里的具体操作是跳出一个 confirm,当用户确认后,即可重新定向


这里也提出 functional guard 的实现方式,鉴于其他的变量名不变,所以这里只需要修改 CanDeactivateGuard service 即可:

export interface CanComponentDeactivate {canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}export const CanDeactivateGuard: CanDeactivateFn<CanComponentDeactivate> = (component: CanComponentDeactivate
): Observable<boolean> | boolean => {if (component.canDeactivate && component.canDeactivate()) return true;
};// @Injectable({
//   providedIn: 'root',
// })
// export class CanDeactivateGuard
//   implements CanDeactivate<CanComponentDeactivate>
// {
//   canDeactivate(
//     component: CanComponentDeactivate,
//     currentRoute: ActivatedRouteSnapshot,
//     currentState: RouterStateSnapshot,
//     nextState: RouterStateSnapshot
//   ): boolean | Observable<boolean> | Promise<boolean> {
//     return component.canDeactivate();
//   }
// }
resolve

canActivate 是控制用户允许访问当前页面, canDeactivate 是控制用户不允许访问当前页面,resolve 则是允许等待一段时间(如获取数据的异步操作),在完成操作后渲染组件

实现如下:

  • 创建一个新的 resolver service

    interface Server {id: number;name: string;status: string;
    }@Injectable({providedIn: 'root',
    })
    export class ServerResolverService implements Resolve<Server> {constructor(private serversService: ServersService) {}resolve(route: ActivatedRouteSnapshot,state: RouterStateSnapshot): Server | Observable<Server> | Promise<Server> {const promise: Promise<Server> = new Promise((resolve) => {setTimeout(() => {console.log('resolving');resolve(this.serversService.getServer(parseInt(route.params.id)));}, 1000);});return promise;}
    }
    

    这个 service 就是实现一个 resolver,即在组件渲染之前获取对应的 server。我这里用 setTimeout 模拟了一个异步操作

  • 更新 routing module

    这里制定要使用 resolver 的组件,即 servers/:id

    const routes = (Routes = [{path: 'servers',children: [{path: ':id',component: ServerComponent,resolve: { server: ServerResolverService },},],},
    ]);
    

    这一步操作会将获取的 server——resolve 的数据——存储到 server 这个变量名中

  • 更新 server component

    export class ServerComponent implements OnInit {server: { id: number; name: string; status: string };ngOnInit() {this.route.data.subscribe((data: Data) => {console.log(data);this.server = data.server;});}
    }
    

    这里主要更新 ngOnInit 中的内容,最终的效果与实现的效果是一致的

最终效果:

在这里插入图片描述

可以看到渲染被延迟了大概一秒钟,然后输出了对应的 server


同样增添一下 functional guard 的实现:

export const serverResolver: ResolveFn<Server> = (route) => {const serverId = parseInt(route.params.id);return inject(ServersService).getServer(serverId);
};

⚠️:getServer 返回的是一个 Server

传输数据

之前在 canActivate guard 中创建了一个 forbidden 页面,这样每次页面报错都会重新导航到 /forbidden 上去。但是这样做的一个问题就在于,如果想要做更多的报错处理,那么可能需要创造更多的报错页面。

下面提供一个可以复用

下面是步骤:

  1. 创建一个新的 generic error 页面

    • V 层
    <h4>{{ errorMessage }}</h4>
    
  • VM 层

    @Component({selector: 'app-error-page',templateUrl: './error-page.component.html',styleUrl: './error-page.component.css',
    })
    export class ErrorPageComponent implements OnInit {errorMessage: string;constructor(private route: ActivatedRoute) {}ngOnInit() {this.errorMessage = this.route.snapshot.data['message'];this.route.data.subscribe((data: Data) => {this.errorMessage = data.message;});}
    }
    
  1. 修改 app routing

    const appRoutes: Routes = [{path: 'not-found',component: ErrorPageComponent,data: { message: 'Page not found!' },},{path: '**',redirectTo: '/not-found',},
    ];
    

效果如下:

在这里插入图片描述

这样可以创建多个不同的路径,并传输不同的信息,实现使用一个 ErrorPageComponent 渲染不同的报错信息

如果搭配其他的 Observable,这里应该也是可以实现避开重复声明路由,而是直接用 ** wildcard 去渲染 ErrorPageComponent,随后使用 Observable 获取报错信息

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

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

相关文章

Linux--文件(2)-重定向和文件缓冲

命令行中的重定向符号 介绍和使用 在Linux的命令行中&#xff0c;重定向符号用于将命令的输入或输出重定向到文件或设备。 常见的重定向符号&#xff1a; 1.“>“符号&#xff1a;将命令的标准输出重定向到指定文件中&#xff0c;并覆盖原有的内容。 2.”>>“符号&a…

1.初识python

1.初识python 编程语言是用来定义计算机程序的语言&#xff0c;用来向计算机发出指令。 1.python语言是一种面向对象的解释型高级编程语言。 解释型语言&#xff1a;使用专门的解释器对源码程序逐行解释成特定平台的机器并立即执行&#xff0c;是代码在执行时才被解释器一行行…

c++数据结构算法复习基础-- 3 --线性表-单向链表-笔试面试常见问题

1、单链表逆序 思路图 代码实现 //著: 链表结构里记得加 friend void ReverseLink(Clink& link); void ReverseLink(Clink& link) {Node* p link.head_->next_;while( p nullptr){return;}Node* q p->next_;link.head_->next_ nullptr;while(p ! nullpt…

YOLOv8改进 在更换的PoolFormer主干网络中增加注意力机制

一、PoolFormer的网络结构 PoolFormer采用自注意力机制和池化操作相结合的方式&#xff0c;同时考虑了局部和全局的特征关系。 具体的代码如&#xff08;YOLOv8改进 更换多层池化操作主干网络PoolFormer_yolov8池化-CSDN博客&#xff09;所示。 二、Global Attention Mechan…

python一张大图找小图的个数

python一张大图找小图的个数 一、背景 有时候我们在浏览网站时&#xff0c;发现都是前端搞出来的一张张图&#xff0c;我们只能用盯住屏幕的小眼睛看着&#xff0c;很累的统计&#xff0c;这个是我在项目中发现没办法统计&#xff0c;网上的教程很多&#xff0c;都不成功&…

Python 面向对象编程——类的使用

一、学习目标 1&#xff0e;掌握类的定义和实例化对象。 2&#xff0e;熟练掌握类的构造函数__init__使用。 3&#xff0e;掌握类的继承机制和使用。 二、相关练习 1、定义一个玩具类Toy()&#xff0c;创建名字为“小汽车”、“手枪”和“积木”的玩具实例&#xff0c;计…

深圳牵头打造鸿蒙原生应用软件生态 | 百能云芯

深圳市工业和信息化局、深圳市政务服务和数据管理局于3月3日联合印发了《深圳市支持开源鸿蒙原生应用发展2024年行动计划》。这一计划旨在通过政策引导、市场推动、社会协同的方式&#xff0c;将深圳打造成一个鸿蒙原生应用软件生态的中心&#xff0c;推动鸿蒙系统在当地的发展…

PyQT6的从零开始在Pycharm中配置与使用

PyQT6的从零开始在Pycharm中配置与使用 1.安装PyQt6 PyQt6-tools2.在Pycharm中配置扩展工具2.1配置QTdesigner2.2配置Pyuic 3.启动3.1、启动designer3.2、启动Pyuic 1.安装PyQt6 PyQt6-tools pip install PyQt6 PyQt6-tools安装成功后&#xff0c;查看安装版本&#xff0c;版本…

基于springboot+vue的医疗报销系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

面试笔记系列四之SpringBoot+SpringCloud+计算机网络基础知识点整理及常见面试题

目录 Spring Boot 什么是 Spring Boot&#xff1f; Spring Boot 有哪些优点&#xff1f; SpringBootApplication注解 Spring Boot 的启动流程 Spring Boot属性加载顺序 springboot自动配置原理是什么&#xff1f;&#xff08;*&#xff09; 如何理解springboot中的start…

低代码平台开发实践:基于React的高效构建与创新【文末送书-29】

文章目录 背景低代码平台简介基于React的优势低代码平台的实际应用 低代码平台开发实践&#xff1a;基于React【文末送书-29】 背景 随着技术的不断进步和业务需求的日益复杂&#xff0c;低代码平台成为现代软件开发领域中备受关注的工具之一。在这个快节奏的时代&#xff0c;…

解决手机连接校园网同一设备老是需要重复认证的问题(+解决原理)

相信大家平时在使用校园网的时候总会遇到同一设备隔三岔五就要重复认证绑定的问题&#xff0c;这里直接附上解决方案。 打开手机的wifi-->连接校园网然后进入设置-->在隐私选项选择“使用设备MAC” 如下图&#xff0c;问题解决了&#xff01;如果想知道原理的可以继续往…

如何处理微服务之间的通信和数据一致性?

✨✨祝屏幕前的兄弟姐妹们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; 目录 引言 一、微服务通信 1、同步通信&#xff1a;HTTP 1.1.同步通信示例代码&#xf…

1、Ajax、get、post、ajax,随机颜色

一、Ajax初始 1、什么是Ajax&#xff1f; 异步的JavaScript和xml 2、xml是什么&#xff1f; 一种标记语言&#xff0c;传输和存储数据----------现在用JSON传输数据 3、Ajax的作用 局部加载 可以使网页异步更新 4、Ajax的原理或者步骤(6步) 创建Ajax对象 if (window.X…

2024年租用阿里云服务器多少钱?阿里云服务器租用价格表(最新版)

2024年租用阿里云服务器一年多少钱&#xff1f;不同时期阿里云服务器的租用价格不同&#xff0c;随着2024年阿里云上云采购季活动的开启和阿里云最新一轮的云产品降价调整&#xff0c;阿里云服务器租用价格也做了一些调整&#xff0c;配置最低的1核1G云服务器收费标准为22.8/月…

NAT模式 LVS负载均衡部署

一 架构图 二 文字表述过程 1 当客户端 发起请求报文是: 源ip:客户端的ip地址(cip) 目的地址:vip(代理服务器的外网地址) 2.当数据包到达我们的 代理服务器 源ip不变&#xff0c;需要修改目的ip及端口号 源ip:客户端的ip地址(c…

智慧城市中的数字孪生:构建城市管理的未来框架

目录 一、引言 二、数字孪生技术概述 三、数字孪生技术在智慧城市中的应用 1、实时监测与预警 2、模拟与优化 3、智能化决策 4、协同与共享 四、数字孪生技术构建城市管理的未来框架的价值 1、提高管理效率 2、优化资源配置 3、提升公共服务水平 4、增强应对突发事…

【Android开发】02-小费计算APP(Tip Time)

github地址&#xff08;项目中的A02_TipTime文件夹&#xff09;&#xff1a; https://github.com/tao355667/Android_Development 一、功能介绍 输入消费金额和服务满意度后&#xff0c;可计算出相应的小费(可选是否四舍五入)支持中英文系统可根据系统主题的明暗切换界面 二、…

SpringBoot源码解读与原理分析(四)SPI机制

文章目录 2.4 SPI机制&#xff08;Service Provider Interface&#xff09;2.4.1 JDK原生SPI1.定义接口实现类2.声明SPI文件3.测试 2.4.2 SpringFramework 3.2 的SPI1.声明SPI文件2.测试3.Spring SPI机制的实现原理 2.4 SPI机制&#xff08;Service Provider Interface&#xf…

【c++设计模式14】结构型6:享元模式(Flyweight Pattern)

【c设计模式14】结构型6&#xff1a;享元模式&#xff08;Flyweight Pattern&#xff09; 一、定义二、适用场景三、过程四、享元模式类图五、C示例代码六、使用注意事项 类型序号设计模式描述结构型1适配器模式&#xff08;Adapter Pattern&#xff09;它用于在不修改已有类的…