10.中储智运面试题
10.1 线程池的使用
核心-》工作队列-》最大线程(核心小于最大)
10.2 自动装配starter自定义系统访问量
思路:
- 创建新项目:初始化项目,添加依赖
- 设计统计服务逻辑:定义一个接口,定义新增访问量和获取总访问量的方法
- 自动配置:定义一个配置类添加FilterRegistrationBean的方法将该Bean注册到Spring容器中。在该Bean上设置自定义的统计服务接口;添加请求组
- 添加过滤器:在每次请求后调用统计服务增加访问计数。
- 创建Spring Factories文件:在META-INF/spring.factories文件中,注册自动配置类的配置属性类
- 打包发布
- 使用自定义starter
实现:
10.3 gateway过滤链流程(官网)
上图是Spring官网的Gateway请求流程图。
首先客户端请求进入到Gateway网关,Gateway的HandlerMapping会根据路由规则匹配请求,如果匹配成功,则会进入到Gateway Web Handler,通过过滤链处理请求前和请求后以及响应前。
10.4 ThreadLocal内存泄漏原理
ThreadLocal被称为线程变量,该变量对其他线程隔离。
- 每个线程都有自己的实例副本,且该副本优当前Thread使用;
- 每个线程都有自己的实例副本,且其他线程不可访问,因此不存在多线程共享的问题。
上图就是ThreadLocal的内部结构,每一个线程中有一个ThreadLocalMap,每个ThreadLocalMap以ThreadLocal作为Key,所需存储的值作为value进行存储。
重点:ThreadLocal作为线程中ThreadLocalMap属性中某一个Entry的Key,虽然多个线程间ThreadLocal这个Key相同,但是不同线程拥有的ThreadLocalMap是独一无二的,即不同线程同一个Key对应的value不同,从而达到线程隔离的目的,但同一线程中value变量的地址相同。
ThreadLocal的底层是ThreadLocalMap以ThreadLOcal实例做key(弱引用),但是value是强引用,这导致了即使ThreadLocal对象本身不再被使用,其关联的值也无法被垃圾回收,从而引发内存泄漏。
10.5 主子线程间值传递(阿里的TTL)
10.6 RBAC权限按钮控制
RBAC(Role-Based Access Control,基于角色的访问控制)权限按钮控制是一种细粒度的权限管理方式,它允许系统管理员根据用户的角色来精确控制用户界面中功能按钮的可见性和可用性。在RBAC模型中,权限不仅限于访问某个页面或模块,还可以细化到页面上每个操作按钮的权限。
前几天以及前段时间一直在思考这个问题,昨天与好友一起讨论了下这个问题,并结合自己在网上查到的资料得以实现。下面给出整个项目代码的重点部分,完整代码如下:
以下代码借鉴了这篇博客,有兴趣可以查看【认证与授权】2、基于session的认证方式 - 黑米面包派 - 博客园 (cnblogs.com)
10.6.1依赖
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ku.test</groupId> <artifactId>test-project</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.7.RELEASE</version> <relativePath /> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> </project>
10.6.2 配置
server.port=8080
10.6.3 主启动类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.ku.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
10.6.4 用户信息
为了方便测试这里只是列出了简单的用户名、密码、权限集合等属性
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.ku.test.entity; import java.util.Set; public class User { private Integer id; private String username; private String password; private Set<String> auth; public User(Integer id, String username, String password, Set<String> auth) { this.id = id; this.username = username; this.password = password; this.auth = auth; } public Integer getId() { return id; } public String getUsername() { return username; } public String getPassword() { return password; } public Set<String> getAuth() { return auth; } }
10.6.5 service层
为了方便测试,并没有使用Mapper从数据库取数据,而是使用Map存储用户信息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.ku.test.service; import com.ku.test.entity.User; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @Service public class UserService { Map<String, User>userMap = new HashMap<String, User>(){{ put("xiaoku", new User(1, "xiaoku", "123456", new HashSet<String>(){{ add("/user/test"); add("/user/test1"); }} )); put("spring", new User(2, "spring", "123456", new HashSet<String>(){{ add("/user/test1"); add("/user/test2"); add("/user/test3"); add("/user/test4"); add("/user/test5"); }} )); }}; public User getUserByName(String username){ return userMap.get(username); } public User auth(String username, String password){ User user = userMap.get(username); if (user == null) throw new RuntimeException("用户不存在!"); if (!password.equals(user.getPassword())) throw new RuntimeException("密码错误!"); return user; } }
10.6.6 Controller层
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.ku.test.controller; import com.ku.test.common.constant.CommonConstants; import com.ku.test.entity.User; import com.ku.test.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/test") public String test(){ return "test"; } @GetMapping("/test1") public String test1(){ //如果请求参数中没有包含HttpServletRequest,则不会被拦截器拦截 return "test1"; } @PostMapping("/login") public String login(HttpServletRequest request){ String username = request.getParameter("username"); String password = request.getParameter("password"); if (request == null || StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) return "用户名或密码为空"; User user = userService.auth(username, password); request.getSession().setAttribute(CommonConstants.USER_SESSION_KET, user); return user.getUsername()+"登录成功!"; } }
10.6.7 配置类
注册认证授权拦截器,并设置拦截路径和非拦截路径
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.ku.test.config; import com.ku.test.interceptor.AuthorizeInterceptor; import com.ku.test.interceptor.LoginInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //1.添加认证拦截器,并设置拦截路径以及排除路径 registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login"); //2.添加授权拦截器,并设置拦截路径以及排除路径 registry.addInterceptor(new AuthorizeInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login"); } }
10.6.8 认证授权拦截器
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.ku.test.interceptor; import com.ku.test.common.constant.CommonConstants; import com.ku.test.entity.User; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { User user = (User)request.getSession().getAttribute(CommonConstants.USER_SESSION_KET); //认证:在拦截器中拦截所有Controller方法的请求,但是排除登录和注册两个请求 // 当用户注册登录后,如果用户为空则说明未登录, if (user != null) return true; response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json;charset=UTF-8"); //以字符串返回未登录信息给客户端 String errorMessage = new String("请先登录!"); //写入Response返回给客户端 response.getWriter().write(errorMessage); return false; } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.ku.test.interceptor; import com.ku.test.common.constant.CommonConstants; import com.ku.test.entity.User; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Iterator; import java.util.Set; public class AuthorizeInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获取该用户的请求路径 String path = request.getServletPath(); User user = (User)request.getSession().getAttribute(CommonConstants.USER_SESSION_KET); Set<String> auth = user.getAuth(); boolean successAuthorize = false; Iterator<String> iterator = auth.iterator(); while (iterator.hasNext()){ if (auth.contains(path)){ successAuthorize = true; break; } break; } if (successAuthorize) return true; response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); response.setContentType("application/json;charset=UTF-8"); //以字符串返回未登录信息给客户端 String errorMessage = new String("权限不足!"); //写入Response返回给客户端 response.getWriter().write(errorMessage); return false; } }
10.6.9 结果
(1)拥有某个方法访问权限的用户
![](https://img2024.cnblogs.com/blog/2350134/202405/2350134-20240513171810935-209048164.png)
![](https://img2024.cnblogs.com/blog/2350134/202405/2350134-20240513171825247-691417548.png)
(2)无某个方法访问权限的用户
![](https://img2024.cnblogs.com/blog/2350134/202405/2350134-20240513171928410-683334005.png)
![](https://img2024.cnblogs.com/blog/2350134/202405/2350134-20240513171947069-885499112.png)
参考链接
史上最全ThreadLocal 详解(二)_史上最全threadlocal 详解(二)-CSDN博客