文章目录
- 课件&资料
- Spring启示录
- OCP开闭原则
- 依赖倒置原则(DIP原则)
- 控制反转
- Spring框架
- 依赖注入
- Spring概述
- Spring简介
- Spring8大模块
- Spring特点
- Spring的入门程序
- Spring5的下载
- Spring6的下载
- Spring的jar文件
- 第一个Spring程序
- 第一个Spring程序剖析
- Spring6启用Log4j2日志框架
- Spring对IoC的实现
- IoC 控制反转
- 依赖注入
- set注入
- 构造注入
- set注入专题
- 注入外部Bean
- 注入内部Bean
- 注入简单类型
- 级联属性赋值
- 注入数组
- 注入List集合与Set集合
- 注入Map集合与注入Properties
- 注入null和空字符串
- 注入的值中含有特殊符号
- p命名空间注入
- c命名空间注入
- util命名空间
- 基于XML的自动装配
- 根据名称自动装配
- 根据类型自动装配
- Spring引入外部属性配置文件
- Bean的作用域
- GoF之工厂模式
- 工厂模式的三种形态
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式(了解)
- Bean的实例化方式
- 通过构造方法实例化
- 通过简单工厂模式实例化
- 通过factory-bean实例化
- 通过FactoryBean接口实例化
- BeanFactory和FactoryBean的区别
- 注入自定义Date
- Bean的生命周期
- 什么是Bean的生命周期
- 为什么要知道Bean的生命周期
- Bean的生命周期之5步
- Bean生命周期之7步
- Bean生命周期之10步
- Bean的作用域不同,管理方式不同
- 自己new的对象如何让Spring管理
- Bean的循环依赖问题
- 什么是Bean的循环依赖
- singleton下的set注入产生的循环依赖
- prototype下的set注入产生的循环依赖
- singleton下的构造注入产生的循环依赖
- Spring解决循环依赖的机理
老杜Spring6课件:https://www.yuque.com/docs/share/866abad4-7106-45e7-afcd-245a733b073f?# 《Spring6》
密码:mg9b
资料:https://pan.baidu.com/s/1HsmuLXtGLxifwNGr4vu1Rw?pwd=1234
提取码:1234
注意术语:
OCP:开闭原则(开发原则)
DIP:依赖倒置原则(开发原则)
IoC:控制反转(一种思想,一种新型的设计模式)
DI:依赖注入(控制反转思想的具体实现方式)
注意:Spring5版本之后是8个模块。在Spring5中新增了WebFlux模块。
Spring Core模块
这是Spring框架最基础的部分,它提供了依赖注入(DependencyInjection)特征来实现容器对Bean的管理。核心容器的主要组件是 BeanFactory,BeanFactory是工厂模式的一个实现,是任何Spring应用的核心。它使用IoC将应用配置和依赖从实际的应用代码中分离出来。
Spring Context模块
如果说核心模块中的BeanFactory使Spring成为容器的话,那么上下文模块就是Spring成为框架的原因。
这个模块扩展了BeanFactory,增加了对国际化(I18N)消息、事件传播、验证的支持。另外提供了许多企业服务,例如电子邮件、JNDI访问、EJB集成、远程以及时序调度(scheduling)服务。也包括了对模版框架例如Velocity和FreeMarker集成的支持
Spring AOP模块
Spring在它的AOP模块中提供了对面向切面编程的丰富支持,Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中,可以自定义拦截器、切点、日志等操作。
Spring DAO模块
提供了一个JDBC的抽象层和异常层次结构,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析,用于简化JDBC。
Spring ORM模块
Spring提供了ORM模块。Spring并不试图实现它自己的ORM解决方案,而是为几种流行的ORM框架提供了集成方案,包括Hibernate、JDO和iBATIS SQL映射,这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring Web MVC模块
Spring为构建Web应用提供了一个功能全面的MVC框架。虽然Spring可以很容易地与其它MVC框架集成,例如Struts,但Spring的MVC框架使用IoC对控制逻辑和业务对象提供了完全的分离。
Spring WebFlux模块
Spring Framework 中包含的原始 Web 框架 Spring Web MVC 是专门为 Servlet API 和 Servlet 容器构建的。反应式堆栈 Web 框架 Spring WebFlux 是在 5.0 版的后期添加的。它是完全非阻塞的,支持反应式流(Reactive Stream)背压,并在Netty,Undertow和Servlet 3.1+容器等服务器上运行。
Spring Web模块
Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文,提供了Spring和其它Web框架的集成,比如Struts、WebWork。还提供了一些面向服务支持,例如:实现文件上传的multipart请求。
Spring6要求JDK最低版本是Java17
官网地址:【https://spring.io/】
官网地址(中文):【http://spring.p2hp.com/】
【https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Artifacts】
【https://repo.spring.io/artifactory/release/org/springframework/spring/5.3.9/】
官网地址:【https://spring.io/】
官网地址(中文):【http://spring.p2hp.com/】
【https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Artifacts】
将下图配置写入pom文件中
打开libs目录,会看到很多jar包:
我们来看一下spring框架都有哪些jar包:
JAR文件 | 描述 |
---|---|
spring-aop-5.3.9.jar | 这个jar 文件包含在应用中使用Spring 的AOP 特性时所需的类 |
spring-aspects-5.3.9.jar | 提供对AspectJ的支持,以便可以方便的将面向切面的功能集成进IDE中 |
spring-beans-5.3.9.jar | 这个jar 文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean 以及进行Inversion ofControl / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI 支持,引入spring-core.jar 及spring-beans.jar 文件就可以了。 |
spring-context-5.3.9.jar | 这个jar 文件为Spring 核心提供了大量扩展。可以找到使用Spring ApplicationContext特性时所需的全部类,JDNI 所需的全部类,instrumentation组件以及校验Validation 方面的相关类。 |
spring-context-indexer-5.3.9.jar | 虽然类路径扫描非常快,但是Spring内部存在大量的类,添加此依赖,可以通过在编译时创建候选对象的静态列表来提高大型应用程序的启动性能。 |
spring-context-support-5.3.9.jar | 用来提供Spring上下文的一些扩展模块,例如实现邮件服务、视图解析、缓存、定时任务调度等 |
spring-core-5.3.9.jar | Spring 框架基本的核心工具类。Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类。 |
spring-expression-5.3.9.jar | Spring表达式语言。 |
spring-instrument-5.3.9.jar | Spring3.0对服务器的代理接口。 |
spring-jcl-5.3.9.jar | Spring的日志模块。JCL,全称为"Jakarta Commons Logging",也可称为"Apache Commons Logging"。 |
spring-jdbc-5.3.9.jar | Spring对JDBC的支持。 |
spring-jms-5.3.9.jar | 这个jar包提供了对JMS 1.0.2/1.1的支持类。JMS是Java消息服务。属于JavaEE规范之一。 |
spring-messaging-5.3.9.jar | 为集成messaging api和消息协议提供支持 |
spring-orm-5.3.9.jar | Spring集成ORM框架的支持,比如集成hibernate,mybatis等。 |
spring-oxm-5.3.9.jar | 为主流O/X Mapping组件提供了统一层抽象和封装,OXM是Object Xml Mapping。对象和XML之间的相互转换。 |
spring-r2dbc-5.3.9.jar | Reactive Relational Database Connectivity (关系型数据库的响应式连接) 的缩写。这个jar文件是Spring对r2dbc的支持。 |
spring-test-5.3.9.jar | 对Junit等测试框架的简单封装。 |
spring-tx-5.3.9.jar | 为JDBC、Hibernate、JDO、JPA、Beans等提供的一致的声明式和编程式事务管理支持。 |
spring-web-5.3.9.jar | Spring集成MVC框架的支持,比如集成Struts等。 |
spring-webflux-5.3.9.jar | WebFlux是 Spring5 添加的新模块,用于 web 的开发,功能和 SpringMVC 类似的,Webflux 使用当前一种比较流程响应式编程出现的框架。 |
spring-webmvc-5.3.9.jar | SpringMVC框架的类库 |
spring-websocket-5.3.9.jar | Spring集成WebSocket框架时使用 |
注意:
如果你只是想用Spring的IoC功能,仅需要引入:spring-context即可。将这个jar包添加到classpath当中。
如果采用maven只需要引入context的依赖即可。
<repositories>
<repository>
<id>repository.spring.milestoneid>
<name>Spring Milestone Repositoryname>
<url>https://repo.spring.io/milestoneurl>
repository>
repositories>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>6.0.0-M2version>
dependency>
dependencies>
<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.0modelVersion>
<groupId>cw.springgroupId>
<artifactId>spring-002artifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
<repositories>
<repository>
<id>repository.spring.milestoneid>
<name>Spring Milestone Repositoryname>
<url>https://repo.spring.io/milestoneurl>
repository>
repositories>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>6.0.0-M2version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
dependencies>
<properties>
<maven.compiler.source>17maven.compiler.source>
<maven.compiler.target>17maven.compiler.target>
properties>
project>
spring框架中管理的每个对象都叫bean
定义bean:User
package cw.spring.bean;
public class User {
}
编写spring的配置文件,该文件放在类的根路径下。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userBean" class="cw.spring.bean.User" />
beans>
编写测试程序
package cw.spring.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
@Test
public void springTest() {
// 第一步:获取Spring容器对象。
// ApplicationContext 翻译为:应用上下文。其实就是Spring容器。
// ApplicationContext 是一个接口。
// ApplicationContext 接口下有很多实现类。其中有一个实现类叫做:ClassPathXmlApplicationContext
// ClassPathXmlApplicationContext 专门从类路径当中加载spring配置文件的一个Spring上下文对象。
// 这行代码只要执行:就相当于启动了Spring容器,解析spring.xml文件,并且实例化所有的bean对象,放到spring容器当中。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// 第二步:根据bean的id从Spring容器中获取这个对象。
Object userBean = applicationContext.getBean("userBean");
System.out.println(userBean);
}
}
//ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml");
//ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml", "beans.xml");
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml", "beans.xml", "xml/beans.xml");
// 源码:
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, (ApplicationContext)null);
}
//Object nowTime = applicationContext.getBean("nowTime");
//Date nowTime = (Date) applicationContext.getBean("nowTime");
// 不想强制类型转换,可以使用以下代码(通过第二个参数来指定返回的bean的类型。)
Date nowTime = applicationContext.getBean("nowTime", Date.class);
//ApplicationContext接口的超级父接口是:BeanFactory(翻译为Bean工厂,就是能够生产Bean对象的一个工厂对象。)
//BeanFactory是IoC容器的顶级接口。
//Spring的IoC容器底层实际上使用了:工厂模式。
//Spring底层的IoC是怎么实现的?XML解析 + 工厂模式 + 反射机制
//ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml");
BeanFactory applicationContext = new ClassPathXmlApplicationContext("spring6.xml");
User user = applicationContext.getBean("userBean", User.class);
System.out.println(user);
new ClassPathXmlApplicationContext("spring6.xml");
的时候,就会实例化对象。从Spring5之后,Spring框架支持集成的日志框架是Log4j2
第一步:引入Log4j2的依赖
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.19.0version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j2-implartifactId>
<version>2.19.0version>
dependency>
第二步:在类的根路径下提供log4j2.xml配置文件(文件名固定为:log4j2.xml,文件必须放到类根路径下。)
<configuration>
<loggers>
<root level="DEBUG">
<appender-ref ref="spring6log"/>
root>
loggers>
<appenders>
<console name="spring6log" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
console>
appenders>
configuration>
第三步:使用日志框架
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// 你自己怎么去使用log4j2记录日志信息呢?
// 第一步:创建日志记录器对象
// 获取FirstSpringTest类的日志记录器对象,也就是说只要是FirstSpringTest类中的代码执行记录日志的话,就输出相关的日志信息。
Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
// 第二步:记录日志,根据不同的级别来输出日志
logger.info("我是一条消息");
logger.debug("我是一条调试信息");
logger.error("我是一条错误信息");
set注入,基于set方法实现的,底层会通过反射机制调用属性对应的set方法然后给属性赋值。这种方式要求属性必须对外提供set方法。
public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert(){
//System.out.println("数据库正在保存用户信息。");
// 使用一下log4j2日志框架
logger.info("数据库正在保存用户信息。");
}
}
public class VipDao {
private static final Logger logger = LoggerFactory.getLogger(VipDao.class);
public void insert(){
logger.info("正在保存Vip信息!!!!");
}
}
public class UserService {
private UserDao userDao;
private VipDao vipDao;
public void setAbc(VipDao vipDao){
this.vipDao = vipDao;
}
// set注入的话,必须提供一个set方法。
// Spring容器会调用这个set方法,来给userDao属性赋值。
// 我自己写一个set方法,不使用IDEA工具生成的。不符合javabean规范。
// 至少这个方法是以set单词开始的。前三个字母不能随便写,必须是“set"
/*public void setMySQLUserDao(UserDao xyz){
this.userDao = xyz;
}*/
// 这个set方法是IDEA工具生成的,符合javabean规范。
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void saveUser(){
// 保存用户信息到数据库
userDao.insert();
vipDao.insert();
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao" ref="userDaoBean"/>
<property name="abc" ref="vipDaoBean"/>
bean>
<bean id="vipDaoBean" class="com.powernode.spring6.dao.VipDao"/>
beans>
实现原理:
property标签的name是:setUserDao()方法名演变得到的。演变的规律是:
另外,对于property标签来说,ref属性也可以采用标签的方式,但使用ref属性是多余的:
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao">
<ref bean="userDaoBean"/>
property>
bean>
总结:set注入的核心实现原理:通过反射机制调用set方法来给属性赋值,让两个对象之间产生关系。
核心原理:通过调用构造方法来给属性赋值。
与set注入相比,构造注入是在创建对象的同时进行注入,进行属性的赋值,而set注入是在对象创建之后。
public class CustomerService {
private UserDao userDao;
private VipDao vipDao;
public CustomerService(UserDao userDao, VipDao vipDao) {
this.userDao = userDao;
this.vipDao = vipDao;
}
public void save(){
userDao.insert();
vipDao.insert();
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="xxxx" class="com.powernode.spring6.dao.UserDao"/>
<bean id="yyyy" class="com.powernode.spring6.dao.VipDao"/>
<bean id="csBean3" class="com.powernode.spring6.service.CustomerService">
<constructor-arg ref="yyyy"/>
<constructor-arg ref="xxxx"/>
bean>
<bean id="csBean2" class="com.powernode.spring6.service.CustomerService">
<constructor-arg name="vipDao" ref="yyyy"/>
<constructor-arg name="userDao" ref="xxxx"/>
bean>
<bean id="csBean" class="com.powernode.spring6.service.CustomerService">
<constructor-arg index="0" ref="xxxx"/>
<constructor-arg index="1" ref="yyyy"/>
bean>
beans>
外部Bean的特点:bean定义到外面,在property标签中使用ref属性进行注入。通常这种方式是常用。
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao">bean>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
<property name="orderDao" ref="orderDaoBean"/>
bean>
内部Bean的方式:在bean标签中嵌套bean标签。
<bean id="orderServiceBean2" class="com.powernode.spring6.service.OrderService">
<property name="orderDao">
<bean class="com.powernode.spring6.dao.OrderDao">bean>
property>
bean>
public class User {
private String username; // String是简单类型
private String password;
private int age; // int是简单类型
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
<bean id="userBean" class="com.powernode.spring6.bean.User">
<property name="username" value="张三"/>
<property name="password" value="123"/>
<property name="age" value="20"/>
bean>
需要特别注意:如果给简单类型赋值,使用value属性或value标签。而不是ref。
通过源码分析得知,BeanUtils类,简单类型包括:
public class BeanUtils{
//.......
/**
* Check if the given type represents a "simple" property: a simple value
* type or an array of simple value types.
* See {@link #isSimpleValueType(Class)} for the definition of simple
* value type.
*
Used to determine properties to check for a "simple" dependency-check.
* @param type the type to check
* @return whether the given type represents a "simple" property
* @see org.springframework.beans.factory.support.RootBeanDefinition#DEPENDENCY_CHECK_SIMPLE
* @see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#checkDependencies
* @see #isSimpleValueType(Class)
*/
public static boolean isSimpleProperty(Class<?> type) {
Assert.notNull(type, "'type' must not be null");
return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}
/**
* Check if the given type represents a "simple" value type: a primitive or
* primitive wrapper, an enum, a String or other CharSequence, a Number, a
* Date, a Temporal, a URI, a URL, a Locale, or a Class.
* {@code Void} and {@code void} are not considered simple value types.
* @param type the type to check
* @return whether the given type represents a "simple" value type
* @see #isSimpleProperty(Class)
*/
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
//........
}
编写一个程序,把所有的简单类型全部测试一遍:
package com.powernode.spring6.beans;
import java.net.URI;
import java.net.URL;
import java.time.LocalDate;
import java.util.Date;
import java.util.Locale;
public class A {
private byte b;
private short s;
private int i;
private long l;
private float f;
private double d;
private boolean flag;
private char c;
private Byte b1;
private Short s1;
private Integer i1;
private Long l1;
private Float f1;
private Double d1;
private Boolean flag1;
private Character c1;
private String str;
private Date date;
private Season season;
private URI uri;
private URL url;
private LocalDate localDate;
private Locale locale;
private Class clazz;
// 生成setter方法
// 生成toString方法
}
enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="a" class="com.powernode.spring6.beans.A">
<property name="b" value="1"/>
<property name="s" value="1"/>
<property name="i" value="1"/>
<property name="l" value="1"/>
<property name="f" value="1"/>
<property name="d" value="1"/>
<property name="flag" value="false"/>
<property name="c" value="a"/>
<property name="b1" value="2"/>
<property name="s1" value="2"/>
<property name="i1" value="2"/>
<property name="l1" value="2"/>
<property name="f1" value="2"/>
<property name="d1" value="2"/>
<property name="flag1" value="true"/>
<property name="c1" value="a"/>
<property name="str" value="zhangsan"/>
<property name="date" value="Fri Sep 30 15:26:38 CST 2022"/>
<property name="season" value="WINTER"/>
<property name="uri" value="/save.do"/>
<property name="url" value="http://www.baidu.com"/>
<property name="localDate" value="EPOCH"/>
<property name="locale" value="CHINESE"/>
<property name="clazz" value="java.lang.String"/>
bean>
beans>
需要注意的是:
经典案例:给数据源的属性注入值:
/**
* 所有的数据源都要实现java规范:javax.sql.DataSource
* 什么是数据源:能够给你提供Connection对象的,都是数据源。
**/
public class MyDataSource implements DataSource { // 可以把数据源交给Spring容器来管理。
private String driver;
private String url;
private String username;
private String password;
public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
@Override
public Connection getConnection() throws SQLException {
// 获取数据库连接对象的时候需要4个信息:driver url username password
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
<bean id="myDataSource" class="com.powernode.spring6.jdbc.MyDataSource">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
bean>
@Test
public void testDataSource(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-datasource.xml");
MyDataSource dataSource = applicationContext.getBean("dataSource", MyDataSource.class);
System.out.println(dataSource);
}
// 班级类
public class Clazz {
// 班级名称
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Clazz{" +
"name='" + name + '\'' +
'}';
}
}
public class Student {
private String name;
// 学生属于哪个班级
private Clazz clazz;
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
// 使用级联属性赋值,这个需要这个get方法。
// 因为级联给学生所在的班级属性赋值会调用学生的getClazz()方法
public Clazz getClazz() {
return clazz;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", clazz=" + clazz +
'}';
}
}
原先的注入方法
<bean id="studentBean" class="com.powernode.spring6.bean.Student">
<property name="name" value="张三"/>
<property name="clazz" ref="clazzBean"/>
bean>
<bean id="clazzBean" class="com.powernode.spring6.bean.Clazz">
<property name="name" value="高三一班"/>
bean>
级联注入:
<bean id="studentBean" class="com.powernode.spring6.bean.Student">
<property name="name" value="张三"/>
<property name="clazz" ref="clazzBean"/>
<property name="clazz.name" value="高三二班"/>
bean>
<bean id="clazzBean" class="com.powernode.spring6.bean.Clazz">bean>
当数组中的元素是简单类型:
public class QianDaYe {
private String[] aiHaos;
public void setAiHaos(String[] aiHaos) {
this.aiHaos = aiHaos;
}
@Override
public String toString() {
return "QianDaYe{" +
"aiHaos=" + Arrays.toString(aiHaos) +
'}';
}
}
<bean id="yuQian" class="com.powernode.spring6.bean.QianDaYe">
<property name="aiHaos">
<array>
<value>抽烟value>
<value>喝酒value>
<value>烫头value>
array>
property>
bean>
当数组中的元素是非简单类型:
public class Woman {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Woman{" +
"name='" + name + '\'' +
'}';
}
}
public class QianDaYe {
private String[] aiHaos;
// 多个女性朋友
private Woman[] womens;
public void setWomens(Woman[] womens) {
this.womens = womens;
}
public void setAiHaos(String[] aiHaos) {
this.aiHaos = aiHaos;
}
@Override
public String toString() {
return "QianDaYe{" +
"aiHaos=" + Arrays.toString(aiHaos) +
", womens=" + Arrays.toString(womens) +
'}';
}
}
<bean id="w1" class="com.powernode.spring6.bean.Woman">
<property name="name" value="小花"/>
bean>
<bean id="w2" class="com.powernode.spring6.bean.Woman">
<property name="name" value="小亮"/>
bean>
<bean id="w3" class="com.powernode.spring6.bean.Woman">
<property name="name" value="小明"/>
bean>
<bean id="yuQian" class="com.powernode.spring6.bean.QianDaYe">
<property name="aiHaos">
<array>
<value>抽烟value>
<value>喝酒value>
<value>烫头value>
array>
property>
<property name="womens">
<array>
<ref bean="w1"/>
<ref bean="w2"/>
<ref bean="w3"/>
array>
property>
bean>
标签public class Person {
// 注入List集合
private List<String> names;
// 注入Set集合
private Set<String> addrs;
public void setNames(List<String> names) {
this.names = names;
}
public void setAddrs(Set<String> addrs) {
this.addrs = addrs;
}
@Override
public String toString() {
return "Person{" +
"names=" + names +
", addrs=" + addrs +
'}';
}
}
<bean id="personBean" class="com.powernode.spring6.bean.Person">
<property name="names">
<list>
<value>张三value>
<value>李四value>
<value>王五value>
<value>张三value>
<value>张三value>
<value>张三value>
<value>张三value>
list>
property>
<property name="addrs">
<set>
<value>北京大兴区value>
<value>北京大兴区value>
<value>北京海淀区value>
<value>北京海淀区value>
<value>北京大兴区value>
set>
property>
bean>
标签
标签嵌套
标签完成。public class Person {
// 注入Map集合
// 多个电话
private Map<Integer, String> phones;
// 注入属性类对象
// Properties本质上也是一个Map集合。
// Properties的父类Hashtable,Hashtable实现了Map接口。
// 虽然这个也是一个Map集合,但是和Map的注入方式有点像,但是不同。
// Properties的key和value只能是String类型。
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
public void setPhones(Map<Integer, String> phones) {
this.phones = phones;
}
@Override
public String toString() {
return "Person{" +
"phones=" + phones +
", properties=" + properties +
'}';
}
}
<bean id="personBean" class="com.powernode.spring6.bean.Person">
<property name="properties">
<props>
<prop key="driver">com.mysql.cj.jdbc.Driverprop>
<prop key="url">jdbc:mysql://localhost:3306/spring6prop>
<prop key="username">rootprop>
<prop key="password">123456prop>
props>
property>
<property name="phones">
<map>
<entry key="1" value="110"/>
<entry key="2" value="120"/>
<entry key="3" value="119"/>
map>
property>
bean>
或者 value=“”
或者 不为该属性赋值public class Cat {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
<bean id="catBean" class="com.powernode.spring6.bean.Cat">
<property name="name">
<value/>
property>
<property name="age" value="3">property>
bean>
public class MathBean {
private String result;
public void setResult(String result) {
this.result = result;
}
@Override
public String toString() {
return "MathBean{" +
"result='" + result + '\'' +
'}';
}
}
<bean id="mathBean" class="com.powernode.spring6.bean.MathBean">
<property name="result">
<value>value>
property>
bean>
public class Dog {
// 简单类型
private String name;
private int age;
// 非简单类型
private Date birth;
// p命名空间注入底层还是set注入,只不过p命名空间注入可以让spring配置变的更加简单。
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", birth=" + birth +
'}';
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dogBean" class="com.powernode.spring6.bean.Dog" p:name="小花" p:age="3" p:birth-ref="birthBean"/>
<bean id="birthBean" class="java.util.Date"/>
beans>
public class People {
private String name;
private int age;
private boolean sex;
// c命名空间是简化构造注入的。
// c命名空间注入办法是基于构造方法的。
public People(String name, int age, boolean sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="peopleBean" class="com.powernode.spring6.bean.People" c:name="jack" c:age="30" c:sex="true">bean>
beans>
使用util命名空间可以让配置复用。
使用util命名空间的前提是:在spring配置文件头部添加配置信息。如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
package com.powernode.spring6.beans;
import java.util.Properties;
public class MyDataSource1 {
// Properties属性类对象,这是一个Map集合,key和value都是String类型。
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "MyDataSource1{" +
"properties=" + properties +
'}';
}
}
package com.powernode.spring6.beans;
import java.util.Properties;
public class MyDataSource2 {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "MyDataSource2{" +
"properties=" + properties +
'}';
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:properties id="prop">
<prop key="driver">com.mysql.cj.jdbc.Driverprop>
<prop key="url">jdbc:mysql://localhost:3306/spring6prop>
<prop key="username">rootprop>
<prop key="password">123prop>
util:properties>
<bean id="ds1" class="com.powernode.spring6.jdbc.MyDataSource1">
<property name="properties" ref="prop"/>
bean>
<bean id="ds2" class="com.powernode.spring6.jdbc.MyDataSource2">
<property name="properties" ref="prop"/>
bean>
beans>
Spring还可以完成自动化的注入,自动化注入又被称为自动装配。它可以根据名字进行自动装配,也可以根据类型进行自动装配。
如果根据名称装配(byName),底层会调用set方法进行注入。
例如:setAge() 对应的名字是age,setPassword()对应的名字是password,setEmail()对应的名字是email。
public class OrderDao {
private static final Logger logger = LoggerFactory.getLogger(OrderDao.class);
public void insert(){
logger.info("订单正在生成....");
}
}
public class OrderService {
private OrderDao orderDao;
// 通过set方法给属性赋值。
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
/**
* 生成订单的业务方法。。。
*/
public void generate(){
orderDao.insert();
}
}
<bean id="orderService" class="com.powernode.spring6.service.OrderService" autowire="byName">bean>
<bean id="orderDao" class="com.powernode.spring6.dao.OrderDao"/>
public class CustomerService {
private UserDao userDao;
private VipDao vipDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setVipDao(VipDao vipDao) {
this.vipDao = vipDao;
}
public void save(){
userDao.insert();
vipDao.insert();
}
}
<bean class="com.powernode.spring6.dao.VipDao">bean>
<bean id="x" class="com.powernode.spring6.dao.UserDao">bean>
<bean id="cs" class="com.powernode.spring6.service.CustomerService" autowire="byType">bean>
我们都知道编写数据源的时候是需要连接数据库的信息的,例如:driver url username password等信息。这些信息可以单独写到一个属性配置文件中吗,这样用户修改起来会更加的方便。当然可以。
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring6
jdbc.username=root
jdbc.password=123
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="ds" class="com.powernode.spring6.jdbc.MyDataSource">
<property name="driver" value="${jdbc.driverClass}">property>
<property name="url" value="${jdbc.url}">property>
<property name="username" value="${jdbc.username}">property>
<property name="password" value="${jdbc.password}">property>
bean>
beans>
<bean id="sb" class="com.powernode.spring6.bean.SpringBean" scope="threadScope">bean>
<bean id="sb" class="com.powernode.spring6.bean.SpringBean" scope="session">bean>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>6.0.0-M2version>
dependency>
接下来咱们自定义一个Scope,线程级别的Scope,在同一个线程中,获取的Bean都是同一个。跨线程则是不同的对象:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="threadScope">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
entry>
map>
property>
bean>
<bean id="sb" class="com.powernode.spring6.bean.SpringBean" scope="threadScope">bean>
beans>
抽象产品 角色
public abstract class Weapon {
/**
* 所有的武器都可以攻击。
*/
public abstract void attack();
}
具体产品 角色
public class Tank extends Weapon{
@Override
public void attack() {
System.out.println("坦克开炮!!!");
}
}
public class Fighter extends Weapon{
@Override
public void attack() {
System.out.println("战斗机抛下小男孩!!!!");
}
}
public class Dagger extends Weapon{
@Override
public void attack() {
System.out.println("砍丫的!!!");
}
}
工厂类 角色(生产者)
public class WeaponFactory {
/**
* 静态方法。要获取什么产品?就看你传什么参数,传TANK获取坦克,传DAGGER获取匕首,传FIGHTER获取战斗机
* 简单工厂模式中有一个静态方法,所以被称为:静态工厂方法模式。
* @param weaponType
* @return
*/
public static Weapon get(String weaponType){
if ("TANK".equals(weaponType)) {
return new Tank();
} else if ("DAGGER".equals(weaponType)) {
return new Dagger();
} else if ("FIGHTER".equals(weaponType)) {
return new Fighter();
} else {
throw new RuntimeException("不支持该武器的生产");
}
}
}
客户端程序(消费者)
public class Test {
public static void main(String[] args) {
// 需要坦克
// 对于我客户端来说,坦克的生产细节,我不需要关心,我只需要向工厂索要即可。
// 简单工厂模式达到了什么呢?职责分离。客户端不需要关心产品的生产细节。
// 客户端只负责消费。工厂类负责生产。一个负责生产,一个负责消费。生产者和消费者分离了。这就是简单工厂模式的作用。
Weapon tank = WeaponFactory.get("TANK");
tank.attack();
// 需要匕首
Weapon dagger = WeaponFactory.get("DAGGER");
dagger.attack();
// 需要战斗机
Weapon fighter = WeaponFactory.get("FIGHTER");
fighter.attack();
}
}
抽象产品角色 Weapon
abstract public class Weapon {
public abstract void attack();
}
具体产品角色 Dagger Gun
public class Dagger extends Weapon{
@Override
public void attack() {
System.out.println("砍丫的!!!");
}
}
public class Gun extends Weapon{
@Override
public void attack() {
System.out.println("开枪射击!!!");
}
}
抽象工厂角色 WeaponFactory
abstract public class WeaponFactory {
/**
* 这个方法不是静态的。是实例方法。
* @return
*/
public abstract Weapon get();
}
具体工厂角色 DaggerFactory GunFactory
public class DaggerFactory extends WeaponFactory{
@Override
public Weapon get() {
return new Dagger();
}
}
public class GunFactory extends WeaponFactory{
@Override
public Weapon get() {
return new Gun();
}
}
武器产品族
public abstract class Weapon {
public abstract void attack();
}
public class Gun extends Weapon{
@Override
public void attack() {
System.out.println("开枪射击!");
}
}
public class Dagger extends Weapon{
@Override
public void attack() {
System.out.println("砍丫的!");
}
}
水果产品族
public abstract class Fruit {
/**
* 所有果实都有一个成熟周期。
*/
public abstract void ripeCycle();
}
public class Orange extends Fruit{
@Override
public void ripeCycle() {
System.out.println("橘子的成熟周期是10个月");
}
}
public class Apple extends Fruit{
@Override
public void ripeCycle() {
System.out.println("苹果的成熟周期是8个月");
}
}
抽象工厂类
public abstract class AbstractFactory {
public abstract Weapon getWeapon(String type);
public abstract Fruit getFruit(String type);
}
具体工厂类
public class WeaponFactory extends AbstractFactory{
public Weapon getWeapon(String type){
if (type == null || type.trim().length() == 0) {
return null;
}
if ("Gun".equals(type)) {
return new Gun();
} else if ("Dagger".equals(type)) {
return new Dagger();
} else {
throw new RuntimeException("无法生产该武器");
}
}
@Override
public Fruit getFruit(String type) {
return null;
}
}
public class FruitFactory extends AbstractFactory{
@Override
public Weapon getWeapon(String type) {
return null;
}
public Fruit getFruit(String type){
if (type == null || type.trim().length() == 0) {
return null;
}
if ("Orange".equals(type)) {
return new Orange();
} else if ("Apple".equals(type)) {
return new Apple();
} else {
throw new RuntimeException("我家果园不产这种水果");
}
}
}
客户端程序
public class Client {
public static void main(String[] args) {
// 客户端调用方法时只面向AbstractFactory调用方法。
AbstractFactory factory = new WeaponFactory(); // 注意:这里的new WeaponFactory()可以采用 简单工厂模式 进行隐藏。
Weapon gun = factory.getWeapon("Gun");
Weapon dagger = factory.getWeapon("Dagger");
gun.attack();
dagger.attack();
AbstractFactory factory1 = new FruitFactory(); // 注意:这里的new FruitFactory()可以采用 简单工厂模式 进行隐藏。
Fruit orange = factory1.getFruit("Orange");
Fruit apple = factory1.getFruit("Apple");
orange.ripeCycle();
apple.ripeCycle();
}
}
public class SpringBean {
public SpringBean() {
System.out.println("Spring Bean的无参数构造方法执行。");
}
}
<bean id="sb" class="com.powernode.spring6.bean.SpringBean"/>
定义一个Bean
public class Star {
public Star() {
System.out.println("Star的无参数构造方法执行。");
}
}
编写简单工厂模式当中的工厂类
public class StarFactory {
// 工厂类中有一个静态方法。
// 简单工厂模式又叫做:静态工厂方法模式。
public static Star get(){
// 这个Star对象最终实际上创建的时候还是我们负责new的对象。
return new Star();
}
}
在Spring配置文件中指定创建该Bean的方法(使用factory-method属性指定)
<bean id="star" class="com.powernode.spring6.bean.StarFactory" factory-method="get"/>
这种方式本质上是:通过工厂方法模式进行实例化。
定义一个Bean
/**
* 工厂方法模式当中的:具体产品角色。
**/
public class Gun {
public Gun() {
System.out.println("Gun的无参数构造方法执行。");
}
}
定义具体工厂类,工厂类中定义实例方法
/**
* 工厂方法模式当中的:具体工厂角色。
**/
public class GunFactory {
// 工厂方法模式中的具体工厂角色中的方法是:实例方法。
public Gun get(){
// 实际上new这个对象还是我们程序员自己new的。
return new Gun();
}
}
在Spring配置文件中指定factory-bean以及factory-method
<bean id="gunFactory" class="com.powernode.spring6.bean.GunFactory"/>
<bean id="gun" factory-bean="gunFactory" factory-method="get"/>
定义一个Bean
public class Person { // 普通的Bean
public Person() {
System.out.println("Person的无参数构造方法执行。");
}
}
编写一个类实现FactoryBean接口
public class PersonFactoryBean implements FactoryBean<Person> {
// PersonFactoryBean也是一个Bean。只不过这个Bean比较特殊。叫做工厂Bean。
// 通过工厂Bean这个特殊的Bean可以获取一个普通的Bean。
@Override
public Person getObject() throws Exception {
// 最终这个Bean的创建还是程序员自己new的。
return new Person();
}
@Override
public Class<?> getObjectType() {
return null;
}
// 这个方法在接口中有默认实现。
// 默认返回true,表示单例的。
// 如果想多例,直接将这个方法修改为return false;即可。
@Override
public boolean isSingleton() {
return true;
}
}
在Spring配置文件中配置FactoryBean
<bean id="person" class="com.powernode.spring6.bean.PersonFactoryBean"/>
我们前面说过,java.util.Date在Spring中被当做简单类型,简单类型在注入的时候可以直接使用value属性或value标签来完成。但我们之前已经测试过了,对于Date类型来说,采用value属性或value标签赋值的时候,对日期字符串的格式要求非常严格,必须是这种格式的:Mon Oct 10 14:30:26 CST 2022。其他格式是不会被识别的。
未使用FactoryBean来完成
public class Student {
// java.util.Date 在Spring当中被当做简单类型。 但是简单类型的话,注入的日期字符串格式有要求。
// java.util.Date 在Spring当中也可以被当做非简单类型。
private Date birth;
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Student{" +
"birth=" + birth +
'}';
}
}
<bean id="nowTime" class="java.util.Date"/>
<bean id="student" class="com.powernode.spring6.bean.Student">
<property name="birth" ref="nowTime"/>
bean>
使用FactoryBean来完成
public class DateFactoryBean implements FactoryBean<Date> {
// DateFactoryBean这个工厂Bean协助我们Spring创建这个普通的Bean:Date。
private String strDate;
public DateFactoryBean(String strDate) {
this.strDate = strDate;
}
@Override
public Date getObject() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse(strDate);
return date;
}
@Override
public Class<?> getObjectType() {
return null;
}
}
<bean id="date" class="com.powernode.spring6.bean.DateFactoryBean">
<constructor-arg index="0" value="2008-10-11"/>
bean>
<bean id="studentBean" class="com.powernode.spring6.bean.Student">
<property name="birth" ref="date"/>
bean>
/**
*
* Bean的生命周期按照粗略的五步的话:
* 第一步:实例化Bean(调用无参数构造方法。)
* 第二步:给Bean属性赋值(调用set方法。)
* 第三步:初始化Bean(会调用Bean的init方法。注意:这个init方法需要自己写,自己配。)
* 第四步:使用Bean
* 第五步:销毁Bean(会调用Bean的destroy方法。注意:这个destroy方法需要自己写,自己配。)
**/
public class User{
private String name;
public User() {
System.out.println("第一步:无参数构造方法执行。");
}
public void setName(String name) {
System.out.println("第二步:给对象的属性赋值。");
this.name = name;
}
// 这个方法需要自己写,自己配。方法名随意。
public void initBean(){
System.out.println("第三步:初始化Bean。");
}
// 这个方法需要自己写,自己配。方法名随意。
public void destroyBean(){
System.out.println("第五步:销毁Bean。");
}
}
@Test
public void testBeanLifecycleFive(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println("第四步:使用Bean:" + user);
// 注意:必须手动关闭Spring容器,这样Spring容器才会销毁Bean.
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();
}
<bean id="user" class="com.powernode.spring6.bean.User"
init-method="initBean" destroy-method="destroyBean" >
<property name="name" value="zhangsan"/>
bean>
/**
* 日志Bean后处理器。
**/
public class LogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第三步:执行Bean后处理器的before方法。");
// return 代码不用修改,不用动
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
// 方法有两个参数:
// 第一个参数:刚创建的bean对象。
// 第二个参数:bean的名字。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第五步:执行Bean后处理器的after方法。");
// return 代码不用修改,不用动
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
<bean class="com.powernode.spring6.bean.LogBeanPostProcessor"/>
<bean id="user" class="com.powernode.spring6.bean.User"
init-method="initBean" destroy-method="destroyBean">
<property name="name" value="zhangsan"/>
bean>
public class User{
private String name;
public User() {
System.out.println("第一步:无参数构造方法执行。");
}
public void setName(String name) {
System.out.println("第二步:给对象的属性赋值。");
this.name = name;
}
// 这个方法需要自己写,自己配。方法名随意。
public void initBean(){
System.out.println("第四步:初始化Bean。");
}
// 这个方法需要自己写,自己配。方法名随意。
public void destroyBean(){
System.out.println("第七步:销毁Bean。");
}
}
@Test
public void testBeanLifecycleFive(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println("第六步:使用Bean:" + user);
// 注意:必须手动关闭Spring容器,这样Spring容器才会销毁Bean.
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();
}
public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
private String name;
public User() {
System.out.println("第一步:无参数构造方法执行。");
}
public void setName(String name) {
System.out.println("第二步:给对象的属性赋值。");
this.name = name;
}
// 这个方法需要自己写,自己配。方法名随意。
public void initBean(){
System.out.println("第四步:初始化Bean。");
}
// 这个方法需要自己写,自己配。方法名随意。
public void destroyBean(){
System.out.println("第七步:销毁Bean。");
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("Bean这个类的加载器:" + classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("生产这个Bean的工厂对象是:" + beanFactory);
}
@Override
public void setBeanName(String name) {
System.out.println("这个Bean的名字是:" + name);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean's afterPropertiesSet执行。");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean's destroy方法执行");
}
}
public class LogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第三步:执行Bean后处理器的before方法。");
// return 代码不用修改,不用动
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
// 方法有两个参数:
// 第一个参数:刚创建的bean对象。
// 第二个参数:bean的名字。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第五步:执行Bean后处理器的after方法。");
// return 代码不用修改,不用动
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
<bean class="com.powernode.spring6.bean.LogBeanPostProcessor"/>
<bean id="user" class="com.powernode.spring6.bean.User"
init-method="initBean" destroy-method="destroyBean">
<property name="name" value="zhangsan"/>
bean>
@Test
public void testBeanLifecycleFive(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println("第六步:使用Bean:" + user);
// 注意:必须手动关闭Spring容器,这样Spring容器才会销毁Bean.
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();
}
<bean class="com.powernode.spring6.bean.LogBeanPostProcessor"/>
<bean id="user" class="com.powernode.spring6.bean.User"
init-method="initBean" destroy-method="destroyBean" scope="prototype">
<property name="name" value="zhangsan"/>
bean>
/**
* Spring容器只对singleton的Bean进行完整的生命周期管理。
* 如果是prototype作用域的Bean,Spring容器只负责将该Bean初始化完毕。等客户端程序一旦获取到该Bean之后,Spring容器就不再管理该对象的生命周期了。
*/
@Test
public void testBeanLifecycleFive(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println("第六步:使用Bean:" + user);
// 注意:必须手动关闭Spring容器,这样Spring容器才会销毁Bean.
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();
}
有些时候可能会遇到这样的需求,某个java对象是我们自己new的,然后我们希望这个对象被Spring容器管理,怎么实现?
@Test
public void testRegisterBean(){
// 自己new的对象
Student student = new Student();
System.out.println(student);
// 将以上自己new的这个对象纳入Spring容器来管理。半路上交给Spring来管理。
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 自己new的对象注册到spring容器中
factory.registerSingleton("studentBean", student);
// 从spring容器中获取
Object studentBean = factory.getBean("studentBean");
System.out.println(studentBean);
}
A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
比如:丈夫类Husband,妻子类Wife。Husband中有Wife的引用。Wife中有Husband的引用。
在singleton + set注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题。
public class Husband {
private String name;
private Wife wife;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
public class Wife {
private String name;
private Husband husband;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="singleton">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
bean>
@Test
public void testCD(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
System.out.println(husbandBean);
Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
System.out.println(wifeBean);
}
当循环依赖的所有Bean的scope="prototype"的时候,产生的循环依赖,Spring是无法解决的,会出现BeanCurrentlyInCreationException异常。
public class Husband {
private String name;
private Wife wife;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
public class Wife {
private String name;
private Husband husband;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="prototype">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
bean>
@Test
public void testCD(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
System.out.println(husbandBean);
Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
System.out.println(wifeBean);
}
如果其中一个是singleton,另一个是prototype,是没有问题的。singleton在解析配置文件时就创建对象,当需要prototype时可以马上创建一个prototype对象赋值给singleton,而prototype对象所需的singleton只有唯一的一个,可以让依赖停止下来,所以没有问题。
public class Husband {
private String name;
private Wife wife;
public Husband(String name, Wife wife) {
this.name = name;
this.wife = wife;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
public class Wife {
private String name;
private Husband husband;
public Wife(String name, Husband husband) {
this.name = name;
this.husband = husband;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
<bean id="h" scope="singleton" class="com.powernode.spring6.bean2.Husband">
<constructor-arg index="0" value="张三">constructor-arg>
<constructor-arg index="1" ref="w">constructor-arg>
bean>
<bean id="w" scope="singleton" class="com.powernode.spring6.bean2.Wife">
<constructor-arg index="0" value="小花">constructor-arg>
<constructor-arg index="1" ref="h">constructor-arg>
bean>
@Test
public void testCD2(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
Husband husbandBean = applicationContext.getBean("h", Husband.class);
System.out.println(husbandBean);
Wife wifeBean = applicationContext.getBean("w", Wife.class);
System.out.println(wifeBean);
}
执行结果:发生了异常,信息如下:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'hBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:325)
... 56 more
Spring解决循环依赖的机理:https://www.yuque.com/dujubin/ltckqu/kipzgd#wt8oL
Spring为什么可以解决set + singleton模式下循环依赖?
根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。
实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。
给Bean属性赋值的时候:调用setter方法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。
源码分析:
DefaultSingletonBeanRegistry类中有三个比较重要的缓存:
private final Map<String, Object> singletonObjects 一级缓存
private final Map<String, Object> earlySingletonObjects 二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories 三级缓存
这三个缓存都是Map集合。
Map集合的key存储的都是bean的name(bean id)。
一级缓存存储的是:单例Bean对象。完整的单例Bean对象,也就是说这个缓存中的Bean对象的属性都已经赋值了。是一个完整的Bean对象。
二级缓存存储的是:早期的单例Bean对象。这个缓存中的单例Bean对象的属性没有赋值。只是一个早期的实例对象。
三级缓存存储的是:单例工厂对象。这个里面存储了大量的“工厂对象”,每一个单例Bean对象都会对应一个单例工厂对象。
这个集合中存储的是,创建该单例对象时对应的那个单例工厂对象。
在该类中有这样一个方法addSingletonFactory(),这个方法的作用是:将创建Bean对象的ObjectFactory对象提前曝光。
从源码中可以看到,spring会先从一级缓存中获取Bean,如果获取不到,则从二级缓存中获取Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题。
Spring只能解决setter方法注入的单例bean之间的循环依赖。ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。