Spring-IoC容器
Spring IoC容器又称为Spring容器,是Spring架构的核心组件,它负责管理应用程序中对象(通常称为bean,bean是IoC容器中的对象实例)的创建、配置、生命周期和依赖关系。
IoC容器的基本概念
IoC容器又称为容器,是一种设计模式,用于管理应用程序中对象的创建、配置、生命周期和依赖关系。容器是实施控制反转(IoC)原则的一种方式,它将对象的管理责任从应用程序代码转移到容器中。
控制反转(IoC)
控制反转是一种思想。传统上,对象的控制权是在程序员手中。在IoC中,这个控制权被反转,将对象的创建权利、对象和对象之间关系的维护权利均交给容器管理,从而实现解耦和灵活配置。
依赖注入(DI)
容器通过依赖注入机制将对象依赖的属性通过配置进行注入。依赖注入可以是构造器注入、属性注入、方法注入。依赖注入实现了控制反转的思想。
bean
bean是指由容器创建、配置、组装、管理的对象。这些对象的生命周期和依赖关系有容器负责管理,从而实现了控制反转和依赖注入。
容器在Spring中的实现
Spring中的容器就是IoC思想的一个实现。IoC容器中创建、配置、组装、管理的对象称为bean。在创建bean之前,首先需要创建容器,下面介绍一下Spring 容器的接口。
BeanFactory
BeanFactory是Spring Ioc容器的最基本接口,提供了基础的依赖注入和Bean管理功能。
这个接口是由包含很多bean定义的对象实现的,每个bean定义由一个String名称唯一标识。根据bean定义,工厂将返回包含对象的独立实例(Prototype设计模式)或单个共享实例。返回哪种类型实例取决于bean factory的配置。
自Spring 2.0开始,使用BeanFactory的子接口ApplicationContext可以使用更多的功能(推荐使用ApplicationContext)。
ApplicationContext
ApplicationContext是BeanFactory的子接口,除了拥有BeanFactory的所有功能外,还提供了更多的功能。
ApplicationContext提供了:
- bean工厂方法用于访问应用程序组件-继承自ListableBeanFactory接口
- 以通用方式加载文件资源的能力-继承自ResourceLoader接口
- 向已注册的监听器发布时间的能力-继承自ApplicationEvntPublisher接口
- 解析消息的能力,支持国际化-继承自MessageSource接口
常见的ApplicationContext实现类
- ClassPathXmlApplicationContext
从类路径下的XML配置文件中加载bean的定义。
- AnnotationConfigApplicationContext
用于基于注解的Java配置(如@Configuration注解)来加载Spring配置类。
- GenericWebApplicationContext
用于Web应用程序的容器,适用于基于Spring Web的应用。
加载ApplicationContext(初始化Spring 容器)的示例
Spring提供了不同的方式加载ApplicationContext,最常见的是通过XML配置、Java配置类或注解来初始化Spring容器,这里先简单演示一下这两种方式,后面会具体讲如何使用
通过XML配置文件加载ApplicationContext
java类:
//将此类定义为spring bean
@Component
public class MyBean {
}
xml文件:
<!-- applicationContext.xml -->
<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="myBean" class="com.example.MyBean"/>
</beans>
java加载文件:
// 使用 ClassPathXmlApplicationContext 来加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 通过ApplicationContext的方法获取到bean对象
MyBean myBean = (MyBean) context.getBean("myBean");
通过Java配置类加载ApplicationContext
java类:
//添加注解,将此类定义为spring bean
@Component
public class MyBean {
}
java配置类:
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
}
java加载文件:
// 使用 AnnotationConfigApplicationContext 来加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyBean myBean = context.getBean(MyBean.class);
基于XML管理bean
引入依赖
pom文件:
<!--spring context依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.11</version>
</dependency>
<!--log4j2的依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
log4j2日志配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<loggers>
<!--
level指定日志级别,从低到高的优先级:
TRACE < DEBUG < INFO < WARN < ERROR < FATAL
trace:追踪,是最低的日志级别,相当于追踪程序的执行
debug:调试,一般在开发中,都将其设置为最低的日志级别
info:信息,输出重要的信息,使用较多
warn:警告,输出警告的信息
error:错误,输出错误信息
fatal:严重错误
-->
<root level="DEBUG">
<appender-ref ref="console"/>
<appender-ref ref="rolling-file"/>
</root>
</loggers>
<appenders>
<!--输出日志信息到控制台-->
<console name="console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
</console>
<!-- 这个会打印出所有的信息,
每次大小超过size,
则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
作为存档-->
<RollingFile name="rolling-file" fileName="/opt/logs/spring6-demo/app.log"
filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="50MB"/>
<!-- DefaultRolloverStrategy属性如不设置,
则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="10"/>
</RollingFile>
</appenders>
</configuration>
获取bean的三种方式
- 根据id获取
- 根据类型获取
- 根据id和类型获取
注: 如果是根据类型获取bean时,指定类型的bean有且仅有一个,否则会报错(报根据类型找到多个)。
获取bean的示例
用于被创建实例的类
public class UserEntity {
private final static Logger logger = LoggerFactory.getLogger(UserEntity.class);
private String id;
private String name;
public void printHello() {
logger.info("hello");
}
}
用于定义bean的XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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演示 UserEntity对象创建-->
<bean id="userEntity" class="com.shen.springiocxml.entity.UserEntity"></bean>
<!-- 当两个相同类型的bean时,通过类型获取bean会报异常-->
<!-- <bean id="UserEntity1" class="com.shen.springiocxml.entity.UserEntity"></bean>-->
</beans>
测试类
public class TestUserEntity {
private final static Logger logger = LoggerFactory.getLogger(TestUserEntity.class);
public static void main(String[] args) {
//创建spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//1.根据id获取bean
logger.info("根据id获取bean");
UserEntity userEntity1 = (UserEntity) context.getBean("userEntity");
userEntity1.printHello();
logger.info("获取bean的hashcode:{}", userEntity1.hashCode());
//2.根据类型获取bean
logger.info("根据类型获取bean");
UserEntity userEntity2 = context.getBean(UserEntity.class);
userEntity2.printHello();
logger.info("获取bean的hashcode:{}", userEntity2.hashCode());
//3.根据id和类型获取bean
logger.info("根据id和类型获取bean");
UserEntity userEntity3 = context.getBean("userEntity", UserEntity.class);
userEntity3.printHello();
logger.info("获取bean的hashcode:{}", userEntity3.hashCode());
}
}
接口实现类根据接口类型获取bean的实例
接口实现类可以根据接口类型获取bean,如果是根据类型获取bean时,同样需要指定接口类型的bean唯一
接口类
public interface UserService {
public void printRun();
}
接口实现类
public class UserServiceImpl implements UserService {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Override
public void printRun() {
logger.info("run====================");
}
}
定义bean的XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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="userServiceImpl" class="com.shen.springiocxml.service.impl.UserServiceImpl"></bean>
</beans>
测试类
public class TestUserServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(TestUserServiceImpl.class);
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//根据类型获取接口对应的bean
UserService userService = context.getBean(UserService.class);
logger.info("userService instanceof UserServiceImpl result:{}", userService instanceof UserServiceImpl);
logger.info("输出得到的对象:{}", userService);
userService.printRun();
}
}
依赖注入
依赖注入(Dependency Injection DI)是一种通过构造器参数、工厂方法参数(构造方法的依赖注入)或者在对象实例构造后、工厂方法返回后设置对象的属性(基于setter的依赖注入),来定义对象的依赖关系(即该对象所依赖的其他对象)的过程。容器在创建bean时会注入这些依赖。这个过程与正常代码创建一个对象相反,bean本身通过类的构造器或者Service Locator模式来控制其依赖项的实例化(因此得名为控制反转-Inversion of Control)
使用DI原则的代码更加简洁,当对象通过依赖注入获取依赖时,解耦效果更为显著。对象不再查找其依赖项,也不知道依赖项的类或位置,因此,当依赖项时接口或抽象类时,你的类会变得更容易进行单元测试,因为可以在单元测试中使用存根(stub)或模拟(muck)。
依赖注入有两种方式:
- 基于构造方法的依赖注入
- 基于setter的依赖注入
依赖注入的示例
用于创建实例的类
public class BookEntity {
private String name;
private String author;
public BookEntity() {
}
//全参构造方法
public BookEntity(String name, String author) {
this.name = name;
this.author = author;
}
//setter方法
public void setName(String name) {
this.name = name;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "BookEntity{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
'}';
}
}
用于定义bean的XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--1.set方式注入-->
<bean id="bookEntity3" class="com.shen.springiocxml.entity.BookEntity">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<property name="name" value="英语"></property>
<property name="author" value="李华"></property>
</bean>
<!--2.全参构造器方式注入-->
<bean id="bookEntity4" class="com.shen.springiocxml.entity.BookEntity">
<!-- constructor-arg标签:通过组件类的构造方法给组件对象设置属性 -->
<!--name属性:指定参数名-->
<constructor-arg name="name" value="物理"></constructor-arg>
<!--index属性:指定参数所在位置的索引(从0开始)-->
<!-- <constructor-arg index="0" value="物理"></constructor-arg>-->
<constructor-arg name="author" value="小王"></constructor-arg>
<!-- <constructor-arg index="1" value="小王"></constructor-arg>-->
</bean>
</beans>
测试类
public class TestBookEntity {
private static final Logger logger = LoggerFactory.getLogger(TestBookEntity.class);
public static void main(String[] args) {
//常规方式注入的两种方式(set和全参构造器)
BookEntity bookEntity1 = new BookEntity();
bookEntity1.setName("语文");
bookEntity1.setAuthor("小明");
logger.info("常规方式注入-set方式,对象打印:{}", bookEntity1);
BookEntity bookEntity2 = new BookEntity("数学", "小李");
logger.info("常规方式注入-全参构造器方式,对象打印:{}", bookEntity2);
ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");
//spring基于xml方式 set注入方式
BookEntity bookEntity3 = context.getBean("bookEntity3", BookEntity.class);
logger.info("ioc注入-set方式,对象打印:{}", bookEntity3);
//spring基于xml方式 全参构造器注入方式
BookEntity bookEntity4 = context.getBean("bookEntity4", BookEntity.class);
logger.info("ioc注入-全参构造器方式,对象打印:{}", bookEntity4);
}
}
依赖注入-特殊值处理
常规值
不存在转义,就是数据本身的值
<property name="name" value="小明"></property>
null值
<property name="author">
<null/>
</property>
xml实体
由于在xml配置文件中‘<’和'>'符号是定义标签的,如果想赋给属性需要使用转义符号
< 表示'<'符号
> 表示'>'符号
<!--给name属性赋值为<hello>-->
<property name="author" value="<hello>"></property>
CDTA
CDTA(Character Data)表示字符数据,用于包含不应由XML解析器解析的文本数据
格式为: <![CDATA[文本内容]]>
在CDTA部分中的所有内容都会被XML解析器视为普通文本,不会进行任何解析或转义
<!--给author属性赋值为a < b-->
<property name="author"> <value><![CDATA[a < b]]></value></property>
依赖注入-不同类型属性的赋值
对象类型属性的赋值
对象类型属性的赋值有三种方式:
- 引用外部的bean
- 内部bean
- 级联属性赋值(不常用)
对象类型属性赋值示例
被引用的类
public class DeptEntity {
private String dName;
@Override
public String toString() {
return "DeptEntity{" +
"dName='" + dName + '\'' +
'}';
}
public String getdName() {
return dName;
}
public void setdName(String dName) {
this.dName = dName;
}
}
包含对象类型属性的类
public class EmpEntity {
private String eName;
private String age;
private DeptEntity deptEntity;
@Override
public String toString() {
return "EmpEntity{" +
"eName='" + eName + '\'' +
", age='" + age + '\'' +
", deptEntity=" + deptEntity +
'}';
}
public DeptEntity getDeptEntity() {
return deptEntity;
}
public void setDeptEntity(DeptEntity deptEntity) {
this.deptEntity = deptEntity;
}
public String geteName() {
return eName;
}
public void seteName(String eName) {
this.eName = eName;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
定义bean的XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- 方法1-->
<bean id="deptEntityOne" class="com.shen.springiocxml.entity.DeptEntity">
<property name="dName" value="财务部"></property>
</bean>
<bean id="empEntityOne" class="com.shen.springiocxml.entity.EmpEntity">
<property name="eName" value="小王"></property>
<property name="age" value="23"></property>
<!-- 引入外部bean ref的值为对应bean的id-->
<property name="deptEntity" ref="deptEntityOne"></property>
</bean>
<!-- 方法2-->
<bean id="empEntityTwo" class="com.shen.springiocxml.entity.EmpEntity">
<property name="eName" value="小刘"></property>
<property name="age" value="35"></property>
<!-- 内部bean-->
<property name="deptEntity">
<bean id="deptEntityTwo" class="com.shen.springiocxml.entity.DeptEntity">
<property name="dName" value="研发部"></property>
</bean>
</property>
</bean>
<!-- 方法3(不常用)-->
<bean id="deptEntityThree" class="com.shen.springiocxml.entity.DeptEntity">
<property name="dName" value="销售部"></property>
</bean>
<bean id="empEntityThree" class="com.shen.springiocxml.entity.EmpEntity">
<property name="eName" value="小张"></property>
<property name="age" value="35"></property>
<!-- 级联赋值-->
<property name="deptEntity" ref="deptEntityThree"></property>
<property name="deptEntity.dName" value="测试部"></property>
</bean>
</beans>
测试类
public class TestEmpEntity {
public static final Logger logger = LoggerFactory.getLogger(TestEmpEntity.class);
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-object-di.xml");
EmpEntity empEntityOne = context.getBean("empEntityOne", EmpEntity.class);
logger.info("emlEntityOne :{}", empEntityOne);
EmpEntity empEntityTwo = context.getBean("empEntityTwo", EmpEntity.class);
logger.info("emlEntityTwo :{}", empEntityTwo);
EmpEntity empEntityThree = context.getBean("empEntityThree", EmpEntity.class);
logger.info("empEntityThree :{}", empEntityThree);
}
}
数组类型属性的赋值
当属性为数组类型时,需要使用array标签表示该属性为数组,在value标签中添加实际数组中元素的值
数组类型属性的赋值示例
<?xml version="1.0" encoding="UTF-8"?>
<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="deptEntity" class="com.shen.springiocxml.entity.DeptEntity">
<property name="dName" value="财务部"></property>
</bean>
<bean id="empEntity" class="com.shen.springiocxml.entity.EmpEntity">
<property name="hobby">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
<property name="eName" value="小王"></property>
<property name="age" value="23"></property>
<!-- 引入外部bean ref的值为对应bean的id-->
<property name="deptEntity" ref="deptEntity"></property>
</bean>
</beans>
集合类型属性的赋值
当属性为集合类型时,需要使用list、set、map标签表示该属性为集合
list集合示例
list和set类似区别在于标签名不同
被引用的类
public class EmpEntity {
public static final Logger logger = LoggerFactory.getLogger(EmpEntity.class);
private String eName;
private String age;
private DeptEntity deptEntity;
private String[] hobby;
public String[] getHobby() {
return hobby;
}
public void setHobby(String[] hobby) {
this.hobby = hobby;
}
public void printHobby() {
for (String s : this.hobby) {
logger.info("{}", s);
}
}
@Override
public String toString() {
return "EmpEntity{" +
"eName='" + eName + '\'' +
", age='" + age + '\'' +
", deptEntity=" + deptEntity +
'}';
}
public DeptEntity getDeptEntity() {
return deptEntity;
}
public void setDeptEntity(DeptEntity deptEntity) {
this.deptEntity = deptEntity;
}
public String geteName() {
return eName;
}
public void seteName(String eName) {
this.eName = eName;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
包含集合属性的类
public class DeptEntity {
private static final Logger logger = LoggerFactory.getLogger(DeptEntity.class);
private String dName;
private List<EmpEntity> empList;
public List<EmpEntity> getEmpList() {
return empList;
}
public void setEmpList(List<EmpEntity> empList) {
this.empList = empList;
}
@Override
public String toString() {
return "DeptEntity{" +
"dName='" + dName + '\'' +
'}';
}
public String getdName() {
return dName;
}
public void setdName(String dName) {
this.dName = dName;
}
public void printEmpList() {
if (this.empList == null) {
return;
}
for (EmpEntity empEntity : this.empList) {
logger.info("{}", empEntity);
}
}
}
定义bean的xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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="empEntityOne" class="com.shen.springiocxml.entity.EmpEntity">
<property name="eName" value="小王"></property>
<property name="age" value="23"></property>
</bean>
<bean id="empEntityTwo" class="com.shen.springiocxml.entity.EmpEntity">
<property name="eName" value="小刘"></property>
<property name="age" value="35"></property>
</bean>
<bean id="deptEntityOne" class="com.shen.springiocxml.entity.DeptEntity">
<property name="dName" value="财务部"></property>
<property name="empList">
<list>
<!-- 如果list中的元素时基本类型,可以用value标签表示-->
<!-- <value>s</value>-->
<ref bean="empEntityOne"></ref>
<ref bean="empEntityTwo"></ref>
</list>
</property>
</bean>
</beans>
测试类
public class TestDeptEntity {
private static final Logger logger = LoggerFactory.getLogger(TestDeptEntity.class);
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-collection-di.xml");
DeptEntity deptEntityOne = context.getBean("deptEntityOne", DeptEntity.class);
deptEntityOne.printEmpList();
}
}
map集合示例
被引用的类
public class TeacherEntity {
private String tid;
private String tname;
public String getTid() {
return tid;
}
public void setTid(String tid) {
this.tid = tid;
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
@Override
public String toString() {
return "TeacherEntity{" +
"tid='" + tid + '\'' +
", tname='" + tname + '\'' +
'}';
}
}
包含集合属性的类
public class StudentEntity {
private Logger logger = LoggerFactory.getLogger(StudentEntity.class);
private int sid;
private String sname;
private Map<String, TeacherEntity> teacherMap;
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Map<String, TeacherEntity> getTeacherMap() {
return teacherMap;
}
public void setTeacherMap(Map<String, TeacherEntity> teacherMap) {
this.teacherMap = teacherMap;
}
public void printStudentInfo() {
logger.info("学生id:[{}],学生姓名:[{}]", this.sid, this.sname);
if (this.teacherMap == null) {
return;
}
for (Map.Entry<String, TeacherEntity> entityEntry : this.teacherMap.entrySet()) {
logger.info("{}课程老师信息:", entityEntry.getKey());
logger.info("{}", entityEntry.getValue());
}
}
}
定义bean的xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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="teacherEntityOne" class="com.shen.springiocxml.entity.TeacherEntity">
<property name="tid" value="1"></property>
<property name="tname" value="张老师"></property>
</bean>
<bean id="teacherEntityTwo" class="com.shen.springiocxml.entity.TeacherEntity">
<property name="tid" value="2"></property>
<property name="tname" value="李老师"></property>
</bean>
<bean id="studentEntityOne" class="com.shen.springiocxml.entity.StudentEntity">
<property name="sid" value="1"></property>
<property name="sname" value="小王"></property>
<property name="teacherMap">
<map>
<entry>
<key>
<value>语文</value>
</key>
<!-- 如果是基本类型使用value即可-->
<!-- <value></value>-->
<ref bean="teacherEntityOne"></ref>
</entry>
<entry>
<key>
<value>数学</value>
</key>
<ref bean="teacherEntityOne"></ref>
</entry>
</map>
</property>
</bean>
</beans>
测试类
public class TestStudentEntity {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-collection-map-di.xml");
StudentEntity studentEntityOne = context.getBean("studentEntityOne", StudentEntity.class);
studentEntityOne.printStudentInfo();
}
}
p命名空间
在Spring框架中,p命名空间是一个用于简化XML配置文件的特性,特别是在进行依赖注入时。p命名空间允许你以属性的方式直接配置bean的属性,而不是使用嵌套的property标签。这种配置方式更加简洁。
p命名空间只能用于设置属性的值,不能用于设置复杂的属性,如集合、映射等。
当使用p:属性名-ref时,则表示引用其他的bean。
p命名空间简化了配置,但不会影响Spring容器的功能,底层仍然是通过setter方式完成依赖注入的。
XML配置文件中引入p命名空间
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
使用p命名空间进行属性注入
<?xml version="1.0" encoding="UTF-8"?>
<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="deptEntityOne" class="com.shen.springiocxml.entity.DeptEntity"
p:dName="财务部">
</bean>
<bean id="empEntityOne" class="com.shen.springiocxml.entity.EmpEntity"
p:eName="小王"
p:age="23"
p:deptEntity-ref="deptEntityOne"
>
</bean>
</beans>
引入外部属性文件
如果想要引入外部属性值,则需要引入context命名空间,使用context命名空间声明外部属性文件的路径
属性文件
jdbc.user=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver
引入context命名空间的xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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的命名空间,才能使用该标签-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="druidDataSourceOne" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driver}"></property>
</bean>
</beans>
测试类
public class TestDruidDataSource {
private static final Logger logger = LoggerFactory.getLogger(TestDruidDataSource.class);
public static void main(String[] args) {
//常规方式生成Druid数据源对象
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUsername("root");
druidDataSource.setPassword("123456");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=UTC");
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
logger.info("常规方式生成DruidDataSource对象的url: {}", druidDataSource.getUrl());
//spring方式生成Druid数据源对象
ApplicationContext context = new ClassPathXmlApplicationContext("bean-jdbc.xml");
DruidDataSource druidDataSource1 = context.getBean("druidDataSourceOne", DruidDataSource.class);
logger.info("spring方式生成DruidDataSource对象的url: {}", druidDataSource1.getUrl());
}
}
bean的作用域
在Spring框架中,bean的作用域定义了bean的生命周期和可见性。Spring支持多种作用域,以下是常见的bean作用域
1.单例模式(singleton)
默认的作用域。在Spring容器中,一个bean定义只生成一个实例。这个实例在spring容器启动时被创建,并在整个应用程序生命周期中重复使用。
优点: 性能高,不需要每次请求时都创建新实例。
缺点:不支持并发场景中的线程安全。
<!--写法-->
<bean id="mySingletonBean" class="com.example.MyClass" scope="singleton"/>
2.原型模式(prototype)
每次请求bean时,spring容器都会创建一个新的实例。
优点:支持并发场景,每个线程都可以拥有自己的bean实例。
缺点:性能较多,因为需要频繁创建和销毁实例。
<!--写法-->
<bean id="myPrototypeBean" class="com.example.MyClass" scope="prototype"/>
3.请求模式(request)
每次HTTP请求都会创建一下新的bean实例。仅适用于web应用程序。
优点:适用于web应用,每个请求都有自己的bean状态。
缺点:不适用于非web应用。
<!--写法-->
<bean id="myRequestBean" class="com.example.MyClass" scope="request"/>
4.会话模式(session)
每个HTTP会话都会创建一个新的bean实例。仅适用于web应用程序。
优点:适用于需要维护会话状态的应用。
缺点:不适用于非web应用。
<!--写法-->
<bean id="mySessionBean" class="com.example.MyClass" scope="session"/>
5.应用模式(application)
每个servletContext都会创建一个新的bean实例。仅适用于web应用程序。
优点:适用于整个web应用共享的数据。
缺点:不适用于非web应用
<!--写法-->
<bean id="myApplicationBean" class="com.example.MyClass" scope="application"/>
bean作用域的示例
用于创建实例的类
public class OrderOne {
}
public class OrderTwo {
}
用于定义bean的XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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="orderOne" class="com.shen.springiocxml.scope.OrderOne" scope="singleton"></bean>
<!-- 多实例-->
<bean id="orderTwo" class="com.shen.springiocxml.scope.OrderTwo" scope="prototype"></bean>
</beans>
测试类
public class TestOrder {
private static final Logger logger = LoggerFactory.getLogger(TestOrder.class);
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-scope.xml");
//单实例在spring容器初始化时创建
OrderOne orderOne1 = context.getBean("orderOne", OrderOne.class);
logger.info("单实例创建orderOne1对象的地址:{}", orderOne1);
OrderOne orderOne2 = context.getBean("orderOne", OrderOne.class);
logger.info("单实例创建orderOne2对象的地址:{}", orderOne2);
//多实例在获取bean时创建
OrderTwo orderTwo1 = context.getBean("orderTwo", OrderTwo.class);
logger.info("多实例创建orderTwo1对象的地址:{}", orderTwo1);
OrderTwo orderTwo2 = context.getBean("orderTwo", OrderTwo.class);
logger.info("多实例创建orderTwo2对象的地址:{}", orderTwo2);
}
}
bean的生命周期
在spring框架中,bean的生命周期指的式一个bean从创建到销毁的整个过程。spring容器负责管理bean的生命周期。bean的生命周期主要有以下阶段。
1.实例化
spring容器根据bean定义创建bean的实例。可以通过构造器或者工厂方法进行实例化。
2.属性赋值
容器为bean的属性注入值(依赖注入),这些值可以通过xml配置、注解或java配置类指定。
3.初始化前
在bean属性赋值完成后,初始化前阶段允许执行一些自定义逻辑。
可以通过实现BeanPostProcess接口的postProcessBeforeInitialization方法来添加自定义逻辑。
4.初始化
容器调用bean的初始化方法。可以通过以下三种方式指定初始化方法:
1.实现InitializingBean接口的afterPropertiesSet方法。
2.在XML配置中使用init-method属性指定自定义初始化方法。
3.使用@PostConstruct注解标记的方法。
5.初始化后
在bean初始化完成后,初始化后阶段允许执行一些自定义逻辑。
可以通过实现BeanPostProcessor接口的postProcessAfterInitialization方法来添加自定义逻辑。
6.使用
bean现在可以被应用程序的其他部分使用。
7.销毁前
在bean销毁前,允许执行一些清理工作。
可以通过实现DestructionAwateBeanPostProcessor接口的postProcessBeforeDestruction方法来 添加自定义逻辑。
8.销毁
容器调用bean的销毁方法。可以通过以下三种方式指定销毁方法:
1.实现DisposableBean接口的destory方法。
2.在XML配置中使用destory-method属性指定自定义销毁方法。
3.使用@PreDestory注解标记的方法。
9.垃圾回收
bean最终被垃圾回收器回收,释放资源。
bean生命周期的示例
用于创建实例的类
public class UserDemo {
private static final Logger logger = LoggerFactory.getLogger(UserDemo.class);
private String name;
public UserDemo() {
logger.info("1.调用无参数构造,创建bean对象");
}
public void initMethod() {
logger.info("4.bean对象初始化,调用指定的初始化");
}
public void destroyMethod() {
logger.info("7.bean对象销毁,调用指定销毁的方法");
}
public String getName() {
return name;
}
public void setName(String name) {
logger.info("2.对bean对象设置属性值");
this.name = name;
}
@Override
public String toString() {
return "UserDemo{" +
"name='" + name + '\'' +
'}';
}
}
BeanPostProcessor接口的实现类
public class MyBeanPost implements BeanPostProcessor {
private static final Logger logger = LoggerFactory.getLogger(MyBeanPost.class);
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
logger.info("3.bean后置处理器,初始化之前执行");
logger.info("bean name:[{}],bean is [{}]", beanName, bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
logger.info("5.bean后置处理器,初始化之后执行");
logger.info("bean name:[{}],bean is [{}]", beanName, bean);
return bean;
}
}
定义bean的XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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="userDemo" class="com.shen.springiocxml.life.UserDemo"
scope="singleton" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="张三"></property>
</bean>
<!-- 后置处理器放入ioc容器才能生效-->
<bean id="myBeanPost" class="com.shen.springiocxml.life.MyBeanPost"></bean>
</beans>
测试类
public class TestUserDemo {
private static final Logger logger = LoggerFactory.getLogger(TestUserDemo.class);
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-life.xml");
UserDemo userDemo = context.getBean("userDemo", UserDemo.class);
logger.info("6.对象已创建,可以被使用");
logger.info("{}", userDemo);
context.close();
}
}
Spring中的FactoryBean接口
在Spring框架中,FactoryBean是一个接口,用于实现复杂的初始化逻辑,它允许你创建一个可以生成其他对象的工厂。FactoryBean本身是一个bean,但它的主要目的是生产其他bean。这种设计模式在spring中很有用,尤其是在需要创建复杂对象或者需要控制对象创建过程的情况。
在整合Mybatis时,Spring就是通过FactoryBean机制来创建SqlSessionFactory对象的。
FactoryBean接口定义了三个方法:
1.T getObject() throws Exception;
这个方法返回由FactoryBean创建的实例。这是客户端代码通过Spring容器获取到的实际对象。
2.Class<?> getObjectType();
返回由getObject()方法返回的对象类型。
3.default boolean isSingleton() {
return true;
}
返回由getObject()方法返回的对象是否是单例,默认是单例返回true
使用FactoryBean的过程:
1.
使用FactoryBean的过程
- 定义FactoryBean实现:创建一个类实现FactoryBean接口
- 配置Spring容器: 在Spring配置文件中声明FactoryBean接口实现类
- 获取对象: 通过Spring容器获取FactoryBean接口实现类要生成的目标对象,获取FactoryBean实现类的bean时会自动调用getObject()方法,获取目标bean
FactoryBean的示例
FactoryBean接口实现类要生成的bean的类
public class UserDemoTwo {
}
FactoryBean接口实现类
public class MyFactoryBean implements FactoryBean<UserDemoTwo> {
@Override
public UserDemoTwo getObject() throws Exception {
return new UserDemoTwo();
}
@Override
public Class<?> getObjectType() {
return UserDemoTwo.class;
}
}
定义bean的XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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="userDemoTwo" class="com.shen.springiocxml.factorybean.MyFactoryBean"></bean>
</beans>
测试类
public class TestUserDemoTwo {
private static final Logger logger = LoggerFactory.getLogger(TestUserDemoTwo.class);
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-factorybean.xml");
UserDemoTwo testUserDemoTwo = (UserDemoTwo) context.getBean("userDemoTwo");
logger.info("{}", testUserDemoTwo);
}
}
基于XML的自动装配
自动装配的概念
Spring中的自动装配是指Spring容器能够自动识别对象之间的依赖关系,并自动建立这些依赖关系的过程。这种机制可以大大简化bean的配置,使得开发者不再需要显式(ref元素)地在配置文件中指定bean之间的依赖关系。
自动装配的方式
Spring提供了几种自动装配的方式,可以通过autowire属性来指定:
1.no:默认方式,不进行装配,依赖必须通过ref元素来定义。
2.byName:根据bean的名称进行自动装配。Spring会查找与依赖属性名相同的bean(id与属性名相同)进行注入。
3.byType: 根据bean的类型进行自动装配。Spring会查找与依赖属性类型相同的bean(class与属性类型相同)进行注入。
4.constructor:类似于byType,但它是针对构造函数的参数进行自动装配。Spring会查找与构造函数参数类型相同的bean进行注入。
5.autodetect:Spring会尝试通过构造函数自动装配(constructor),如果不可用,则会按类型自动装配(byType)
自动装配的优缺点
优点:
- 减少了配置文件中的XML配置,使得配置更加简洁。
- 提高开发效率,减少了手动配置依赖的工作量。
缺点:
- 当容器中存在多个相同类型的bean时,可能会出现自动装配失败的情况。
- 对于复杂依赖关系,自动装配不够灵活。
基于xml的自动装配示例
控制层接口类
/**
* 用户控制层接口类
*/
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void invokeServiceMethod() {
logger.info("控制层对象调用service方法");
//常规调用方式
// UserService userService = new UserServiceImpl();
// userService.invokeDaoMethod();
userService.invokeDaoMethod();
}
业务接口与业务接口实现类
/**
* 用户业务类
*/
public interface UserService {
void invokeDaoMethod();
}
/**
* 用户业务实现类
*/
public class UserServiceImpl implements UserService {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void invokeDaoMethod() {
logger.info("业务层对象调用Dao方法");
//常规调用方式
// UserDao userDao = new UserDaoImpl();
// userDao.printHello();
userDao.printHello();
}
}
数据传输接口与数据传输接口实现类
public interface UserDao {
void printHello();
}
/**
* 数据传输实现类
*/
public class UserDaoImpl implements UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);
@Override
public void printHello() {
logger.info("数据传输对象打印hello");
logger.info("hello");
}
}
定义bean的xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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="userController" class="com.shen.springiocxml.auto.controller.UserController" autowire="byType"></bean>-->
<!-- <bean id="userService" class="com.shen.springiocxml.auto.service.impl.UserServiceImpl" autowire="byType"></bean>-->
<!-- <bean id="userDao" class="com.shen.springiocxml.auto.dao.impl.UserDaoImpl"></bean>-->
<!-- 根据名称自动装配,需要保证id名与实例名一致-->
<bean id="userController" class="com.shen.springiocxml.auto.controller.UserController" autowire="byName"></bean>
<bean id="userService" class="com.shen.springiocxml.auto.service.impl.UserServiceImpl" autowire="byName"></bean>
<bean id="userDao" class="com.shen.springiocxml.auto.dao.impl.UserDaoImpl"></bean>
</beans>
测试类
/**
* 测试类
*/
public class TestUser {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-auto.xml");
UserController userController = context.getBean("userController", UserController.class);
userController.invokeServiceMethod();
}
}
基于注解管理bean
Java注解和Spring注解
Java注解
Java注解的概念
Java注解是使用@interface关键字定义的。
注解可以包含元素(类似于方法),这些元素可以有默认值。
注解可以用于修饰类、方法、字段、参数等。
Java中常见的内置注解
- @Override: 表示当前方法覆盖了父类的方法。
- @Deprecated:表示当前元素已过时,不推荐使用。
- @SuppressWarnings:抑制编译器警告。
Java中的自定义注解
开发者可以自定义注解,用于特定场景或目的。
自定义注解可以包含元注解,如@Retention(保留策略)、@Target(目标元素类型)等。
示例
public @interface MyAnnotation {
String value() default "default";
}
@MyAnnotation(value = "example")
public class MyClass {
// ...
}
Java注解的应用场景
- 提供元数据,如作者、版本等。
- 编译时检查,如@Override。
- 运行时处理,如通过反射读取注解信息。
Spring注解
Spring注解的概念
Spring注解时Spring框架提供的,基于Java注解实现的。
这些注解用于简化Spring应用的配置,如依赖注入,组件扫描,事务管理,切面编程等。
常见的Spring注解
- 依赖注入:@Autowired,@Value
- 组件扫描:@ComponentScan
- 定义bean:@Component,@Service,@Repository,@Controller
- 事务管理:@Transactional
- 切面编程:@Aspect,@Pointcut,@Before,@After,@AfterReturning,@AfterThrowing,@Around。
应用场景
- 简化Spring配置,减少XML配置文件的使用。
- 提供声明式事务管理。
- 实现面向切面编程(AOP)。
基于注解的自动装配
通过注解实现自动装配的步骤
- 引入Spring相关依赖
- 开启组件扫描
- 使用注解定义bean
- 依赖注入(两种注解注入方式)
引入Spring相关依赖
<!--spring context依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.11</version>
</dependency>
<!--log4j2的依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
<!-- @Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【**如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。**】-->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
开启组件扫描
Spring默认不使用注解装配bean,因此需要在XML配置文件中引入context命名空间,通过context:component-scan元素开启bean的自动扫描功能。开启之后,Spring会自动从指定的包中扫描,对使用定义bean注解(@Component,@Service,@Repository,@Controller)的类自动注册为bean。
组件扫描示例
<?xml version="1.0" encoding="UTF-8"?>
<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:component-scan base-package="com.shen.springiocannotation"></context:component-scan>
</beans>
使用注解定义bean
Spring提供了多个注解,这些注解功能相同,均作用于类上,将该类自动注册为bean,只是应用的场景不同。
注解 | 说明 |
---|---|
@Component | 通用注解,适用于任何层次,用于标记一个类为spring容器中的bean |
@Controller | 作用于控制层的注解,用于标记一个控制层的类为spring容器中的bean |
@Service | 作用于业务层的注解,用于标记一个业务层的类为spring容器中的bean |
@Repository | 作用于数据访问层的注解,用于标记一个数据访问层的类为spring容器中的bean |
依赖注入
注解形式的依赖分为两种方式:使用Spring注解@Autowired或者使用Java注解@Resource。
@Autowired注解与@Resource注解的区别
- 来源和规范
@Autowired: 是Spring框架自带的注解,遵循Spring的依赖注入规范。
@Resource: 是Java JSR-250规范中的注解,由Java官方提供,是一种通用的依赖注入方式。
- 注入方式
@Autowired: 默认按照类型进行自动注入,如果找到多个相同类型的bean,会根据名称进行匹配。可以使用@Qualifier注解来指定要注入的bean的名称。
@Resource: 默认按照名称进行注入,名称由name属性,如果未指定name,则使用字段名或方法名作为默认名称。如果找不到匹配的名称,则按照类型进行注入。
- 属性支持
@Autowired: 可以用于构造方法、属性、方法参数、配置类中的@Bean方法参数。
@Resource: 通常用于属性和方法的注入,不常用于构造函数注入。
- 循环依赖
@Autowired: Spring支持通过构造函数的循环依赖,因为它在创建bean的早期阶段就解决了依赖关系
@Resource: 由于它是通过JNDI(Java Naming and Directory Interface)查找来实现注入的,所以不支持构造函数的循环依赖。
使用Spring注解@Autowired进行依赖注入
@Autowired默认根据类型进行装配。
@Autowired注解源码
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
/**
* Declares whether the annotated dependency is required.
* <p>Defaults to {@code true}.
*/
boolean required() default true;
}
@Autowired注解进行依赖注入的方式
- 构造方法上注入
- set方法上注入
- 形参上注入
- 属性上注入
- 类中只有一个构造方法时不需要注解
- 与@Qualifier注解结合通过名称进行注入
示例
控制层
@Controller
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
//注入Service
//第一种方式 属性注入-根据属性类型找到对应对象,完成注入 不用构建set方法
// @Autowired
// private UserService userService;
// //第二种方式set方法注入
// private UserService userService;
//
// @Autowired
// public void setUserService(UserService userService) {
// this.userService = userService;
// }
//// 第三种方式 构造方法注入
// private UserService userService;
//
// @Autowired
// public UserController(UserService userService) {
// this.userService = userService;
// }
// //第四种方式 形参上注入
// private UserService userService;
//
// public UserController(@Autowired UserService userService) {
// this.userService = userService;
// }
// 第五种方式 只有一个构造方法,不需要注解
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
public void printHello() {
logger.info("控制层调用打印方法");
userService.printHello();
}
}
业务层
public interface UserService {
void printHello();
}
@Service
public class UserServiceImpl implements UserService {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
// @Autowired
// private UserDao userDao;
//第六种方式 @Autowired和@Qualifier注解 根据名称自动装配
@Autowired
@Qualifier(value = "userOneDaoImpl")
private UserDao userDao;
@Override
public void printHello() {
logger.info("业务层调用打印方法");
userDao.printHello();
}
}
数据访问层
public interface UserDao {
void printHello();
}
@Repository
public class UserOneDaoImpl implements UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserOneDaoImpl.class);
@Override
public void printHello() {
logger.info("one");
}
}
@Repository
public class UserDaoImpl implements UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);
@Override
public void printHello() {
logger.info("数据传输层打印hello");
logger.info("hello");
}
}
开启组件扫描的XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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:component-scan base-package="com.shen.springiocannotation"></context:component-scan>
</beans>
测试类
public class TestUserController {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
UserController userController = context.getBean(UserController.class);
userController.printHello();
}
}
使用Java注解@Resource进行依赖注入
@Resource默认根据名称进行装配,当注解没有指定name时会把属性名当作name,如果根据name找不到则会根据类型装配。
@Resource注解源码
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
@Repeatable(Resources.class)
public @interface Resource {
/**
* The JNDI name of the resource. For field annotations,
* the default is the field name. For method annotations,
* the default is the JavaBeans property name corresponding
* to the method. For class annotations, there is no default
* and this must be specified.
*/
String name() default "";
String lookup() default "";
Class<?> type() default java.lang.Object.class;
enum AuthenticationType {
CONTAINER,
APPLICATION
}
AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
boolean shareable() default true;
String mappedName() default "";
String description() default "";
}
示例
控制层
@Controller("myUserController")
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
// //根据名称进行注入
// @Resource(name = "myUserService")
// private UserService userService;
//既没有指定名字,属性名也不一致的情况下通过类型进行注入
@Resource
private UserService userService;
public void printHello() {
logger.info("控制层调用打印方法");
userService.printHello();
}
}
业务层
public interface UserService {
void printHello();
}
@Service("myUserService")
public class UserServiceImpl implements UserService {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
//没有指定名称根据属性名进行注入
@Resource
private UserDao myUserDao;
@Override
public void printHello() {
logger.info("业务层调用打印方法");
myUserDao.printHello();
}
}
数据访问层
public interface UserDao {
void printHello();
}
@Repository("myUserDao")
public class UserDaoImpl implements UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);
@Override
public void printHello() {
logger.info("数据传输层打印hello");
logger.info("hello");
}
}
开启组件扫描的XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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:component-scan base-package="com.shen.springiocannotation"></context:component-scan>
</beans>
测试类
public class TestMyUserController {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
UserController userController = context.getBean(UserController.class);
userController.printHello();
}
}
替代XML配置文件的配置类
示例
原XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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:component-scan base-package="com.shen.springiocannotation"></context:component-scan>
</beans>
替代的配置类
@Configuration
@ComponentScan("com.shen.springiocannotation")
public class Spring6Config {
}
启动类加载Spring容器
public class TestUserControllerByAnnotation {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
UserController userController = context.getBean(UserController.class);
userController.printHello();
}
}
手写Spring容器
Spring框架中的容器是基于Java反射机制实现的。
Java反射
java反射机制可以参考我之前写的博客。
Spring容器实现过程
- 创建测试类:新增业务层和数据访问层的类用于创建相应类的对象。
- 自定义注解: 新增@Bean注解用于创建对象,@Di注解用于属性注入。
- 创建bean容器接口,定义方法,用于返回对象。
- 实现bean容器接口方法:根据包加载bean,返回对象(扫描包中的类信息,如果类中有@Bean注解,则将该类实例化)。
示例
自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}
业务层
public interface UserService {
void printService();
}
@Bean
public class UserServiceImpl implements UserService {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Di
private UserDao userDao;
@Override
public void printService() {
logger.info("print service ==============");
userDao.printDao();
}
}
数据访问层
public interface UserDao {
void printDao();
}
@Bean
public class UserDaoImpl implements UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);
@Override
public void printDao() {
logger.info("dao print ==========");
}
}
bean容器
public interface ApplicationContext {
Object getBean(Class<?> cls);
}
public class AnnoationApplicationContext implements ApplicationContext {
private final static Logger logger = LoggerFactory.getLogger(AnnoationApplicationContext.class);
//创建map集合,存入bean对象
private final Map<Class<?>, Object> beanFactory = new HashMap<>();
private String rootPath;
@Override
public Object getBean(Class<?> cls) {
return beanFactory.get(cls);
}
//创建有参构造,传参为包路径,设置包扫描规则
//当前包以及其子包,哪个类有@Bean注解,把这个类通过反射实例化
public AnnoationApplicationContext(String basePackagePath) {
try {
//1.把.替换成\
String packagePath = basePackagePath.replaceAll("\\.", "\\\\");
//2.获取包的绝对路径
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
rootPath = filePath.substring(0, filePath.length() - packagePath.length());
loadBean(new File(filePath));
logger.info("完成{}包扫描", basePackagePath);
}
} catch (Exception e) {
logger.info("扫描包异常:", e);
}
}
//包扫描方法
private void loadBean(File file) throws Exception {
//1. 判断当前是否为文件夹
if (file.isDirectory()) {
//2. 获取文件夹中所有内容
File[] childFiles = file.listFiles();
//3. 判断文件夹里面是否为空,不为空则遍历所有内容
if (childFiles == null || childFiles.length == 0) {
return;
}
for (File child : childFiles) {
//4.1 遍历得到每个File对象,如果文件对象是文件夹则递归遍历
if (child.isDirectory()) {
//递归
loadBean(child);
} else {
//4.2 如果File对象是文件,则获取文件的绝对路径
String filePath = child.getAbsolutePath();
//4.3 判断当前文件路径是否带有.class
if (filePath.contains(".class")) {
//4.4 如果带有class则获取全类名
String pathWithClass = filePath.substring(rootPath.length() - 1).replaceAll("\\\\", ".");
String className = pathWithClass.replace(".class", "");
//4.5 通过全类名获取Class类对象
Class<?> cls = Class.forName(className);
///4.6 判断是否为接口
if (!cls.isInterface()) {
Bean annotation = cls.getAnnotation(Bean.class);
//4.7 判断是否有Bean注解
if (annotation != null) {
Constructor<?> constructor = cls.getConstructor();
//4.8 实例化对象
Object object = constructor.newInstance();
//4.9 如果该类有接口,则取该类继承的一个接口作为key存入map集合中,反之则将该类作为key存入map集合中,value为实例
if (cls.getInterfaces().length > 0) {
beanFactory.put(cls.getInterfaces()[0], object);
} else {
beanFactory.put(cls, object);
}
}
}
}
}
}
}
//属性注入
loadDi();
}
private void loadDi() throws IllegalAccessException {
//实例化对象在beanFactory中
//1. 遍历beanFactory的map集合
Set<Map.Entry<Class<?>, Object>> entries = beanFactory.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
//2. 获取map集合中每个对象,并且获取每个对象的所有属性
Object object = entry.getValue();
//获取Class类对象
Class<?> cls = object.getClass();
//获取所有属性
Field[] fields = cls.getDeclaredFields();
//3. 遍历属性数组
for (Field field : fields) {
Di annotation = field.getAnnotation(Di.class);
//4. 判断属性上是否有@Di注解
if (annotation != null) {
//暴破,允许对私有属性赋值
field.setAccessible(true);
//5. 如果有@Di注解,把此属性的对象进行注入
field.set(object, beanFactory.get(field.getType()));
}
}
}
}
}
测试类
public class TestUser {
private static final Logger logger = LoggerFactory.getLogger(TestUser.class);
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnoationApplicationContext("com.shen.shouxieioc");
UserService userService = (UserService) applicationContext.getBean(UserService.class);
userService.printService();
}
}
参考资料
https://docs.spring.io/spring-framework/reference/core/beans.html
https://www.bilibili.com/video/BV1kR4y1b7Qc/
标签:容器,String,Spring,void,public,bean,logger,IoC,class From: https://www.cnblogs.com/shenStudy/p/18666989