☰
Tomcat请求处理链路:从Socket到Servlet的7层穿透原理
2026/10/23 14:24:23 网站建设 项目流程

简介:本资源为2025年最新整理的Java核心面试题精编文档,面向Java初中级开发者、应届求职者及技术面试备考人员,聚焦高频考点与底层原理辨析,助力系统梳理知识脉络、突破面试难点。全文以Word文档(.docx)形式呈现,共1个文件,大小仅37KB,轻量易读,内容涵盖Tomcat运行机制、Spring IOC/AOP/DI工作原理、JVM字节码加载与执行流程、基本类型与包装类深层差异、Shiro权限标签实战用法、Servlet完整生命周期,以及Java作用域层级与内存分布等七大模块,每部分均含原理阐述与关键细节对比。已有544人学习下载,适合作为面试前速查清单、原理复习提纲或技术分享素材,结构清晰、要点凝练、无冗余代码,直击面试官常问的‘为什么’与‘怎么运作’类问题。

1. Tomcat启动不是“一键运行”:8080端口背后藏着4个必须答对的底层链路考点

别再背“Tomcat是Servlet容器”这种空话了——2025年Java面试官一开口就卡你命门:“请画出从main()执行到你浏览器看到index.jsp的完整调用链,标出每个环节的类名、方法名、线程状态和数据流向。”这题直接筛掉70%只配过Spring Boot自动配置的候选人。真实考法是:不考概念定义,专考控制流穿透能力——你能顺着请求从Socket层一直追到JSP编译器吗?能说清Server→Service→Connector→Container→Wrapper→Servlet这条链上,哪一步触发类加载、哪一步决定线程复用、哪一步做URL映射吗?这份《Java面试题精选(2025最新版)》把Tomcat、Spring、JVM、Servlet、Shiro五大模块拆成可验证的执行路径图谱,每道题都带真实调试断点位置和日志输出示例。适合正在冲刺中高级岗位、被“原理题”反复暴击、急需把零散知识点焊成肌肉记忆的开发者。它不教你怎么写Hello World,只教你怎么在面试官追问“为什么是这个类加载器?”时,掏出一张手绘的双亲委派流程图。

2. Tomcat运行原理:4个核心链路节点与3道高频真题拆解

Tomcat不是黑匣子,它是可调试、可打断点、可日志追踪的确定性系统。面试官要的不是“它监听8080端口”,而是你能否说出Bootstrap.main()里第几行代码触发Server.start(),以及StandardServer.await()阻塞时线程栈长什么样。下面按真实执行顺序拆解关键链路,并附带高频真题及答题逻辑。

2.1 Server启动链路:从main()到await()的初始化全景

Tomcat启动入口是org.apache.catalina.startup.Bootstrap的main()方法。它通过反射创建Catalina实例并调用load()和start()。Catalina.start()最终触发StandardServer.startInternal(),该方法会:

  • 启动所有Service(如StandardService)
  • 每个Service启动其Connector(如Http11NioProtocol)和Container(如StandardEngine)
  • Connector启动Endpoint(如NioEndpoint),后者真正调用java.nio.channels.ServerSocketChannel.open()绑定8080端口
  • Endpoint启动Acceptor线程,进入while (running) { channel.accept() }循环等待连接

提示:面试官常问“Tomcat怎么实现高并发?”答案不在NIO本身,而在Acceptor只负责accept连接,立即将SocketChannel转给Poller线程池处理I/O事件,避免阻塞主线程。这是理解其吞吐量的关键分水岭。

2.2 请求处理链路:从Socket读取到Servlet执行的7层穿透

当浏览器访问http://localhost:8080/app/hello时,数据流如下:

  1. NioEndpoint.Poller检测到OP_READ事件,将NioChannel交给SocketProcessorBase
  2. SocketProcessorBase.doRun()调用AbstractProcessorLight.process()解析HTTP头,生成Request和Response对象
  3. CoyoteAdapter.service()将org.apache.coyote.Request适配为javax.servlet.http.HttpServletRequest
  4. StandardHostValve.invoke()根据Host名路由到对应StandardContext
  5. StandardContextValve.invoke()根据URL路径匹配StandardWrapper
  6. StandardWrapperValve.invoke()调用ServletWrapper.allocate()获取Servlet实例(首次调用触发init())
  7. ApplicationFilterChain.doFilter()执行过滤器链后,最终调用Servlet.service()

注意:StandardWrapper持有Servlet实例缓存,但Servlet本身无状态;Request/Response对象在每次请求中全新创建,避免线程安全问题。

2.3 类加载与实例化:动态加载背后的双亲委派破局点

Tomcat打破JVM默认双亲委派机制,为每个Web应用创建独立的WebappClassLoader。其加载顺序为:

当前类加载器 → /WEB-INF/classes → /WEB-INF/lib/*.jar → 系统类加载器(rt.jar等)→ Bootstrap类加载器

关键点在于:Servlet类由WebappClassLoader加载,但其父类(如HttpServlet)由CommonClassLoader加载。当StandardWrapper.loadServlet()调用Class.forName("com.example.HelloServlet", true, classLoader)时:

  • 若HelloServlet未加载,则WebappClassLoader.loadClass()先委托父加载器查HttpServlet(存在),再自己加载HelloServlet
  • 实例化时,newInstance()使用当前类加载器,确保HelloServlet与HttpServlet的类加载器隔离

提示:这就是为什么修改/WEB-INF/classes下class文件后,Tomcat能热部署——它丢弃旧WebappClassLoader,新建一个重新加载所有类,而HttpServlet等基础类仍由父加载器提供,无需重复加载。

2.4 高频真题1:请描述Tomcat如何处理一个HTTP GET请求?要求指出至少5个关键类及其作用

答题思路:拒绝泛泛而谈“接收请求→处理→返回”,必须锚定具体类名和职责边界。面试官想听的是你是否真看过源码或调试过。

  • NioEndpoint.Poller:轮询Selector获取就绪的SocketChannel,是I/O事件分发中心
  • CoyoteAdapter:将Coyote协议对象(org.apache.coyote.Request)转换为Servlet规范对象(HttpServletRequest),完成协议适配
  • StandardContextValve:根据URL路径(如/hello)查找匹配的StandardWrapper,是URL映射执行者
  • StandardWrapperValve:管理Servlet生命周期,调用allocate()获取实例,是Servlet执行调度器
  • ApplicationFilterChain:按@WebFilter或web.xml声明顺序执行过滤器,是横切逻辑注入点

易错点:很多人把StandardEngineValve或StandardHostValve当作核心,其实它们只做容器级路由(Engine→Host→Context),不参与具体Servlet调用。

2.5 高频真题2:为什么修改web.xml后Tomcat需要重启,而修改JSP页面却不用?

答题逻辑:直指类加载机制差异——web.xml驱动容器初始化,JSP本质是动态编译的Servlet。

  • web.xml在StandardContext.startInternal()阶段被解析,用于创建FilterDef、ServletDef等元数据对象。这些对象决定FilterChain和Wrapper的初始化顺序。修改后,旧的StandardContext已构建完毕,无法热更新其内部结构,必须销毁重建。
  • JSP文件由JspServlet(实际是JasperInitializer)在首次访问时编译为.java,再经Javac编译为.class,最后由WebappClassLoader加载。后续访问时,JspServlet会检查JSP文件时间戳,若变更则重新编译并替换WebappClassLoader中的对应Class对象,无需重启Context。

验证方式:在conf/web.xml中设置<init-param><param-name>development</param-name><param-value>true</param-value></init-param>,即可开启JSP热编译。

2.6 高频真题3:Tomcat的8005、8043、8080端口各承担什么角色?能否关闭8005?

标准答案:

  • 8080:HTTP/1.1协议端口,处理客户端HTTP请求(默认,可改)
  • 8009:AJP协议端口(非8043!原文有误),供Apache HTTPD等前置服务器反向代理,传输二进制格式请求(更高效)
  • 8005:Shutdown端口,监听SHUTDOWN命令字符串(默认值),收到后触发StandardServer.stop()优雅关闭

能否关闭?能,且强烈建议生产环境关闭。在conf/server.xml中注释或删除<Server port="8005" shutdown="SHUTDOWN">节点。关闭后需改用kill -15 <pid>或./catalina.sh stop(依赖CATALINA_PID文件)。

血泪经验:某次线上事故因运维误发telnet localhost 8005后输入SHUTDOWN,导致集群全量宕机。现在我们所有生产镜像都默认禁用该端口,并添加iptables拦截。

3. Spring工作原理:IOC/DI/AOP三驾马车的执行时序与3个致命误区

Spring不是魔法,它是基于Java反射、动态代理、BeanDefinition注册的精密流水线。面试官最常设陷阱:让你画出@Autowired字段注入的完整时序图,结果90%的人卡在“什么时候调用populateBean()”。这份资料把Spring容器启动拆成可断点的12个关键步骤,每步标注源码类名和调试技巧。

3.1 IOC容器启动四阶段:从refresh()到Bean实例化的精确切片

AbstractApplicationContext.refresh()是Spring容器启动总入口,其核心流程为:

  1. prepareRefresh():设置启动时间、激活标志位、初始化Environment
  2. obtainFreshBeanFactory():创建DefaultListableBeanFactory,加载BeanDefinition(XML/注解扫描结果)
  3. invokeBeanFactoryPostProcessors():执行BeanFactoryPostProcessor(如PropertyPlaceholderConfigurer),此时Bean尚未实例化,可修改BeanDefinition
  4. registerBeanPostProcessors():注册BeanPostProcessor(如AutowiredAnnotationBeanPostProcessor),为后续Bean初始化增强做准备
  5. finishBeanFactoryInitialization():核心阶段——遍历所有BeanDefinition,调用getBean()触发实例化、属性填充、初始化

注意:getBean()不是简单new对象。它先调用createBeanInstance()(反射构造)、再populateBean()(注入@Autowired字段)、最后initializeBean()(执行@PostConstruct和InitializingBean.afterPropertiesSet())

3.2 DI注入三层次:字段注入、构造器注入、Setter注入的本质差异

注入方式触发时机依赖检查循环依赖支持源码关键方法
字段注入(@Autowired)populateBean()中调用AutowiredAnnotationBeanPostProcessor.postProcessProperties()运行时检查(@Required)仅支持Setter/字段注入,构造器注入会报错InjectionMetadata.inject()
构造器注入createBeanInstance()中通过SmartInstantiationAwareBeanPostProcessor.determineCandidateConstructors()选择编译期检查(final修饰)不支持(构造器需完整依赖)ConstructorResolver.autowireConstructor()
Setter注入populateBean()中调用applyPropertyValues()运行时检查(@Required)支持(提前暴露ObjectFactory)AbstractAutowireCapableBeanFactory.populateBean()

关键结论:构造器注入最安全(不可变性、编译检查),但牺牲了Spring的循环依赖解决能力;字段注入最便捷,但隐藏了依赖关系,单元测试困难。

3.3 AOP代理生成时机:JDK动态代理与CGLIB的抉择逻辑

Spring AOP在initializeBean()后,registerDisposableBeanIfNecessary()前触发代理创建。决策逻辑如下:

// AbstractAutoProxyCreator.wrapIfNecessary() if (beanName != null && this.targetSourcedBeans.contains(beanName)) { // 目标源Bean,跳过代理 } else if (!this.advisedBeans.containsKey(cacheKey)) { if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { // 基础类(Advice/Pointcut)或跳过类(@Aspect),不代理 this.advisedBeans.put(cacheKey, Boolean.FALSE); } else { // 获取匹配的Advisor(@Before/@After等) Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, null); if (specificInterceptors != DO_NOT_PROXY) { // 创建代理 this.advisedBeans.put(cacheKey, Boolean.TRUE); Object proxy = createProxy(beanClass, beanName, specificInterceptors, new SingletonTargetSource(bean)); return proxy; } } }

代理类型选择规则:

  • 目标类实现接口 → 默认JDK动态代理(Proxy.newProxyInstance())
  • 目标类无接口或强制proxy-target-class=true→ CGLIB代理(Enhancer.create())
  • CGLIB代理要求目标类不能是final,方法不能是final(否则无法重写)

提示:面试官爱问“为什么@Transactional在private方法上失效?”——因为CGLIB代理通过子类重写方法,private方法无法被继承,故无法增强。JDK代理同理,private方法不在接口中。

3.4 易错题:Spring Bean的循环依赖是如何解决的?三级缓存具体存什么?

错在哪:只答“用三级缓存解决”,说不出缓存key、value、何时存何时取。
为什么错:没理解Spring解决的是单例Bean的构造器之后、初始化之前的循环引用,而非所有循环依赖。
怎么答对:

Spring用三个Map解决:

缓存级别Map名称存储内容存入时机取出时机作用
一级缓存singletonObjects完整初始化的Bean(getBean()最终返回的对象)addSingleton()在getSingleton()末尾getSingleton(String beanName)主逻辑对外提供成品Bean
二级缓存earlySingletonObjects提前曝光的Bean(构造完成但未填充属性),是ObjectFactory的getObject()返回值addEarlySingletonObject()在getSingleton()中beforeSingletonCreation()后getSingleton()中singletonObject == null时解决普通循环依赖(A→B→A)
三级缓存singletonFactoriesObjectFactory工厂(lambda表达式),用于生成早期引用,支持AOP代理addSingletonFactory()在getSingleton()中beforeSingletonCreation()后立即getSingleton()中earlySingletonObjects未命中时,调用getObject()生成代理解决AOP场景下的循环依赖(A→B→A且B被代理)

执行流程:A创建时,addSingletonFactory("a", () -> getEarlyBeanReference(a))→ B创建时getBean("a")触发getSingleton("a")→ 先查一级缓存(空)→ 查二级缓存(空)→ 查三级缓存,执行ObjectFactory.getObject()生成A的早期引用(可能是原始A或A的代理)→ 放入二级缓存 → B填充属性 → A填充属性 → A初始化完成 → 移入一级缓存。

3.5 易错题:@Transactional注解为什么在同一个类的内部方法调用时失效?

错在哪:答“因为是代理对象调用”,但说不清代理对象从哪来、谁在调用。
为什么错:混淆了代理创建时机和调用链路。this.methodB()是目标对象自身调用,绕过了代理对象。
怎么答对:

  • @Transactional生效的前提是:调用方必须持有代理对象的引用,而非目标对象引用。
  • 当serviceA.methodA()调用this.methodB()时,this指向的是目标对象(Target Object),methodB()直接执行,不经过TransactionInterceptor.invoke()。
  • 正确做法:注入自身(@Autowired ServiceA self;),然后调用self.methodB(),此时self是代理对象。
  • 或使用AopContext.currentProxy()(需开启expose-proxy="true"),但这是反模式,破坏封装。

验证代码:

@Service public class OrderService { @Autowired private OrderService self; // 自注入代理对象 public void createOrder() { // this.pay() → 失效(this是目标对象) // self.pay() → 生效(self是代理对象) self.pay(); } @Transactional public void pay() { // 数据库操作 } }

3.6 易错题:Spring Boot的@EnableAutoConfiguration是如何工作的?它和传统Spring的@ComponentScan有什么区别?

错在哪:只答“扫描META-INF/spring.factories”,不说清加载时机和条件判断。
为什么错:没意识到自动配置是条件化加载,不是无脑注册Bean。
怎么答对:

  • @EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)导入。
  • AutoConfigurationImportSelector.selectImports()读取META-INF/spring.factories中org.springframework.boot.autoconfigure.EnableAutoConfiguration键的所有值(如DataSourceAutoConfiguration)。
  • 关键过滤:对每个候选配置类,调用getAutoConfigurationEntry(),执行ConditionEvaluator.shouldSkip()——检查@ConditionalOnClass(类路径存在)、@ConditionalOnMissingBean(容器中无该Bean)等条件。
  • 例如DataSourceAutoConfiguration只有在类路径有HikariDataSource且容器中无DataSourceBean时才生效。

与@ComponentScan区别:

  • @ComponentScan:扫描@Component等注解,注册BeanDefinition,无条件。
  • @EnableAutoConfiguration:读取预定义配置类,按条件筛选后注册,是“智能装配”。

4. JVM运行原理:字节码加载、解释执行与GC策略的3个硬核考点

JVM不是黑盒,它是可监控、可调优、可生成字节码的确定性引擎。面试官不再问“JVM内存模型”,而是抛出javap -c反编译结果,让你解释monitorenter/monitorexit指令为何出现在synchronized块中。这份资料把JVM拆成字节码→类加载→运行时数据区→执行引擎→GC五层透镜,每层配真实字节码片段和JVM参数验证。

4.1 字节码生成与验证:javac编译器的3个关键优化

Java源码经javac编译为.class文件,其字节码结构包含常量池、字段表、方法表。关键点:

  • 字符串拼接优化:String s = "a" + "b"→ 编译期直接合并为"ab",存入常量池;String s = str1 + str2→ 编译为new StringBuilder().append(str1).append(str2).toString()。
  • 自动装箱拆箱:Integer i = 1;→ 编译为Integer.valueOf(1);int j = i;→ 编译为i.intValue()。
  • try-with-resources:try (FileInputStream fis = new FileInputStream("a.txt")) { ... }→ 编译器自动生成finally块调用fis.close(),即使try块抛异常也保证执行。

验证命令:

# 编译 javac Test.java # 反编译查看字节码 javap -c Test.class

4.2 类加载双亲委派模型:打破它的3种合法场景

双亲委派模型要求类加载器先委托父加载器,父无法加载时才自己加载。但以下场景必须打破:

  1. SPI机制(Service Provider Interface):java.util.ServiceLoader加载META-INF/services/java.sql.Driver时,使用线程上下文类加载器(TCCL),即Thread.currentThread().getContextClassLoader(),通常是AppClassLoader,而非BootstrapClassLoader(它看不到/WEB-INF/lib)。
  2. 热部署/模块化:OSGi、Jigsaw模块系统中,每个模块有独立类加载器,需隔离加载,避免双亲委派导致版本冲突。
  3. Tomcat Web应用:如前所述,WebappClassLoader需优先加载/WEB-INF/classes,而非委托CommonClassLoader加载java.lang.String。

代码示例(SPI加载):

// ServiceLoader使用TCCL ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class); // 内部调用:ClassLoader cl = Thread.currentThread().getContextClassLoader();

4.3 运行时数据区实战:堆、栈、方法区的内存布局与OOM定位

JVM运行时数据区包括:

区域存储内容线程共享OOM典型场景关键JVM参数
程序计数器当前线程执行字节码行号线程私有无—
Java虚拟机栈局部变量、操作数栈、动态链接、方法出口线程私有StackOverflowError(递归过深)、OutOfMemoryError(线程过多)-Xss256k
本地方法栈Native方法调用线程私有同上—
Java堆对象实例、数组线程共享java.lang.OutOfMemoryError: Java heap space(内存泄漏、大对象)-Xms512m -Xmx2g
方法区(元空间)类信息、常量、静态变量、即时编译代码线程共享java.lang.OutOfMemoryError: Metaspace(大量动态生成类,如CGLIB)-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m

定位OOM工具链:

  • jstat -gc <pid>:实时查看GC次数、耗时、各区内存占用
  • jmap -histo:live <pid>:显示存活对象数量及大小(按类名分组)
  • jmap -dump:format=b,file=heap.hprof <pid>:生成堆快照,用VisualVM或Eclipse MAT分析

4.4 解释器与JIT编译器协同:热点代码的3级编译策略

JVM执行字节码有两种模式:

  • 解释执行:逐条翻译字节码为机器码,启动快,但执行慢。
  • JIT编译(Just-In-Time):对热点代码(方法调用超10000次或循环体执行超100000次)编译为本地机器码,执行快,但编译耗时。

HotSpot VM采用分层编译(Tiered Compilation):

编译层级执行引擎触发条件特点
第0层解释器启动时无编译开销,快速响应
第1层C1编译器(Client Compiler)方法调用计数器超阈值快速编译,优化少(如方法内联),适合启动阶段
第2层C2编译器(Server Compiler)循环回边计数器超阈值深度优化(逃逸分析、锁消除、向量化),适合长期运行

验证命令:

# 查看JIT编译日志 java -XX:+PrintCompilation Test # 输出示例:123 1 3 java.lang.String::hashCode (67 bytes) # 表示第123毫秒,第1个编译任务,C1编译器,编译hashCode方法(67字节)

4.5 GC算法与调优:G1收集器的Region分区与Mixed GC触发逻辑

G1(Garbage First)是JDK9+默认GC,其核心思想是将堆划分为多个2MB大小的Region,优先回收垃圾最多的Region(Garbage First)。

  • Young GC:当Eden区满时触发,复制存活对象到Survivor或Old区。
  • Mixed GC:当老年代占用率达-XX:InitiatingOccupancyPercent=45%(默认)时触发,同时回收所有Young Region和部分Old Region。
  • Full GC:当Mixed GC无法释放足够空间时触发,STW时间长,需避免。

关键调优参数:

  • -XX:MaxGCPauseMillis=200:设置最大停顿时间目标(G1尽力满足,非绝对)
  • -XX:G1HeapRegionSize=2M:Region大小(1MB~32MB,根据堆大小自动计算)
  • -XX:G1NewSizePercent=20:Young区最小占比(默认5%)

诊断GC日志:

# 开启详细GC日志 java -Xlog:gc*:file=gc.log:time,tags:filecount=5,filesize=100m Test # 日志中Mixed GC标识:[GC pause (G1 Evacuation Pause) (mixed)]

5. Java基础与框架细节:包装类、Servlet生命周期、Shiro标签的3个易踩坑点

基础题最容易翻车——你以为在考概念,其实考的是JVM规范细节、Servlet规范版本差异、框架源码行为。比如“Integer i=127; Integer j=127; i==j为true”这题,90%的人答“缓存”,但答不出IntegerCache的范围是-128~127,更答不出-XX:AutoBoxCacheMax=200可以扩展。下面直击3个高频失分点。

5.1 包装类缓存陷阱:Integer、Long、Boolean的缓存范围与自定义

Java为提升性能,对部分包装类实现缓存:

类型缓存范围是否可配置缓存类
Booleantrue/false否Boolean内部静态实例
Byte-128 ~ 127否ByteCache
Short-128 ~ 127否ShortCache
Integer-128 ~ 127是,-XX:AutoBoxCacheMax=<value>IntegerCache
Long-128 ~ 127是,-XX:AutoBoxCacheMax=<value>LongCache

关键代码:

// Integer.valueOf()源码节选 public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }

易错场景:

Integer a = 128; Integer b = 128; System.out.println(a == b); // false(超出缓存范围,new两个对象) // 但 Integer c = 127; Integer d = 127; System.out.println(c == d); // true(在缓存范围内,指向同一对象)

血泪教训:某支付系统用==比较订单状态Integer status,测试环境127正常,上线后128失败。改为equals()或status.intValue() == 128。

5.2 Servlet生命周期:init()、service()、destroy()的线程安全真相

Servlet规范规定:一个Servlet实例被多个线程共享。因此:

  • init():单线程执行,线程安全,适合初始化全局资源(数据库连接池、配置加载)。
  • service():多线程并发执行,非线程安全,局部变量安全,但实例变量(字段)和静态变量需同步。
  • destroy():单线程执行,线程安全,适合释放资源(关闭连接池、清理缓存)。

反模式代码:

@WebServlet("/counter") public class CounterServlet extends HttpServlet { private int count = 0; // 错!实例变量被多线程共享 protected void doGet(HttpServletRequest req, HttpServletResponse resp) { count++; // 非原子操作,竞态条件 resp.getWriter().print(count); } }

正确做法:

  • 使用局部变量(int count = getCountFromDB();)
  • 使用线程安全类(AtomicInteger count = new AtomicInteger(0);)
  • 使用synchronized块(性能差,慎用)

Servlet 4.0新特性:@WebServlet(asyncSupported = true)支持异步处理,AsyncContext可将请求移交其他线程,避免阻塞容器线程。

5.3 Shiro权限标签:guest/user/authenticated的语义鸿沟与Session劫持风险

Shiro标签看似简单,但<shiro:guest>和<shiro:user>的区别常被误解:

标签条件典型场景安全风险
<shiro:guest>SecurityUtils.getSubject().getPrincipal() == null未登录用户可见的注册/登录入口无
<shiro:user>`subject.isAuthenticated()subject.isRemembered()`
<shiro:authenticated>subject.isAuthenticated() == true敏感操作(如支付)必须强认证更安全,避免“记住我”的弱认证

致命误区:认为<shiro:user>比<shiro:guest>更“高级”,其实它放宽了安全要求。

加固方案:

  • 敏感页面强制<shiro:authenticated>
  • “记住我”功能启用rememberMeManager.setCipherKey(Base64.decode("..."))加密Cookie
  • Session超时设置sessionManager.setGlobalSessionTimeout(30 * 60000)(30分钟)

验证Shiro状态:

Subject subject = SecurityUtils.getSubject(); System.out.println("isAuthenticated: " + subject.isAuthenticated()); // true仅当login()成功 System.out.println("isRemembered: " + subject.isRemembered()); // true当cookie有效 System.out.println("getPrincipal: " + subject.getPrincipal()); // 用户名或Principal对象

6. 面试官最想听的答题心法:用“执行路径图谱”代替死记硬背

从那以后我每次准备原理题,都强制走一遍三步建模法:第一步,用jstack或IDEA Debugger抓取真实线程栈,画出从main()到目标方法的完整调用链,标出每个方法的类名、包名、关键参数;第二步,用javap -c反编译核心类,把字节码指令和Java代码逐行对照,搞清synchronized为何生成monitorenter、Stream.collect()如何调用CollectorImpl;第三步,用jstat和jmap在测试环境复现OOM或GC问题,截图堆内存分布,把“老年代占用率85%”这种模糊描述,变成“jstat -gc 12345显示MGCC列持续增长,jmap -histo:live 12345 \| head -20显示byte[]占堆70%”的硬证据。

这不是炫技,是让面试官相信:你说的每一个字,都来自你亲手敲过的命令、亲眼见过的日志、亲手修复过的Bug。当他说“讲讲Spring循环依赖”,你掏出手机展示刚截的AbstractBeanFactory源码截图,指着singletonFactories那行注释说:“这里存的是ObjectFactory,它的getObject()在B创建时被调用,生成A的早期引用……”——那一刻,他不会再问“为什么”,只会问“你遇到过哪些实际场景?”

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询