Spring篇知识点(1)
<p> </p><h1>一、Spring框架的特性</h1>
<ul>
<li> IOC和DI支持:Spring 的核⼼就是⼀个⼤的⼯⼚容器,可以维护所有对象的创建和依赖关系,Spring ⼯⼚⽤于⽣成Bean,并且管理 Bean 的⽣命周期,实现⾼内聚低耦合的设计理念。</li>
<li>AOP编程支持:方便实现对程序进行权限拦截、运行监控等切面功能</li>
<li>声明式事务支持:加@Transactional注解,方法执行时自动开启/提交/回滚事务。</li>
<li>快捷测试支持:Spring 提供了 <strong data-start="835" data-end="867">Spring TestContext Framework</strong>,方便做单元测试/集成测试。可以直接在测试里加载 Spring 容器,注入 Bean。</li>
<li>快速集成功能:指的是 Spring Boot 提供的一套 Starter 机制。通过一个依赖 + 配置文件,就能自动接入常见的第三方框架,省去大量手写配置。</li>
<li>复杂API模板封装:链式</li>
</ul>
<h1>二、Spring框架由哪些部分组成</h1>
<ul>
<li> Spring Container(核心容器)
<ul>
<li><strong data-start="145" data-end="160">Spring Core</strong>:提供 IoC、依赖注入(DI)的底层功能。</li>
<li><strong data-start="187" data-end="203">Spring Beans</strong>:BeanFactory、ApplicationContext,负责 Bean 的生命周期和管理。</li>
<li><strong data-start="257" data-end="275">Spring Context</strong>:更高级的 IoC 容器,支持国际化、事件传播、资源加载</li>
<li><strong data-start="309" data-end="346">Spring Expression Language (SpEL)</strong>:支持在配置文件和注解里写表达式,比如 <code data-start="366" data-end="379">#{user.age}</code>、<code data-start="380" data-end="391">${db.url}</code>。</li>
</ul>
</li>
<li>AOP:@Transactional、AOP 日志切面。</li>
<li>Data Access/Integration:<code data-start="632" data-end="646">JdbcTemplate</code> 简化原始 JDBC API。整合 Hibernate、MyBatis、JPA 等。提供声明式事务和编程式事务。</li>
<li>Spring Web:Spring MVC,最常用的 Web 框架,基于 Servlet,支持 RESTful API。</li>
<li>Test:
<ul>
<li data-start="1033" data-end="1058">
<p data-start="1035" data-end="1058">提供对 JUnit、TestNG 的集成。</p>
</li>
<li data-start="1059" data-end="1111">
<p data-start="1061" data-end="1111">提供 Spring TestContext Framework,可以在测试中直接注入 Bean。</p>
</li>
<li data-start="1112" data-end="1139">
<p data-start="1114" data-end="1139">支持 MockMvc 模拟 Web 请求测试。</p>
</li>
</ul>
</li>
</ul>
<h1>三、Spring中常用注解</h1>
<h2>1. Web</h2>
<p>① @ Controller</p>
<p> @RestController = @Controller + @ResponseBody</p>
<p> Java里的返回值 = 逻辑视图名,如“home”,再交给模板引擎渲染成完整的HTML页面(如 <code data-start="329" data-end="350">templates/home.html</code>),最后返回给浏览器。</p>
<p> 若你用@Controller,加了@ResponseBody,返回值就直接写到响应体,不会再走试图解析器,跟@RestController 的作用一样。</p>
<p>② RequestMapping</p>
<p> 这是请求映射注解,用来把HTTP请求映射到控制器方法或类。包括类级别和方法级别。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@Controller<br>// 类级别
@RequestMapping("/users")
public class UserController {
// 方法级别
@RequestMapping("/list")
public String listUsers() {
return "userList"; // 返回页面
}
}<br>// 请求<code data-start="451" data-end="464">/users/list</code> 会映射到 <code data-start="470" data-end="483">listUsers()</code>。<br data-start="484" data-end="487">// 类上的 <code data-start="491" data-end="508">@RequestMapping</code> 作为 <strong data-start="512" data-end="518">前缀</strong>,方法上的作为 <strong data-start="526" data-end="532">后缀</strong>。</pre>
</div>
<p> 1) 指定请求方法:</p>
<p><img src="https://img2024.cnblogs.com/blog/2297173/202509/2297173-20250912104654933-1938828056.png"></p>
<p> 2) 指定参数条件</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">// 只有带 ?role=admin 的请求才会匹配。
@RequestMapping(value="/search", params="role=admin")
public String searchAdmins() { ... }
</pre>
</div>
<p> 3) 指定请求头</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@RequestMapping(value="/json", headers="Content-Type=application/json")
public String handleJson() { ... }</pre>
</div>
<p>③ ReponseBody</p>
<p> 把方法返回值直接写到 HTTP 响应体里(JSON、字符串等),而不是解析为视图。</p>
<p>④ @RequestBody</p>
<p> 把 <strong data-start="1906" data-end="1922">请求体 JSON/XML</strong> 自动反序列化为 Java 对象。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">// 请求
{
"name": "Alice",
"password": "123456"
}
@PostMapping("/login")
public String login(@RequestBody User user) {
return "用户 " + user.getName() + " 登录成功";
}</pre>
</div>
<p>⑤ @PathVariable</p>
<p> 把 <strong data-start="2197" data-end="2209">URL 路径参数</strong>绑定到方法参数。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@GetMapping("/users/{id}")
public String getUserById(@PathVariable("id") int userId) {
return "查询用户ID: " + userId;
}
</pre>
</div>
<h2>2、容器</h2>
<p>① Component</p>
<p> <strong>通用的</strong>组件标识,告诉Spring这是一个需要被容器管理的类。Spring启动时会扫描并注册到容器里。(自己写的业务类)</p>
<p>② Service</p>
<p> 业务逻辑层组件,语义化的@Component,功能和它一样,区别在于职责标识。</p>
<p>③ Repository</p>
<p> 数据访问层组件(DAO),通常操作数据库。它会额外提供 <strong data-start="625" data-end="637">数据访问异常转换</strong> 功能。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@Repository
public class UserDao {
public void save(User user) {
// JDBC 操作
throw new SQLException("主键重复");
}
}
</pre>
</div>
<p> Spring 会拦截,把 <code data-start="1330" data-end="1344">SQLException</code> 转换为 <code data-start="1349" data-end="1372">DuplicateKeyException</code>(继承自 <code data-start="1377" data-end="1398">DataAccessException</code>)。<br data-start="1400" data-end="1403"> 你在上层 service 里就统一 catch <code data-start="1430" data-end="1451">DataAccessException</code> 就行了。</p>
<p>④ Autowired</p>
<p> 自动注入依赖(按照类型注入)。</p>
<p>⑤ Qualifier</p>
<p> 当一个接口有多个实现类时,用来指定具体注入哪一个。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">public interface Payment {
void pay();
}
@Component("aliPay")
class AliPay implements Payment {
public void pay() { System.out.println("支付宝支付"); }
}
@Component("wechatPay")
class WechatPay implements Payment {
public void pay() { System.out.println("微信支付"); }
}
@Service
class PayService {
@Autowired
@Qualifier("aliPay")// 指定用支付宝
private Payment payment;
public void doPay() {
payment.pay();
}
}</pre>
</div>
<p>⑥ Configuration</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;"><bean id="appName" class="java.lang.String">
<constructor-arg value="MySpringApp"/>
</bean>
@Component
public class AppConfig {
@Bean
public String appName() {
return "MySpringApp";
}
}</pre>
</div>
<p> 若是第三方类,不是你自己的写的,需要往容器里加。如果用@Component也能运行,但Spring不会做CGLIB代理,每次调用appName()都会执行一次方法,可能得到多个不同的实例。但@Configuration会被CGLIB代码,保证@Bean方法返回的都是单例Bean。——如果很多第三方类(数据库连接池、消息队列客户端等)都很重,如果每次都重新new,会造成资源浪费、状态丢失(有些对象需要维护上下文、缓存)。所以Spting希望通过容器来统一管理这些第三方对象,保证它们是单例且可控。</p>
<p>⑦ Value</p>
<p> 注入外部配置(application.properties / yml)里的值。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@Service
public class ConfigService {
@Value("${server.port}")
private int port;
public void printPort() {
System.out.println("服务端口: " + port);
}
}</pre>
</div>
<p>⑧ Bean</p>
<p> 告诉 Spring 容器要手动注册一个 bean(方法的返回值会交给 Spring 管理)。</p>
<p>⑨ Scope</p>
<p data-start="2373" data-end="2398"><strong data-start="2373" data-end="2379">作用</strong>:定义 Bean 的作用域。常见的有:</p>
<ul data-start="2399" data-end="2504">
<li data-start="2399" data-end="2421">
<p data-start="2401" data-end="2421"><code data-start="2401" data-end="2412">singleton</code>(默认,单例)</p>
</li>
<li data-start="2422" data-end="2450">
<p data-start="2424" data-end="2450"><code data-start="2424" data-end="2435">prototype</code>(每次 new 一个新的)</p>
</li>
<li data-start="2451" data-end="2480">
<p data-start="2453" data-end="2480"><code data-start="2453" data-end="2462">request</code>(一次 HTTP 请求一个实例)</p>
</li>
<li data-start="2481" data-end="2504">
<p data-start="2483" data-end="2504"><code data-start="2483" data-end="2492">session</code>(一次会话一个实例)</p>
</li>
</ul>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@Component
@Scope("prototype")
public class PrototypeBean {
public PrototypeBean() {
System.out.println("新建 PrototypeBean 实例");
}
}
</pre>
</div>
<h2>3、AOP</h2>
<p>(1) @Aspect:声明是一个切面类,切面里定义的就是要织入的横切逻辑(入日志、事务、权限等)</p>
<p>① PointCut:定义一个切点,也就是拦截规则。<span style="text-decoration: underline">execution(访问修饰符 返回类型 包名.类名.方法名(参数))</span></p>
<p>② @After:在目标方法执行 <strong data-start="805" data-end="811">之后</strong> 执行(无论是否抛异常)。</p>
<p>③ @Before:在目标方法执行 <strong data-start="588" data-end="594">之前</strong> 执行。</p>
<p>④ @Around:<strong data-start="1022" data-end="1030">环绕增强</strong>,可以在方法前后都插入逻辑,还能决定方法是否执行。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@Aspect
@Component
public class LogAspect {
// 定义切点:匹配 service 包下所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("前置日志: " + joinPoint.getSignature().getName());
}
@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("后置日志: " + joinPoint.getSignature().getName());
}
@Around("serviceMethods()")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前: " + pjp.getSignature().getName());
Object result = pjp.proceed();
System.out.println("环绕后: " + pjp.getSignature().getName());
return result;
}
}
</pre>
</div>
<h2>4、事务</h2>
<p>@Transactinal</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">// 1.基本用法<br>@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(String name) {
userRepository.save(new User(name));
// 模拟异常<br> //如果 <code data-start="687" data-end="703">name = "error"</code>,抛异常,事务会回滚,用户不会被插入数据库。
if ("error".equals(name)) {
throw new RuntimeException("模拟异常");
}
}
}</pre>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">//1.作用在类上
@Service
@Transactional
public class OrderService {
public void createOrder() {
// 所有 public 方法自动开启事务
}
}
//2. 指定异常回滚
@Transactional(rollbackFor = Exception.class)
public void updateData() {
// 即使抛 IOException 也会回滚
}
@Transactional(noRollbackFor = ArithmeticException.class)
public void calc() {
int x = 1 / 0; // 抛异常,但不回滚
}
//3.事务的隔离级别
//READ_COMMITTED——读可提交
// REPEATABLE_READ——可重复读(MySQL 默认)
// SERIALIZABLE——串行化
@Transactional(isolation = Isolation.SERIALIZABLE)
public void serializableTx() {
// 严格的事务隔离
}
</pre>
</div>
<blockquote>
<p>事务并发可能出现的问题:</p>
<ul>
<li>脏读:事务A读到了事务B还没提交的数据</li>
<li>不可重复读:事务A在两次查询中,事务B修改了数据,导致结果不一致</li>
<li>幻读:事务A查询某个范围的数据,事务B插入了新数据,事务A再查时发现多了行</li>
</ul>
<p>SQL标准的四种隔离级别:</p>
<ul>
<li><strong data-start="412" data-end="432">READ UNCOMMITTED</strong>(读未提交):事务可以读到其他事务未提交的数据。会导致脏读、不可重复读、幻读</li>
<li><strong data-start="525" data-end="543">READ COMMITTED</strong>(读已提交):只能读到已提交的数据。解决了脏读,可能出现后面两个。这是Oracle默认隔离级别</li>
<li><strong data-start="647" data-end="666">EPEATABLE READ</strong>(可重复读):在同一事务内,多次读取同一行结果一致。解决了前两个,可能出现幻读。(mysql innoDB的默认隔离级别)</li>
<li><strong data-start="789" data-end="805">SERIALIZABLE</strong>(串行化):所有事务串行执行,像排队一样。解决了三个。但性能最差。 (对数据一致性要求极高的情况,如银行转账)</li>
</ul>
</blockquote>
<h1>四、Spring中用了哪些设计模式?</h1>
<p><span style="font-size: 16px"><strong>1. 工厂模式</strong></span></p>
<p> 传统方式是自己new对象,但在Spring是一个大工厂,用来生产和管理对象Bean。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">//手动写代码拿bean
public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
OrderService orderService = ctx.getBean(OrderService.class);
orderService.placeOrder();
}
}
//自动(推荐),容器在启动时完成依赖注入,相当于Spring自动帮你调用了getBean()
@Service
public class OrderService {
@Autowired
private UserService userService;
public void placeOrder() {
userService.sayHello();
}
}</pre>
</div>
<p><span style="font-size: 16px"><strong>2. 代理模式</strong></span></p>
<p> Spring AOP的核心是动态代理(JDK Proxy或CGLIB)。若没有代理的话,一个方法没有事务控制,也没有额外逻辑。引入代理的思想,<strong><span style="text-decoration: underline">就是在不改变目标类的前提下,为它“加功能”</span></strong>。如下所示,OrderService是目标类,Spring在启动时会生成一个代理类。于是,你注入的其实是代理对象,而不是自己new 的OrderService。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@Service
public class OrderService {
@Transactional
public void createOrder() {
System.out.println("创建订单");
}
}
// 伪代码演示:
class OrderServiceProxy extends OrderService {
@Override
public void createOrder() {
// 事务开始
try {
super.createOrder(); // 调用目标方法
// 提交事务
} catch (Exception e) {
// 回滚事务
}
}
}<br>// 执行时的实际流程:<br>// 1. 调用的是代理对象的createOrder()<br>// 2. 代理对象会:开启事务-->执行真正的createOrder()-->成功则提交事务-->失败则回滚事务</pre>
</div>
<blockquote>
<p>JDK Proxy vs CGLIB,Spring使用这两种动态代理机制:</p>
<p>1、JDK动态代理:JDK自带的Proxy,只能给<strong>接口</strong>生成代理类。代理类实现了统一接口,然后把调用转发给目标对象。</p>
<p>// 接口<br>public interface UserService {<br> void sayHello();<br>}</p>
<p>// 实现类<br>public class UserServiceImpl implements UserService {<br> public void sayHello() {<br> System.out.println("Hello User");<br> }<br>}</p>
<p>2、CGLIB动态代理:用字节码技术,直接生成目标类的<strong>子类</strong>,然后覆盖方法。</p>
<p>class UserService$$Proxy extends UserService { // 继承目标类<br> @Override<br> public void sayHello() {<br> // 代理逻辑<br> System.out.println("事务开始");<br> super.sayHello(); // 调用父类方法<br> System.out.println("事务提交");<br> }<br>}</p>
<p>3、 Spring为什么要区分</p>
<ul data-start="1269" data-end="1366">
<li data-start="1269" data-end="1320">
<p data-start="1271" data-end="1320">如果目标类有接口,Spring 默认走 <strong data-start="1291" data-end="1304">JDK Proxy</strong>,因为更轻量、JDK 自带。</p>
</li>
<li data-start="1321" data-end="1366">
<p data-start="1323" data-end="1366">如果目标类没有接口,Spring 自动退回到 <strong data-start="1346" data-end="1355">CGLIB</strong>,保证依然能代理。</p>
</li>
</ul>
</blockquote>
<p><span style="font-size: 16px"><strong>3. 单例模式</strong></span></p>
<p> Spring Bena默认作用域是单例,容器中一个Bean只有一个实例。也就是如果反复注入一个类,得到的是一个对象。</p>
<p><span style="font-size: 16px"><strong>4. 模板模式</strong></span></p>
<p> Spring提供了很多模板类,比如 <strong data-start="1062" data-end="1091">JdbcTemplate、RestTemplate</strong>,把公共流程固定下来,<code data-start="1331" data-end="1340">query()</code> 固定了 <strong data-start="1345" data-end="1369">获取连接 → 执行 SQL → 释放资源</strong> 的流程,具体怎么封装结果集由你提供。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
List<User> users = jdbcTemplate.query("SELECT * FROM user",
(rs, rowNum) -> new User(rs.getInt("id"), rs.getString("name")));</pre>
</div>
<p><span style="font-size: 16px">5. <strong>观察者模式</strong></span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">// 自定义事件
public class UserRegisterEvent extends ApplicationEvent {<br> // 说明这是一个可以被Spring容器识别、发布和监听的事件类型
public UserRegisterEvent(Object source) { super(source); }
}
// 事件发布
@Component
public class UserService {
@Autowired
private ApplicationEventPublisher publisher;
public void registerUser() {
System.out.println("用户注册");
publisher.publishEvent(new UserRegisterEvent(this));
}
}
// 事件监听
@Component
public class EmailListener {
@EventListener
public void onUserRegister(UserRegisterEvent event) {
System.out.println("发送注册成功邮件");
}
}
</pre>
</div>
<p> 这里会先自定义事件类,继承自ApplicationEvent。然后用户服务类注入ApplicationEventPublisher(这是Spring提供的事件发布器),当执行了registerUser方法时,会调用<code data-start="380" data-end="433">publisher.publishEvent(new UserRegisterEvent(this))</code>,发布一个用户注册事件。<code data-start="477" data-end="504">ApplicationEventPublisher</code> 接收到事件后,会把事件分发给所有感兴趣的监听器。当外部如果userService.registerUser()时,Spring会自动调用这个监听的方法。</p>
<p><span style="font-size: 16px"><strong>6. 适配器模式</strong></span></p>
<p> 适配器模式是把一个接口转换成另一个接口,让原本不兼容的类可以一起工作(类比插头转换器)。如果在Spring MVC 场景里,DispatcherServlet收到请求后会找到对应的 Controller,执行 Controller,返回结果。但Controller有很多种写法,如常见的 <span style="font-family: 黑体, "Heiti SC""><code data-start="411" data-end="424">@Controller</code> + <code data-start="427" data-end="444">@RequestMapping、实现了<code data-start="453" data-end="473">HttpRequestHandler</code> 接口的 Controller或更老式的Controller。这时就引入一个HandlerAdapter。</code></span></p>
<ul>
<li data-start="674" data-end="712">
<p data-start="676" data-end="712">每种 Controller 对应一个 HandlerAdapter。</p>
</li>
<li data-start="713" data-end="780">
<p data-start="715" data-end="780">DispatcherServlet <strong data-start="733" data-end="753">不直接调用 Controller</strong>,而是通过 HandlerAdapter 来执行。</p>
</li>
<li data-start="781" data-end="838">
<p data-start="783" data-end="838">这样 DispatcherServlet 就只管“调适配器”,不用关心 Controller 的具体类型。</p>
</li>
</ul>
<p> </p>
<p><strong><span style="font-size: 16px">7. 策略模式</span></strong></p>
<p> Spring中有一个Resource接口,它的不同实现类,会根据不同的策略去访问资源。</p>
<hr>
<h1>IOC</h1>
<h2>5、说一说什么是IOC?什么是DI?</h2>
<p> Java是一个面向对象的语言,我们在代码里就是创建对象和对象的依赖。IOC是控制反转的思想,就是由容器来负责控制对象的生命周期和对象间的关系。引入IOC之后控制对象生命周期的不再是引用它的对象,而是容器。</p>
<p> DI(依赖注入),指的是容器在实例化对象的时候把它依赖的类注入给它。所以我理解的是IOC是思想,DI是实现。</p>
<p> 那使用IOC的最主要的目的就是为了让对象间不再过度耦合,写代码的时候可以专注于业务,而不是复杂的对象的生命周期的管理和依赖。</p>
<h2>6、简单说一下Spring IOC 的实现机制</h2>
<p> Spring启动时会读取配置文件/注解,生成一堆BeanDefinition 对象,可能包含beanName、beanClass、scope、lazy-init(懒加载,意思是容器启动的时候先不实例化,等到要用的时候再实例化?),这个BeanDefinition 本身 <strong data-start="707" data-end="728">不是 UserService 实例</strong>,而是 UserService 的“说明书”。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@Component
public class UserService { }
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// 容器启动时,UserService 单例 Bean 已经被 getBean() 过了
}
</pre>
</div>
<p> 如果是设置了@Lazy或prototype作用域,容器启动时不会创建,只有在你第一次显式调用getBean时才会触发创建。在业务代码里,一般会通过@Autowired(后台自动调用getBean())来注入对象,即若Spring在实例化一个Bean时,若它依赖了其他Bean,容器会自动调用getBean(“userDao”)来拿到依赖的对象。</p>
<p> 若是第一次getBean,先查singletonObjects,若没有的话会doCreateBean()进行实例化对象,然后通过反射填充属性,放入singletonObjects。若是第二次getBean,就直接从singletonObjects HashMap取对象。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@Service
public class <strong>UserService</strong> {
@Autowired
private UserDao userDao;<br> private String name;</pre>
<pre class="brush:java;gutter:true;"> public void <strong>doSomething</strong>() {
userDao.query();
}
}
@Repository
public class UserDao {
public void query() {
System.out.println("UserDao 查询数据库...");
}
}<br>//从上面可以看出,UserService依赖UserDao,<br>//那么后台是怎么通过@Autowired注解就把这个类注入进来的呢?</pre>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">// ====================== 容器启动 ======================
// 1. 扫描到 UserService、UserDao
BeanDefinition userServiceDef = new BeanDefinition(UserService.class);
BeanDefinition userDaoDef = new BeanDefinition(UserDao.class);
// 放到 beanDefinitionMap
beanDefinitionMap.put("userService", userServiceDef);
beanDefinitionMap.put("userDao", userDaoDef);
// ====================== 第一次 getBean(UserService) ======================
public Object <strong>getBean</strong>(String name) {
// 2. 查缓存 → 没有
Object bean = singletonObjects.get(name);
if (bean != null) return bean;
// 3. 没有 → 创建 Bean
return doCreateBean(beanDefinitionMap.get(name));
}
private Object <strong>doCreateBean</strong>(BeanDefinition def) {
// 4. 用反射创建对象
Object bean = def.getBeanClass().newInstance();
// 相当于 new UserService()
// 5. 填充属性(依赖注入)<br> // 取出UserService中的字段对象:userDao和name
for (Field field : def.getBeanClass().getDeclaredFields()) {<br> // 只对有autowired注解的字段处理
if (field.isAnnotationPresent(<strong>Autowired</strong>.class)) {
// 找到依赖 UserDao
Object dependency = getBean("userDao"); // 递归调用 getBean
// 暴力破解Java的访问检查(private),让反射能操作私有字段
field.setAccessible(true);<br> // 相当于userService.userDao = userDao;
field.set(bean, dependency);
}
}
// 6. 放进单例池
singletonObjects.put(def.getBeanName(), bean);
return bean;
}
//最终效果:业务代码
userService.doSomething();
//实际运行
// userService 内部的 userDao 已经被 Spring 注入好了
userService.userDao.query();
//输出
UserDao 查询数据库...</pre>
</div>
<h2>7、说说BeanFactory和ApplicantContext</h2>
<p> BeanFacotory时Spring最基础的 IOC 容器,负责创建和管理Bean。它的做法跟上面讲的做法是一样的。特点是启动时只保存Bean的定义信息,只有当第一次getBean的时候,采用反射去创建对象,创建好后放到单例池,下次再用就直接拿。</p>
<p> ApplicantContext是BeanFactory的子接口,功能更完整,是企业级容器。在启动时就会把所有单例Bean创建好(除非标了@Lazy),除了IOC,还提供很多高级功能,如国际化、事件机制,与AOP、事务等框架集成。</p>
<h2>8、你知道Spring容器启动阶段会干什么吗</h2>
<p> ① 加载配置</p>
<p> 可能是XML、注解等,Spring会读取这些配置,把Bean的信息(类名、作用域、以来等)保存成BeanDefinition。放在一个beanDefinitionMap里。</p>
<p> ② 注册beanDefinition</p>
<p> 把所有解析到的beanDefinition放到容器的注册表(DefaultListableBeanFactory)里,这一步只保存“描述信息”,还没真正创建对象。</p>
<p> ③ BeanFactoryPostProcessor执行</p>
<p> 在实例化 Bean 之前,Spring 会先执行 <strong data-start="520" data-end="548">BeanFactoryPostProcessor</strong>,允许修改 BeanDefinition。比如替换属性值、动态修改配置。(如<property name="url" value="${db.url}" />)</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;"><bean id="userService" class="com.example.UserService">
<property name="name" value="默认名字"/>
</bean>
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 1. 拿到 BeanDefinition
BeanDefinition bd = beanFactory.getBeanDefinition("userService");
// 2. 修改属性值(替换原来 XML 里的 "默认名字")
bd.getPropertyValues().add("name", "张三");
// 3. 也可以改 scope(默认 singleton)
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
// 4. 甚至可以换掉实现类
// bd.setBeanClassName("com.example.AnotherUserService");
}
}
</pre>
</div>
<p> ④ BeanPostProcessor注册</p>
<ul>
<li data-start="716" data-end="749">
<p data-start="718" data-end="749"><strong data-start="718" data-end="738">实例化(newInstance)</strong> → 得到一个空对象。</p>
</li>
<li data-start="753" data-end="807">
<p data-start="755" data-end="807"><strong data-start="755" data-end="777">依赖注入(populateBean)</strong> → 给对象的字段/属性赋值,比如注入 <code data-start="797" data-end="806">UserDao</code>。</p>
</li>
<li data-start="811" data-end="870">
<p data-start="813" data-end="870"><strong data-start="813" data-end="860">调用 BeanPostProcessor.beforeInitialization()</strong> → 初始化前处理。</p>
</li>
<li data-start="874" data-end="930">
<p data-start="876" data-end="930"><strong data-start="876" data-end="887">执行初始化方法</strong> → <code data-start="890" data-end="906">@PostConstruct</code>、<code data-start="907" data-end="929">afterPropertiesSet()</code>。</p>
</li>
<li data-start="934" data-end="1003">
<p data-start="936" data-end="1003"><strong data-start="936" data-end="982">调用 BeanPostProcessor.afterInitialization()</strong> → 初始化后处理(比如 AOP 代理)。</p>
</li>
</ul>
<p> ⑤ 实例化单例Bean(非懒加载的)</p>
<p> ApplicationContext 默认会在启动时创建所有单例 Bean。</p>
<p> ⑥ 发布刷新完成事件:</p>
<ul>
<li data-start="1118" data-end="1172">
<p data-start="1120" data-end="1172">当所有 Bean 都准备好,Spring 会发一个 <code data-start="1146" data-end="1169">ContextRefreshedEvent</code>。</p>
</li>
<li data-start="1173" data-end="1194">
<p data-start="1175" data-end="1194">这时候你写的监听器就可以收到通知。</p>
</li>
</ul>
<h2>9、说一下Spring Bean的生命周期</h2>
<p> 基本容器BeanFacotory和扩展容器ApplicationContext的实例化实际不一样。前者是延迟初始化的方式,只有在第一次getBean的时候,才会实例化Bean。后者启动后会实例化所有的Bean定义。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">UserService userService = context.getBean(UserService.class);
</pre>
</div>
<p> 在你能用到 <code data-start="1361" data-end="1374">userService</code> 之前,Spring 已经帮它走完了以下所有步骤。</p>
<ol>
<li>容器启动,加载配置</li>
<li>在Bean实例化之前,对BeanDefinition做修改</li>
<li>实例化:Spring通过反射调用构造方法创建Bean对象,创建出Bean空壳对象</li>
<li>属性赋值:Spring根据BeanDefinition里的PropetyValues,把依赖注入到对象里。</li>
<li>初始化:1)调用Aware接口(容器把自己的信息告诉Bean);2)BeanPostProcessor前置处理,注入依赖;3)执行初始化方法(由开发者定义);4)BeanPostProcessor后置处理(常用于AOP),把Bean包装成代理对象返回。</li>
<li>使用中:context.getBean("xxx")得到的就是最终的Bean(可能是代理对象)</li>
<li>销毁:调用 <code data-start="1425" data-end="1451">DisposableBean.destroy()、</code>调用配置的 <code data-start="1463" data-end="1479">destroy-method</code>。</li>
</ol>
<h2>10、Bean定义和依赖定义有哪些方式</h2>
<p> ① 直接编码(最底层)</p>
<p> 平常很少写,但Spring内部就是用这种API来实现的。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
RootBeanDefinition beanDefinition = new RootBeanDefinition(UserService.class);
beanDefinition.getPropertyValues().add("name", "张三");
// 注册 BeanDefinition
factory.registerBeanDefinition("userService", beanDefinition);
// 获取 Bean
UserService userService = (UserService) factory.getBean("userService");
</pre>
</div>
<p> ② 配置文件方式</p>
<p> 传统方式,主要是XML配置或propterties文件。Spring 启动时会解析配置文件,转成 BeanDefinition。</p>
<p> ③ 注解方式(最常用)</p>
<p> 在类和字段上加注解,Spring 会扫描并注册到容器里。</p>
<p> </p>
<h1>参考:</h1>
<p> 沉默王二公众号</p><br><br>
来源:https://www.cnblogs.com/xiaoqian01/p/19083396
頁:
[1]