第八篇:Spring Boot——把配置从XML搬到了注解里
别再背自动配置的源码了你只需要知道“Spring Boot就是帮你把Spring的脏活累活干了”写在前面Spring Boot是Java后端开发的事实标准。面试里问到Spring Boot本质上是在问两件事①自动配置是怎么实现的②IoC和AOP的底层原理是什么很多人一上来就背SpringBootApplication注解的源码背得滚瓜烂熟但说不清“这东西到底解决了什么问题”。Spring Boot的本质就一句话把Spring原来那一大堆XML配置全给你自动配好了你只需要写业务代码。你以前用Spring MVC要手动配数据源、事务管理器、视图解析器、拦截器……一大堆XML。Spring Boot说“别配了我按业界主流约定给你配好你想改再改。”这就是**“约定优于配置Convention over Configuration”**。一、Spring Boot是什么解决了什么问题没有Spring Boot之前Spring 1.x/2.x时代写web.xml配置DispatcherServlet写applicationContext.xml配置数据源、事务、AOP写spring-servlet.xml配置Controller扫描、视图解析器部署到Tomcat启动一套配置下来少说几百行XML换个数据库就要改三四个地方特别容易配错。有了Spring Boot之后引入spring-boot-starter-web写个application.yml配数据库地址写个SpringBootApplication启动类直接main()方法运行内嵌Tomcat自动启动开发效率提升10倍不止。Spring Boot把“用什么框架”变成“引入什么Starter”把“怎么配”变成“按默认约定来”这就是它最大的价值。二、自动配置原理Spring Boot的灵魂这是Spring Boot面试的头号重灾区。你要能讲清楚Spring Boot是怎么知道我配了MySQL就自动帮我配好DataSource的2.1SpringBootApplication到底干了什么SpringBootApplication是个组合注解包含了三个核心注解SpringBootConfiguration本质上就是个Configuration标记这是个配置类。EnableAutoConfiguration自动配置的核心开启了自动配置功能。ComponentScan扫描当前包及子包把Component、Service、Controller、Repository等注解的类注册成Bean。2.2EnableAutoConfiguration干了什么这个注解通过Import导入了AutoConfigurationImportSelector。AutoConfigurationImportSelector的核心逻辑是从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里读取所有自动配置类的全限定名。Spring Boot 2.7之前用的是spring.factories文件2.7之后改用AutoConfiguration.imports但原理一样。把这些配置类全部加载进来。每个配置类上用Conditional条件注解控制是否生效。说白了就是Spring Boot扫描了一个文件里面列了所有“可能”的配置类然后挑出“当前环境满足条件”的那些来生效。2.3Conditional条件注解控制哪些配置生效这是Spring Boot最精妙的设计。每个自动配置类上都有条件注解只有满足条件才生效。注解含义ConditionalOnClass(DataSource.class)classpath里有DataSource类才生效ConditionalOnMissingBean(DataSource.class)用户没有自己配DataSource时才生效ConditionalOnProperty(name spring.datasource.url)配置文件里有spring.datasource.url才生效ConditionalOnWebApplication是Web应用才生效这就是“按需加载”的原理Configuration(proxyBeanMethodsfalse)ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class})ConditionalOnMissingBean(typeio.r2dbc.spi.ConnectionFactory)EnableConfigurationProperties(DataSourceProperties.class)Import({DataSourcePoolMetadataProvidersConfiguration.class,...})publicclassDataSourceAutoConfiguration{// 自动配置数据源的代码}这个配置类生效的条件是有DataSource类说明引入了JDBC驱动用户没有自己配ConnectionFactory配置文件里有数据源相关配置三个条件都满足Spring Boot才帮你自动配数据源。2.4spring.factories和AutoConfiguration.imports面试官追问“Spring Boot是怎么知道要去加载哪些自动配置类的”在spring-boot-autoconfigure的jar包里有一个文件Spring Boot 2.7之前META-INF/spring.factoriesSpring Boot 2.7及之后META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports这个文件里列了所有的自动配置类比如org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration ...AutoConfigurationImportSelector会读取这个文件把所有类名加载进来再逐个判断条件是否满足。2.5 如何自定义Starter面试加分写一个自己的Starter只需要三步写自动配置类ConfigurationConditional条件注解 EnableConfigurationProperties绑定配置属性。写配置属性类用ConfigurationProperties(prefix xxx)绑定application.yml里的配置。在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports里注册加上你的配置类全限定名。然后别人引入你的StarterSpring Boot启动时就会自动加载你的配置实现开箱即用。三、IoC控制反转就是那个大MapIoC是Spring的基石。理解了IoC你就理解了Spring一半。3.1 IoC是什么“控制反转”这四个字太抽象了我们把它拆开说以前你自己创建对象UserService service new UserService();→ 你掌控对象的生命周期。用了Spring你加个Service注解Spring在启动时帮你创建对象放进一个大Map里。你需要的时候用Autowired从Map里拿 →对象的创建权从“你手里”反转到了“Spring手里”。控制反转到哪里去了反转到了IoC容器Spring容器。说白了就是一个ConcurrentHashMap里面存着所有Bean的实例。3.2 Bean的生命周期面试常问一个Bean从创建到销毁Spring管了一整套流程实例化通过反射调用构造方法创建对象。属性赋值填充属性Autowired依赖注入在这里发生。初始化执行PostConstruct标注的方法执行InitializingBean.afterPropertiesSet()执行自定义的init-method使用Bean在容器里提供服务。销毁PreDestroy、DisposableBean.destroy()、destroy-method。面试官追问“Autowired是怎么注入的”Spring在Bean实例化后、初始化前会调用AutowiredAnnotationBeanPostProcessor这个后置处理器扫描类里所有带Autowired的字段和方法然后从容器Map里找到对应的Bean通过反射塞进去。3.3 循环依赖Spring的三级缓存这是一个面试深水区但一旦理解了就很简单。场景ServicepublicclassA{AutowiredprivateBb;}ServicepublicclassB{AutowiredprivateAa;}A依赖BB依赖A怎么实例化Spring用三级缓存解决一级缓存singletonObjects存完整的Bean实例已初始化完成。二级缓存earlySingletonObjects存提前暴露的Bean实例化了但还没初始化完。三级缓存singletonFactories存ObjectFactory一个工厂对象用来生成提前暴露的引用。流程用A和B举例创建A实例化A → 把A的ObjectFactory放进三级缓存 → 填充A的属性时发现需要B。创建B实例化B → 把B的ObjectFactory放进三级缓存 → 填充B的属性时发现需要A。从三级缓存拿到A的ObjectFactory → 生成A的提前引用 → 放进二级缓存 → 从三级缓存删除A的工厂。B获取到A的引用二级缓存里拿 → B完成初始化 → 把完整B放进一级缓存。A获取到B的引用一级缓存里拿 → A完成初始化 → 把完整A放进一级缓存。注意三级缓存解决的是“单例模式下setter注入的循环依赖”。构造器注入的循环依赖解决不了因为构造器注入在实例化阶段就需要依赖还没进三级缓存会直接报错。所以推荐用构造器注入 避免循环依赖如果非要有循环依赖用setter注入并加上LazyA的B用Lazy延迟代理。为什么是三级缓存不是二级因为三级缓存存的是ObjectFactory函数式接口它不光能返回原始对象还能返回代理对象比如AOP代理。如果只有二级缓存直接存对象那Spring就失去了在“提前暴露”这个时间点决定“到底返回原始对象还是代理对象”的灵活性。简单说三级缓存是为了支持AOP与循环依赖共存。如果有AOP切面在A上A的代理对象需要在实例化后、初始化前生成三级缓存里的ObjectFactory提供了一个生成代理的机会。如果只有二级缓存直接存对象AOP就没法在循环依赖的场景下工作了。四、AOP面向切面编程动态代理的实战AOP是Spring的第二大基石。你之前理解的“动态代理”就是AOP的底层实现。4.1 AOP是什么AOP就是在不修改业务代码的前提下给方法加上额外的功能。典型场景Transactional方法前后自动开启/提交/回滚事务Cacheable方法执行前先查缓存Log方法执行前后打印日志权限校验方法执行前判断用户有没有权限核心概念面试要能说清概念大白话切面Aspect增强功能的类比如日志切面、事务切面连接点JoinPoint所有可能被增强的方法切入点Pointcut实际要增强的方法匹配规则通知Advice在什么时候执行增强前、后、环绕织入Weaving把增强逻辑和业务逻辑整合到一起的过程4.2 AOP的底层——动态代理AOP的核心就是动态代理。Spring在启动时给目标Bean生成一个代理对象然后把这个代理对象放进IoC容器Map而不是原始对象。两种代理方式① JDK动态代理基于接口目标类必须实现接口。生成一个实现了相同接口的代理类用java.lang.reflect.Proxy。调用方法时代理类通过InvocationHandler把调用转发给目标对象同时插入增强逻辑。// 伪代码Proxy.newProxyInstance(classLoader, interfaces, invocationHandler)UserServiceproxy(UserService)Proxy.newProxyInstance(UserService.class.getClassLoader(),newClass[]{UserService.class},(proxy,method,args)-{System.out.println(前置增强);Objectresultmethod.invoke(target,args);// 调原始对象System.out.println(后置增强);returnresult;});② CGLIB动态代理基于继承目标类不需要实现接口。生成目标类的子类重写所有非final的方法在子类方法里插入增强逻辑。用MethodInterceptor拦截方法调用。Spring Boot 2.x默认用的是CGLIB因为spring.aop.proxy-target-classtrue默认开启它会生成目标类的子类代理。面试官追问“JDK代理和CGLIB代理有什么区别”对比JDK动态代理CGLIB动态代理目标类要求必须实现接口无要求但不能是final类生成方式生成接口的实现类生成目标类的子类性能较慢反射调用较快直接方法调用但生成代理类慢Spring默认如果目标类实现了接口就用JDK现在默认CGLIB为什么现在Spring Boot默认用CGLIB因为用JDK代理时Autowired注入的必须是接口类型如果注入的是实现类UserServiceImpl userService就会报错。而CGLIB代理可以注入实现类因为代理是目标类的子类对开发者更友好。五、Spring事务Transactional的原理Transactional就是AOP的一个经典应用。面试官会追问“Transactional失效的场景有哪些”5.1Transactional失效的经典场景必考① 非public方法Transactional默认只对public方法生效。如果你加在private或protected方法上事务不生效。② 类内部调用自己调自己ServicepublicclassOrderService{publicvoidcreateOrder(){this.updateStock();// 直接调用不走代理事务不生效}TransactionalpublicvoidupdateStock(){// 这个方法不会被代理事务不生效}}解决把updateStock()移到另一个Service里或通过(OrderService)AopContext.currentProxy()获取代理对象再调用。③ 异常被吞了TransactionalpublicvoidcreateOrder(){try{// 业务逻辑}catch(Exceptione){// 只打印日志不往外抛 —— 事务不会回滚}}解决手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()或者直接把异常抛出去。④ rollbackFor没指定Spring事务默认只回滚RuntimeException和Errorchecked异常Exception的子类但不是RuntimeException的子类比如SQLException不回滚。// 这样写SQLException不会回滚TransactionalpublicvoidcreateOrder()throwsSQLException{// ...}// 指定了才会回滚Transactional(rollbackForException.class)publicvoidcreateOrder()throwsException{// ...}⑤ 事务传播级别用错了比如Propagation.REQUIRES_NEW和NESTED的区别没搞对导致事务嵌套表现不符合预期。REQUIRES_NEW是挂起当前事务、新建一个独立事务NESTED是在当前事务里嵌套一个子事务子事务回滚不影响主事务靠Savepoint实现。用错场景会导致数据不一致。5.2 事务传播级别简单记面试官可能让你说出7种传播级别但不需要全背。记三个最常用的传播级别含义REQUIRED默认有事务就加入没有就新建REQUIRES_NEW不管有没有都新建一个独立事务挂起当前事务NESTED有事务就嵌套一个子事务回滚只影响子事务没有就新建六、面试官真正想听什么场景1“Spring Boot自动配置的原理是什么”普通回答“EnableAutoConfiguration注解去加载spring.factories文件。”加分回答“本质上是EnableAutoConfiguration通过Import导入AutoConfigurationImportSelector它读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里列的所有配置类。然后每个配置类通过Conditional条件注解比如ConditionalOnClass、ConditionalOnMissingBean判断是否应该加载。这种‘SPI机制 条件装配’的设计保证了Spring Boot的按需自动配置而且用户可以通过ConditionalOnMissingBean控制优先级自己配的Bean会覆盖默认的。”场景2“循环依赖Spring是怎么解决的”普通回答“三级缓存。”加分回答流畅版“Spring用了三级缓存一级存完整Bean二级存提前暴露的Bean三级存ObjectFactory。A创建时发现依赖B就把A的工厂放进三级缓存去创建BB创建时发现依赖A从三级缓存拿A的工厂生成A的提前引用放进二级缓存然后B完成初始化A再拿到B的引用完成初始化。但注意这只能解决单例模式下的setter注入循环依赖构造器注入的循环依赖解决不了因为实例化阶段就要依赖还没进三级缓存就卡住了。所以在设计上要尽量避免循环依赖或者用Lazy做延迟注入。”场景3“Transactional事务不生效你们遇到过吗”加分回答真实案例“遇到过。同事在一个Service里写了个private方法加Transactional以为是事务结果发现数据没回滚。因为Transactional依赖AOP代理而代理只拦截public方法。还有一个更隐蔽的类内部调用this.method()不经过代理事务也不生效。我们的解决方案是统一规定事务方法必须是public且不要在内部调用或者用Autowired注入自己通过代理对象调。”小结核心概念大白话面试考点Spring Boot帮你配好了Spring约定优于配置自动配置条件满足就自动生效ConditionalAutoConfiguration.importsIoC容器存放Bean的大Map三级缓存解决循环依赖Bean生命周期实例化→赋值→初始化→使用→销毁各个阶段可以插扩展点AOP不改代码加功能动态代理JDK vs CGLIBTransactional用AOP管理事务事务失效的5种场景Starter一堆依赖的打包自动配置自定义Starter的步骤记住一句话Spring Boot不创造新概念它只是把Spring原来手动配的东西自动化了。IoC就是个Map存对象AOP就是动态代理加增强。你把这两个搞明白Spring Boot就不神秘了。下一篇我们讲微服务与分布式——注册中心、负载均衡、熔断限流、网关、分布式事务……把这些系统级的组件全部拆开告诉你它们到底在解决什么问题。下期见。