经典权限五张表案例分析

news/2024/10/24 9:18:25/

文章目录

  • 模块分析
    • 模块
    • 分析
  • 描述五张表的关系
  • 重要知识讲解
    • 抽取成一个BaseServlet
    • SpringIOC思想(底层)
      • 实现代码
      • IOC概述
    • SPI机制(为学习框架做思想和技术铺垫)
      • SPI引入
        • 1. 标准/规范
        • 2. 具体的实现
        • 3. 调用
      • SPI介绍
      • SPI练习JDBC4.0免注册驱动原理
      • Servlet实现方式三 ServletContainerInitializer

模块分析

模块

项目分为三个模块

  1. 用户模块:完成增删改查
  2. 角色模块:增删改查
  3. 权限模块:增删改查

分析

用户和角色的关系
一个用户有多个角色,张三可以是SVIP,也可以是绿钻
一个角色可以对应多个用户,SVIP可以是张三,也可以是李四
结果:用户和角色属于多对多的关系,根据表设计原则,多对多关系创建中间表,在中间表起码要有另外俩张主表用户和角色的主键作为外键进行关联
角色和权限的关系
一个角色有多个权限,SVIP可以点赞20次,可以使qq名变红
一个权限可以对应多个角色,使qq名变红可以是SVIP,也可以是VIP
结果:角色和权限属于多对多的关系,根据表设计原则,多对多关系创建中间表,在中间表起码有另外俩张主表权限和角色的主键作为外键进行关联
:用户 角色 权限具有经典的五张表

描述五张表的关系

image.png

重要知识讲解

抽取成一个BaseServlet

问题:对用户进行增删改查,那么单对用户就要写4个Servlet,这样创建的类太多比较麻烦
解决措施:我们想的是对于用户只创建一个Servlet,路径的话变成/user/*,这样关于用户的所有操作都会放到这个Servlet类中,在里面定义方法来分别操作用户,方法名和路径名保持相同,代码看着简单明了,很简洁
问题:但是这样做我们就只能使用在用户模块,不能使用其他模块,造成代码冗余,还得必须使用if判断到底执行哪个方法
解决措施:前面已经学过可以获取前端的请求数据,那么就可以获取到请求路径,然后用反射来执行方法,然后将其抽取到一个类中,让其他模块创建的Servlet类继承这个类,其他模块的Servlet类中只写操作方法即可
关键:this关键字
代码如下

public class BaseServlet extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {doGet(request, response);}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//获取请求路径String uri = request.getRequestURI();int i = uri.lastIndexOf("/");String methodName = uri.substring(i + 1);//判断方法名//名字固定,不利于整体书写
//        if ("findAll".equals(methodName)){
//            findAll(request,response);
//        } else if ("update".equals(methodName)) {
//            update(request,response);
//        } else if ("add".equals(methodName)) {
//            add(request,response);
//        }else if ("delete".equals(methodName)){
//            delete(request,response);
//        }//用反射技术,使用固定代码执行所有方法//方法名和路径相同,利用反射获取方法名Class aClass = this.getClass();try {Method method = aClass.getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);method.invoke(this,request,response);} catch (Exception e) {throw new RuntimeException(e);}}
}
@WebServlet("/user/*")
public class UserServlet extends BaseServlet {private void delete(HttpServletRequest request, HttpServletResponse response) {System.out.println("删除用户");}public void add(HttpServletRequest request, HttpServletResponse response) {System.out.println("增加用户");}public void update(HttpServletRequest request, HttpServletResponse response) {System.out.println("更新用户");}public void findAll(HttpServletRequest request, HttpServletResponse response) {}
}

SpringIOC思想(底层)

实现代码

问题:我们以前创建对象都是通过new创建对象,例如:创建业务层对象:
UserServiceImple userServiceImpl = new UserServiceImpl();
这种方式创建对象的弊端是:严重耦合。如果业务层实现类UserServiceImpl被删除了,web会报错。或者在实际开发中向对业务层进行扩展,一般是定义一个新的业务层类UserServiceImpl2,然后将之前的UserServiceImpl替换,就造成web层无法运行,那么太过于耦合
解决措施:降低耦合。就是不在web层中使用new方式创建对象
使用:反射+面向接口编程+读取配置文件+工程设计模式等方式来取代new创建对象

  1. 定义接口

在业务层下定义接口,然后定义一个impl包,将UserServiceImpl类放到impl包下,这样就可以将上面创建业务层对象改成:
UserService userService = new UserServiceImpl();
这样的话就是实现左边解耦了,如果扩展实现类只需要修改等号右边

  1. 书写properties文件

在resources下定义一个beans.properties文件,然后按照key=value格式书写,key:userService,value:写UserServiceImpl的全路径名

  1. 使用反射+读取配置文件创建对象

使用ResourceBundle抽象类去读取properties配置文件,利用反射创建对象

  1. 定义一个工具类,使用工厂设计模式来创建对象

定义一个Map集合,key存properties文件内容=左边的值,value存用反射创建的对象
代码如下
工厂类

package com.itheima.case2.utils;import java.util.HashMap;
import java.util.ResourceBundle;/*TODO:当前工厂类的作用就是创建对象的回顾:一个类对象:1)单例 单个对象2)多例 多个对象我们这里实现产生的对象是单例 userService=com.itheima.case2.service.impl.UserServiceImpl1.创建一个Map集合: new HashMap<String,Object>();2.Map集合的key:例如配置文件等号左边的标识userService roleService3.Map集合的value:就是创建的对象UserServiceImpl 类的对象  RoleServiceImpl类的对象4.实现步骤:1)创建map集合存储创建的对象2)定义静态方法创建具体类的对象3)在静态方法中判断map集合的key是否有值,如果没有值,说明第一创建对象4)直接使用反射+读取配置文件方式创建对象5)将创建的对象作为map集合的value和key存入到map中6)返回给调用者创建的对象*/
public class BeansFactory {// 1)创建map集合存储创建的对象/*key            valueuserService    UserServiceImpl0x001roleService    RoleServiceImpl0x002*/private static HashMap<String,Object> map = new HashMap<>();// 2)定义静态方法创建具体类的对象//多线程安全问题:t1 t2public static synchronized  <T> T getInstance(String key) throws Exception{//String key=userService roleService// 3)在静态方法中判断map集合的key是否有值,如果没有值,说明第一创建对象Object obj = map.get(key);// 4)直接使用反射+读取配置文件方式创建对象if(obj == null){//说明当前map集合中没有传递的key对应的值//4.1使用反射+读取配置文件创建对象ResourceBundle bundle = ResourceBundle.getBundle("beans");//参数beans表示要关联的配置文件的名字,不能书写后缀名//4.2根据配置文件的key获取值 t1//userService=com.itheima.case2.service.impl.UserServiceImplString classNameStr = bundle.getString(key);//"com.itheima.case2.service.impl.UserServiceImpl"//4.3使用反射创建对象Class<?> clazz = Class.forName(classNameStr);obj = clazz.newInstance();//调用UserServiceImpl类的无参构造方法// 5)将创建的对象作为map集合的value和key存入到map中map.put(key,obj);//t1线程创建的对象0x001 t2线程创建的对象0x002}// 6)返回给调用者创建的对象return (T)obj;}}

配置文件

userService=com.itheima.case2.service.impl.UserServiceImpl

web层UserServlet

UserService userService = BeanFactory.getInstance("userService");

IOC概述

Inversion of Control:控制反转
以前我们要获取对象,我们自己new主动获取,现在有了工厂模式,我们需要获取对象,是工厂创建,我们被动接收工厂创建的对象,这就是控制反转,说白了就是ioc采用工厂模式创建对象达到解耦合
其实SpringIOC底层是Map集合,我们经常会说SpringIOC容器即Map集合。

SPI机制(为学习框架做思想和技术铺垫)

SPI引入

1. 标准/规范
  1. 工程 spi_interface
  2. 只有一个接口car
2. 具体的实现
  1. 工程 honda_car 和 tesla_car
  2. 工程依赖了spi_interface
    pom.xml
  3. 有一个实现类,实现了标准
    HondaCar implements Car
    TeslaCar implements Car
  4. 还有一个配置文件
    1). 在类路径classpath下
    resources/META-INF/services
    2). 文件名: 接口的全限定名
    com.itheima.Car
    3). 文件内容: 实现类的全限定名
    com.itheima.impl.HondaCar
3. 调用
  1. 工程 spi_test
  2. 工程依赖了 honda_car 和 tesla_car
  3. 测试类 SpiTest

测试类代码

 # ServiceLoader<Car> cars = ServiceLoader.load(Car.class);加载接口实现1. 加载当前工程依赖的所有工程 classpath:META-INF/services目录下跟当前接口参数同名的文件(classpath:META-INF.services/com.itheima.Car文件)2. 当前案例依赖了两个工程,那么这两个工程的配置文件都会被读取到honda_car===META-INF.services/com.itheima.Car文件中的com.itheima.impl.HondaCartesla_car===META-INF.services/com.itheima.Car文件中的com.itheima.impl.TeslaCar注意:配置文件名必须是实现的接口全路径,配置文件中书写实现类的全路径3. 通过反射创建接口文件中配置的实例Class clazz= Class.forName("com.itheima.impl.TeslaCar");Car car =  clazz.newInstance();

ServiceLoader类介绍

  1. ServiceLoader功能和ClassLoader功能类似,能装载类文件,但是使用时是有区别的
  2. ServiceLoader装载的是一系列有某种共同特征的实现类(类实现同一个接口,在实现类的工程中的resources目录下存在META-INF/services目录下接口同名的文件),即这些类实现接口或者抽象类。而ClassLoader是可以加载任何类的类加载器;
  3. ServiceLoader加载时需要特殊的配置:
    1. 在类路径:classpath:META-INF/services/接口全路径文件
    2. 在文件中配置实现类全路径com.itheima.impl.HondaCar
  4. ServiceLoader还实现了Iterable接口,可以进行迭代
  5. 原理:在ServiceLoader.load的时候,根据传入的接口Class对象,遍历META-INF/services目录下的以该接口命名的文件中的所有类,将创建实现类的对象返回。

SPI介绍

全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件
Java的SPI机制就是将一些类信息写在约定的文件中,然后由特定的类加载器ServiceLoader加载解析文件获取资源
Java SPI 基于"接口编程+策略模式+配置文件(约定)"组合实现的动态加载机制
运用场景

场景说明
数据库驱动数据库驱动加载接口实现类的加载 JDBC加载不同类型数据库的驱动
日志门面SLF4J接口实现类加载SLF4J加载不同提供商的日志实现类
SpringSpring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
DubboDubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装,允许用户扩展实现Filter接口
SpringBootSpringBoot基于SPI思想实现自动装配

SPI练习JDBC4.0免注册驱动原理

public class JdbcDemo {public static void main(String[] args) throws Exception {//1.加载驱动
//        Class.forName("com.mysql.jdbc.Driver");//2.获取连接Connection connection = DriverManager.getConnection("jdbc:mysql:///dbvue", "root", "1234");System.out.println(connection);}
}
//DriverManager类源码:
public class DriverManager {//静态代码块static {loadInitialDrivers();println("JDBC DriverManager initialized");}private static void loadInitialDrivers() {.....AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {//spi/*1.java.sql.Driver 是一个接口2.java.sql.Driver接口实现类,com.mysql.jdbc.Driver使用SerciveLoader类加载器调用方法获取java.sql.Driver接口的实现类对象放到LoadedDrivers中*/ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(java.sql.Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});......}
}

Servlet实现方式三 ServletContainerInitializer

前俩种方式是注解和xml
第三种就是spi方式,后面学习的框架底层就是采用这种方式
ServletContainerInitializer 是 Servlet 3.0 新增的一个接口,主要用于在容器启动阶段通过编程风格注册Filter, Servlet以及Listener,以取代通过web.xml配置注册。这样就利于开发内聚的web应用框架.
运行原理

  1. ServletContainerInitializer接口的实现类通过java SPI声明自己是ServletContainerInitializer 的提供者.
  2. web容器启动阶段依据java spi获取到所有ServletContainerInitializer的实现类,然后执行其onStartup方法.
  3. 在onStartup中通过编码方式将组件servlet加载到ServletContext

小结
ServletContainerInitializer 在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filter等,servlet规范中通过ServletContainerInitializer实现此功能。每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作
image.png


http://www.ppmy.cn/news/1461077.html

相关文章

【Excel VBA】深入理解 VBA 中的 CallByName 函数

动态调用方法与属性&#xff1a;深入理解 VBA 中的 CallByName 函数 昨天在介绍Choose函数在复杂的VBA应用程序开发中&#xff0c;有时我们需要根据运行时的情况来决定调用哪个对象的哪个方法或属性。这种灵活性可以通过 CallByName 函数实现。在本篇博客中&#xff0c;田辛老…

SpringSecurity6集成数据库

本文章对应视频可在B站查看SpringSecurity6对应视频教程&#xff0c;记得三连哦&#xff0c;这对我很重要呢&#xff01; 温馨提示&#xff1a;视频与文章相辅相成&#xff0c;结合学习效果更强哦&#xff01; 系列文章链接 1、初识SpringSecurity&#xff0c;认识主流Java权限…

【Toritoise SVN】SVN 怎么忽略文件夹下的所有文件但是不忽略文件夹本身

比如&#xff1a;忽略 Assets\StreamingAssets\LocalAsset文件夹下的所有文件但是不忽略LocalAsset这个文件夹 在TortoiseSVN中&#xff0c;你可以通过以下步骤来修改文件夹的svn:ignore属性&#xff1a; 打开Windows资源管理器&#xff0c;导航到你的工作副本中的Assets\Stre…

通过Aop测试业务层万次执行效率

前言 在开发复杂的应用程序时&#xff0c;业务层的效率通常是一个关键问题。为了保证系统的高性能和稳定性&#xff0c;我们需要及时发现并解决潜在的性能问题。本文将介绍如何利用 AOP 技术来测试业务层的效率&#xff0c;为系统性能优化提供有力支持。 1. 什么是 AOP&#x…

数据结构与算法学习笔记四---双向链表的表示和实现(C语言)

1.前言 这篇文章主要介绍双向链表的表示和实现。 2.双向链表 单链表中只有一个指示直接后继的指针域&#xff0c;由此&#xff0c;从某个结点出发只能顺指针向后寻查其他结点。若要寻查结点的直接前驱&#xff0c;则必须从表头指针出发。换句话说&#xff0c;在单链表中&#…

Redis如何避免数据丢失?——RDB

目录 1. RDB机制的配置 2. fork()函数和写时复制(Copy On Write&#xff09; 什么是Copy On Write 系统fork中使用Copy On Write机制 3. RDB文件结构 RDB文件内容和内容顺序 InfoAuxFields是rdb信息数据 数据库数据 数据 数据存储格式 字符串编码 操作码 4. RDB的2…

vue 中的 Vuex

Vuex Vuex是什么&#xff1f; 概念&#xff1a;专门在vue中实现集中式状态&#xff08;数据&#xff09;管理的一个Vue插件&#xff0c;对Vue应用中多个组件的共享状态进行集中式的管理(读/写&#xff09;&#xff0c;也是一种组件间通信的方式&#xff0c;且适用于任意组件间…

Spring boot环境的常见问题

文章目录 一、启动类无法运行二、包相关问题2.1 默认配置的包无法下载2.2 第三方库的包无法下载2.3 包找不到 三、出现了一个无效的源发行版17四、类文件具有错误的版本 61.0&#xff0c;应为52.0五、控制台乱码 一、启动类无法运行 原因&#xff1a;IDEA 没有把当前项目识别成…