Spring——容器:IoC

容器:IoC

IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合更优良的程序。

Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。

IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。

IoC容器

控制反转(IoC)

  • 控制反转是一种设计思想,不是一种技术
  • 控制反转是为了使得程序松耦合,提高程序的扩展力
  • 控制反转反转的是什么
    • 将对象的实例化、初始化都交给第三方容器负责
    • 将对象与对象之间依赖关系的维护交给第三方容器负责
  • 控制反转这种思想如何实现?
    • DI(Dependency Injection)依赖注入

依赖注入

DI(Dependency Injection):依赖注入,依赖注入实现了控制反转的思想。

依赖注入:

  • 指Spring创建对象的过程中,将对象依赖属性通过配置进行注入

注入可以简单理解为赋值

依赖注入常见的实现方式包括两种:

  • 第一种:set注入
  • 第二种:构造注入

所以结论是:IoC 就是一种控制反转的思想, 而 DI 是对 IoC 的一种具体实现

Bean管理说的是:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。

IoC容器在Spring的实现

Spring 的 IoC 容器就是 IoC思想的一个落地的产品实现。IoC容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建IoC 容器。Spring 提供了IoC 容器的两种实现方式:

①BeanFactory

这是 IoC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

②ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。

③ApplicationContext的主要实现类

类型名简介
ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContextApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
WebApplicationContext专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

基于XML管理bean

搭建子模块spring-ioc-xml

①搭建模块

搭建方式如:spring-ioc-xml

②引入配置文件

引入spring-ioc-xml模块配置文件:beans.xml、log4j2.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"></beans>
<?xml version="1.0" encoding="UTF-8"?>
<configuration><loggers><!--level指定日志级别,从低到高的优先级:TRACE < DEBUG < INFO < WARN < ERROR < FATALtrace:追踪,是最低的日志级别,相当于追踪程序的执行debug:调试,一般在开发中,都将其设置为最低的日志级别info:信息,输出重要的信息,使用较多warn:警告,输出警告的信息error:错误,输出错误信息fatal:严重错误--><root level="DEBUG"><appender-ref ref="spring6log"/><appender-ref ref="RollingFile"/><appender-ref ref="log"/></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><!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用--><File name="log" fileName="d:/spring6_log/test.log" append="false"><PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/></File><!-- 这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--><RollingFile name="RollingFile" fileName="d:/spring6_log/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="20"/></RollingFile></appenders>
</configuration>
③添加依赖
<dependencies><!--spring context依赖--><!--当你引入Spring Context依赖以后,表示将Spring的基础依赖引入了--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.2</version></dependency><!--junit--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.8.1</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-slf4j-impl</artifactId><version>2.19.0</version></dependency>
</dependencies>
④引入java类
package com.ling.spring6.iocxml;public class User {private String name;private int age;private void run() {System.out.println("run...");}
}

实验一:获取bean

①方式一:根据id获取
<!--user对象创建-->
<bean id="user" class="com.ling.spring6.iocxml.User"></bean>
public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");// 根据id获取bean对象User user = (User) context.getBean("user");System.out.println("根据id获取bean对象:" + user);
}

运行结果

②方式二:根据类型获取
@Test
public void testUser(){ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");// 根据类型获取bean对象User user = context.getBean(User.class);System.out.println("根据类型获取bean对象:" + user);
}

运行结果

③方式三:根据id和类型
@Test
public void testUser2(){ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");User user = context.getBean("user", User.class);System.out.println("根据id和类型获取bean对象:" + user);
}

运行结果

④注意的地方

当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个

如果配置文件中配置了两个相同类型的bean

<!--user对象创建-->
<bean id="user" class="com.ling.spring6.iocxml.User"></bean>
<bean id="user1" class="com.ling.spring6.iocxml.User"></bean>

再执行以下代码就会抛出异常

@Test
public void testUser1(){ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");// 根据类型获取bean对象User user = context.getBean(User.class);System.out.println("根据类型获取bean对象:" + user);
}

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.ling.spring6.iocxml.User' available: expected single matching bean but found 2: user,user1

异常显示:期望匹配的是一个单实例的bean,但找到两个相同类型的bean

如果在配置文件当中确实有相同类型的bean,那就采用另外两种方式来获取bean(根据id获取、根据id和类型获取)

⑤扩展知识
如果组件类实现了接口,根据接口类型可以获取 bean 吗?能否实现以下效果?

UserDao

package com.ling.spring6.iocxml.bean;public interface UserDao {public void run();
}

UserDaoImpl

package com.ling.spring6.iocxml.bean;public class UserDaoImpl implements UserDao{@Overridepublic void run() {System.out.println("run....");}
}

bean.xml

<!--一个接口实现类获取bean-->
<bean id="userDaoImpl" class="com.ling.spring6.iocxml.bean.UserDaoImpl"></bean>

TestUserDao

package com.ling.spring6.iocxml.bean;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestUserDao {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");// 根据类型获取接口对应beanUserDao userDao = context.getBean(UserDao.class);userDao.run();}
}

运行结果

表明确实可以通过接口类型获取bean

如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?

 UserDao

package com.ling.spring6.iocxml.bean;public interface UserDao {public void run();
}

UserDaoImpl

package com.ling.spring6.iocxml.bean;public class UserDaoImpl implements UserDao{@Overridepublic void run() {System.out.println("run....");}
}

PersonUserDaoImpl

package com.ling.spring6.iocxml.bean;public class PersonUserDaoImpl implements UserDao{@Overridepublic void run() {System.out.println("PersonUserDaoImpl run...");}
}

bean.xml

<bean id="userDaoImpl" class="com.ling.spring6.iocxml.bean.UserDaoImpl"></bean>
<bean id="personUserDaoImpl" class="com.ling.spring6.iocxml.bean.PersonUserDaoImpl"></bean>

TestUserDao

package com.ling.spring6.iocxml.bean;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestUserDao {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");// 根据类型获取接口对应beanUserDao userDao = context.getBean(UserDao.class);userDao.run();}
}

运行结果

Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.ling.spring6.iocxml.bean.UserDao' available: expected single matching bean but found 2: userDaoImpl,personUserDaoImpl

表明如果一个接口有多个实现类,那么就不能通过接口类型获取任何一个bean

结论

根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。

java中,instanceof运算符用于判断前面的对象是否是后面的类,或其子类、实现类的实例。如果是返回true,否则返回false。也就是说:用instanceof关键字做判断时, instanceof 操作符的左右操作必须有继承或实现关系

实验二:依赖注入之setter注入

①创建图书类Book
package com.ling.spring6.iocxml.di;public class Book {private String bookName;private String author;public Book() {}public String getBookName() {return bookName;}public void setBookName(String bookName) {this.bookName = bookName;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}}
②配置bean时为属性赋值

bean-di.xml 

<!-- set方法完成注入-->
<bean id="book" class="com.ling.spring6.iocxml.di.Book"><!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 --><!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) --><!-- value属性:指定属性值 --><property name="bookName" value="C++"/><property name="author" value="王五"/>
</bean>
③测试
@Test
public void testBook() {ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");Book book = context.getBean("book", Book.class);System.out.println(book);
}
运行结果

实验三:依赖注入之构造器注入

①在Book类中添加有参构造
// 有参构造
public Book(String bookName, String author) {System.out.println("有参数构造执行了....");this.bookName = bookName;this.author = author;
}
②配置bean

bean-di.xml

<!--构造器完成注入-->
<bean id="bookConstructor" class="com.ling.spring6.iocxml.di.Book"><constructor-arg name="bookName" value="Python"/><constructor-arg name="author" value="赵六"/>
</bean>

注意:

constructor-arg标签还有两个属性可以进一步描述构造器参数:

  • index属性:指定参数所在位置的索引(从0开始)
  • name属性:指定参数名
③测试
@Test
public void testBook2() {ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");Book book = context.getBean("bookConstructor", Book.class);System.out.println(book);
}
运行结果

实验四:特殊值处理

①字面量赋值

什么是字面量?

int a = 10;

声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a的时候,我们实际上拿到的值是10。

而如果a是带引号的:'a',那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面量。所以字面量没有引申含义,就是我们看到的这个数据本身。

<!--使用value属性给bean的属性赋值时,Spring会把value属性的值看作字面量-->
<property name="bookName" value="C++"/>
②null值
<property name="others"><null/>
</property>

以上的写法才是给others赋空值

注意:

<property name="others" value="null"></property>

以上写法,为name所赋的值是字符串null

测试
@Test
public void testBook() {ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");Book book = context.getBean("book", Book.class);System.out.println(book);
}
运行结果

③xml实体

由于我们是在xml文件中配置bean的定义信息,而xml文件有些特殊符号

例如:小于号在XML文档中用来定义标签的开始,不能随便使用

解决办法一:使用xml实体来代替

<property name="others" value="a &lt; b"/>
测试
@Test
public void testBook() {ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");Book book = context.getBean("book", Book.class);System.out.println(book);
}
运行结果

④CDATA节

如果不想使用xml实体来代替xml文件中的特殊符号

解决办法二:使用CDATA节

<property name="others"><!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 --><!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 --><!-- 所以CDATA节中写什么符号都随意 --><value><![CDATA[a < b]]></value>
</property>
测试
@Test
public void testBook() {ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");Book book = context.getBean("book", Book.class);System.out.println(book);
}
运行结果

实验五:为对象类型属性赋值

①创建部门类

package com.ling.spring6.iocxml.ditest;// 部门
public class Department {private String dname;public String getDname() {return dname;}public void setDname(String dname) {this.dname = dname;}// 添加这个方法是为了方便演示引用外部bean能否实现public void info() {System.out.println("部门名称:" + dname);}
}

②修改Employee类

package com.ling.spring6.iocxml.ditest;// 员工
public class Employee {// 对象类型的属性:员工属于某个部门private Department dept;// 员工名称private String ename;// 员工年龄private int age;public Department getDept() {return dept;}public void setDept(Department dept) {this.dept = dept;}public String getEname() {return ename;}public void setEname(String ename) {this.ename = ename;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public void work(){System.out.println("employee正在工作...他的名字是" + ename + ",年龄是" + age);dept.info();}
}
方式一:引用外部bean

配置Department的bean,并为Employee中的department属性赋值

<!--
第一张方式:引入外部bean1、创建两个类对象:department和employee2、在employee的bean标签里面,使用property引入dept的bean
-->
<bean id="department" class="com.ling.spring6.iocxml.ditest.Department"><property name="dname" value="22计科1班"/>
</bean>
<bean id="employee" class="com.ling.spring6.iocxml.ditest.Employee"><!--对象类型属性注入private Department dept;--><property name="dept" ref="department"/><!--普通类型属性注入--><property name="ename" value="张三"/><property name="age" value="20"/>
</bean>

错误演示

<bean id="employee" class="com.ling.spring6.iocxml.ditest.Employee"><!--对象类型属性注入private Department dept;--><property name="dept" value="department"/><!--普通类型属性注入--><property name="ename" value="张三"/><property name="age" value="20"/>
</bean>

如果错把ref属性写成了value属性,会抛出异常: Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.ling.spring6.iocxml.Department' for property 'dept': no matching editors or conversion strategy found

意思是不能把String类型转换成我们要的Department类型,说明我们使用value属性时,Spring只把这个属性看做一个普通的字符串,不会认为这是一个bean的id,更不会根据它去找到bean来赋值

并且 ref 属性的值是我们配置的外部 bean 的 id 属性的值

方式二:内部bean
<!--第二种方式:内部bean注入-->
<bean id="employee2" class="com.ling.spring6.iocxml.ditest.Employee"><!--内部bean--><property name="dept"><bean id="department2" class="com.ling.spring6.iocxml.ditest.Department"><property name="dname" value="22计科2班"/></bean></property><!--普通类型属性注入--><property name="ename" value="李四"/><property name="age" value="20"/>
</bean>
方式三:级联属性赋值
<!--第三种方式:级联赋值-->
<bean id="department3" class="com.ling.spring6.iocxml.ditest.Department"><property name="dname" value="22计科3班"/>
</bean>
<bean id="employee3" class="com.ling.spring6.iocxml.ditest.Employee"><property name="dept" ref="department3"/><property name="dept.dname" value="22计科4班"/><property name="ename" value="王五"/><property name="age" value="20"/>
</bean>

注意:级联赋值重新设定的值会覆盖原先的值(如果外部bean有进行赋值的话)

实验六:为数组类型属性赋值

①修改Employee类

在Employee类中添加以下代码:

// 员工爱好
private String[] hobby;public String[] getHobby() {return hobby;
}public void setHobby(String[] hobby) {this.hobby = hobby;
}
②配置bean
<bean id="department" class="com.ling.spring6.iocxml.ditest.Department"><property name="dname" value="22计科1班"/>
</bean>
<bean id="employee" class="com.ling.spring6.iocxml.ditest.Employee"><!--对象属性注入--><property name="dept" ref="department"/><!--普通属性注入--><property name="ename" value="张三"/><property name="age" value="20"/><!--数组属性赋值--><property name="hobby"><array><value>篮球</value><value>足球</value><value>乒乓球</value></array></property>
</bean>

实验七:为集合类型属性赋值

①为List集合类型属性赋值

在Department类中添加以下代码

// 一个部门有多个员工
private List<Employee> employeeList;public List<Employee> getEmployeeList() {return employeeList;
}public void setEmployeeList(List<Employee> employeeList) {this.employeeList = employeeList;
}

配置bean

<bean id="employeeOne" class="com.ling.spring6.iocxml.ditest.Employee"><property name="ename" value="张三"/><property name="age" value="20"/>
</bean>
<bean id="employeeTwo" class="com.ling.spring6.iocxml.ditest.Employee"><property name="ename" value="李四"/><property name="age" value="30"/>
</bean><bean id="department" class="com.ling.spring6.iocxml.ditest.Department"><property name="dname" value="22计科1班"/><property name="employeeList"><list><ref bean="employeeOne"/><ref bean="employeeTwo"/></list></property>
</bean>

若为Set集合类型属性赋值,只需要将其中的list标签改为set标签即可

②为Map集合类型属性赋值

创建教师类Teacher

package com.ling.spring6.iocxml.dimap;public class Teacher {private String teacherId;private String teacherName;public String getTeacherId() {return teacherId;}public void setTeacherId(String teacherId) {this.teacherId = teacherId;}public String getTeacherName() {return teacherName;}public void setTeacherName(String teacherName) {this.teacherName = teacherName;}@Overridepublic String toString() {return "Teacher{" +"teacherId='" + teacherId + '\'' +", teacherName='" + teacherName + '\'' +'}';}
}

创建Student类

package com.ling.spring6.iocxml.dimap;import java.util.Map;public class Student {// 一个学生对应多个老师private Map<String, Teacher> teacherMap;private String sid;private String sname;public Map<String, Teacher> getTeacherMap() {return teacherMap;}public void setTeacherMap(Map<String, Teacher> teacherMap) {this.teacherMap = teacherMap;}public String getSid() {return sid;}public void setSid(String sid) {this.sid = sid;}public String getSname() {return sname;}public void setSname(String sname) {this.sname = sname;}public void run() {System.out.println("学生编号:" + sid + ",学生姓名:" + sname);System.out.println(teacherMap);}
}

配置bean

<bean id="teacherOne" class="com.ling.spring6.iocxml.dimap.Teacher"><property name="teacherId" value="100"/><property name="teacherName" value="张三"/>
</bean>
<bean id="teacherTwo" class="com.ling.spring6.iocxml.dimap.Teacher"><property name="teacherId" value="200"/><property name="teacherName" value="李四"/>
</bean>
<bean id="student" class="com.ling.spring6.iocxml.dimap.Student"><!--普通类型属性注入--><property name="sid" value="001"/><property name="sname" value="小学生"/><!--Map类型属性注入--><property name="teacherMap"><map><entry><key><value>10000</value></key><ref bean="teacherOne"></ref></entry><entry><key><value>20000</value></key><ref bean="teacherTwo"></ref></entry></map></property>
</bean>
③引用集合类型的bean
不用 util : 集合 的写法
<?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、创建三个对象2、注入普通类型属性3、使用util:类型 定义4、在学生bean引入util:类型定义bean,完成list、map类型属性注入--><bean id="lessonOne" class="com.ling.spring6.iocxml.dimap.Lesson"><property name="lessonName" value="计网"/></bean><bean id="lessonTwo" class="com.ling.spring6.iocxml.dimap.Lesson"><property name="lessonName" value="计组"/></bean><bean id="teacherOne" class="com.ling.spring6.iocxml.dimap.Teacher"><property name="teacherId" value="100"/><property name="teacherName" value="张三"/></bean><bean id="teacherTwo" class="com.ling.spring6.iocxml.dimap.Teacher"><property name="teacherId" value="200"/><property name="teacherName" value="李四"/></bean><bean id="student" class="com.ling.spring6.iocxml.dimap.Student"><property name="sid" value="001"/><property name="sname" value="小学生"/><property name="lessonList"><list><ref bean="lessonOne"/><ref bean="lessonTwo"/></list></property><property name="teacherMap"><map><entry><key><value>10000</value></key><ref bean="teacherOne"/></entry><entry><key><value>20000</value></key><ref bean="teacherTwo"/></entry></map></property></bean></beans>
使用 util : 集合 的写法
<!--1、创建三个对象2、注入普通类型属性3、使用util:类型 定义4、在学生bean引入util:类型定义bean,完成list、map类型属性注入
-->
<bean id="lessonOne" class="com.ling.spring6.iocxml.dimap.Lesson"><property name="lessonName" value="计网"/>
</bean>
<bean id="lessonTwo" class="com.ling.spring6.iocxml.dimap.Lesson"><property name="lessonName" value="计组"/>
</bean>
<bean id="teacherOne" class="com.ling.spring6.iocxml.dimap.Teacher"><property name="teacherId" value="100"/><property name="teacherName" value="张三"/>
</bean>
<bean id="teacherTwo" class="com.ling.spring6.iocxml.dimap.Teacher"><property name="teacherId" value="200"/><property name="teacherName" value="李四"/>
</bean>
<util:list id="lessonList"><ref bean="lessonOne"/><ref bean="lessonTwo"/>
</util:list>
<util:map id="teacherMap"><entry><key><value>100</value></key><ref bean="teacherOne"/></entry><entry><key><value>200</value></key><ref bean="teacherTwo"/></entry>
</util:map>
<bean id="student" class="com.ling.spring6.iocxml.dimap.Student"><property name="sid" value="001"/><property name="sname" value="小学生"/><property name="lessonList" ref="lessonList"/><property name="teacherMap" ref="teacherMap"/>
</bean>

使用util:list、util:map标签必须引入相应的命名空间

<?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"xsi:schemaLocation="http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">

实验八:p命名空间

引入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.xsdhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
引入p命名空间后,可以通过以下方式为bean的各个属性赋值
<!--p命名空间注入-->
<bean id="studentp" class="com.ling.spring6.iocxml.dimap.Student" p:sid="001" p:sname="初中生" p:lessonList-ref="lessonList" p:teacherMap-ref="teacherMap"/>

实验九:引入外部属性文件

原生代码(不采用引入外部属性文件的形式)
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
①加入依赖
<!--MySQL驱动-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.26</version>
</dependency>
<!--数据源-->
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.15</version>
</dependency>
②创建外部属性文件

jdbc.properties

jdbc.username=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver
③引入属性文件
引入context 名称空间
<?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/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
引入外部属性文件
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>

注意:在使用 context:property-placeholder 元素加载外包配置文件功能前,首先需要在 XML 配置的一级标签 中添加 context 相关的约束。

④配置bean
<!--完成数据库信息的注入-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/><property name="driverClassName" value="${jdbc.driver}"/>
</bean>
⑤测试
@Test
public void demo2() {ApplicationContext context = new ClassPathXmlApplicationContext("bean-jdbc.xml");DruidDataSource dataSource = context.getBean(DruidDataSource.class);System.out.println(dataSource.getUrl());
}

实验十:bean的作用域

①概念

在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:

取值含义创建对象的时机
singleton(默认)在IOC容器中,这个bean的对象始终为单实例IOC容器初始化
prototype这个bean在IOC容器中有多个实例获取bean

如果是在WebApplicationContext环境下还会有另外几个作用域(但不常用):

取值含义
request在一个请求范围内有效
session在一个会话范围内有效
②创建类Orders
package com.ling.spring6.iocxml.scope;public class Orders {
}
③配置单实例bean
<!--通过scope属性配置单实例或者多实例 不写scope属性默认就是单实例-->
<!--singleton:单实例(默认值)-->
<!--prototype:多实例-->
<bean id="orders" class="com.ling.spring6.iocxml.scope.Orders" scope="singleton"/>
④配置多实例bean
<!--通过scope属性配置单实例或者多实例 不写scope属性默认就是单实例-->
<!--singleton:单实例(默认值)-->
<!--prototype:多实例-->
<bean id="orders" class="com.ling.spring6.iocxml.scope.Orders" scope="prototype"/>
⑤测试
package com.ling.spring6.iocxml.scope;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestOrders {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("bean-scope.xml");Orders orders = context.getBean("orders", Orders.class);Orders orders1 = context.getBean("orders", Orders.class);System.out.println(orders==orders1);}
}
单实例结果

多实例结果

并且可以看出多实例比单实例少了一条日志,这是因为单实例创建对象是在 IoC 容器初始化时候创建,而多实例创建对象是在获取 bean 的时候创建

实验十一:bean生命周期

①具体的生命周期过程
  • bean对象创建(调用无参构造器)

  • 给bean对象设置属性

  • bean的后置处理器(初始化之前)

  • bean对象初始化(需在配置bean时指定初始化方法)

  • bean的后置处理器(初始化之后)

  • bean对象就绪可以使用

  • bean对象销毁(需在配置bean时指定销毁方法)

  • IOC容器关闭

②修改类User

package com.ling.spring6.iocxml.lifecycle;public class User {// 无参构造public User() {System.out.println("1、调用无参数构造创建bean对象");}private String name;public String getName() {return name;}public void setName(String name) {System.out.println("2、给bean对象设置属性值");this.name = name;}// 初始化的方法public void initMethod() {System.out.println("4、bean对象初始化,调用指定的初始化方法");}// 销毁的方法public void destroyMethod() {System.out.println("7、bean对象销毁,调用指定的销毁方法");}}

注意其中的initMethod()destroyMethod(),可以通过配置bean指定为初始化销毁的方法

③配置bean
<bean id="user" class="com.ling.spring6.iocxml.lifecycle.User"scope="singleton" init-method="initMethod" destroy-method="destroyMethod"><property name="name" value="ling"/>
</bean>
④测试
package com.ling.spring6.iocxml.lifecycle;import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestUser {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-lifecycle.xml");User user = context.getBean("user", User.class);System.out.println("6、bean对象创建完成,可以使用");System.out.println(user);context.close(); // 销毁}
}
执行结果

⑤bean的后置处理器

bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行

创建bean的后置处理器:
package com.ling.spring6.iocxml.lifecycle;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;public class MyBeanPost implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("3、bean后置处理器,初始化之前执行");System.out.println(beanName + ":" + bean);return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("5、bean后置处理器,初始化之后执行");System.out.println(beanName + ":" + bean);return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}
}
在IOC容器中配置后置处理器:
<!--bean的后置处理器要放在IoC容器中才能生效-->
<bean id="myBeanPost" class="com.ling.spring6.iocxml.lifecycle.MyBeanPost"/>
执行结果

实验十二:FactoeyBean

①简介

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。

/** Copyright 2002-2020 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package org.springframework.beans.factory;import org.springframework.lang.Nullable;/*** Interface to be implemented by objects used within a {@link BeanFactory} which* are themselves factories for individual objects. If a bean implements this* interface, it is used as a factory for an object to expose, not directly as a* bean instance that will be exposed itself.** <p><b>NB: A bean that implements this interface cannot be used as a normal bean.</b>* A FactoryBean is defined in a bean style, but the object exposed for bean* references ({@link #getObject()}) is always the object that it creates.** <p>FactoryBeans can support singletons and prototypes, and can either create* objects lazily on demand or eagerly on startup. The {@link SmartFactoryBean}* interface allows for exposing more fine-grained behavioral metadata.** <p>This interface is heavily used within the framework itself, for example for* the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the* {@link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for* custom components as well; however, this is only common for infrastructure code.** <p><b>{@code FactoryBean} is a programmatic contract. Implementations are not* supposed to rely on annotation-driven injection or other reflective facilities.</b>* {@link #getObjectType()} {@link #getObject()} invocations may arrive early in the* bootstrap process, even ahead of any post-processor setup. If you need access to* other beans, implement {@link BeanFactoryAware} and obtain them programmatically.** <p><b>The container is only responsible for managing the lifecycle of the FactoryBean* instance, not the lifecycle of the objects created by the FactoryBean.</b> Therefore,* a destroy method on an exposed bean object (such as {@link java.io.Closeable#close()}* will <i>not</i> be called automatically. Instead, a FactoryBean should implement* {@link DisposableBean} and delegate any such close call to the underlying object.** <p>Finally, FactoryBean objects participate in the containing BeanFactory's* synchronization of bean creation. There is usually no need for internal* synchronization other than for purposes of lazy initialization within the* FactoryBean itself (or the like).** @author Rod Johnson* @author Juergen Hoeller* @since 08.03.2003* @param <T> the bean type* @see org.springframework.beans.factory.BeanFactory* @see org.springframework.aop.framework.ProxyFactoryBean* @see org.springframework.jndi.JndiObjectFactoryBean*/
public interface FactoryBean<T> {/*** The name of an attribute that can be* {@link org.springframework.core.AttributeAccessor#setAttribute set} on a* {@link org.springframework.beans.factory.config.BeanDefinition} so that* factory beans can signal their object type when it can't be deduced from* the factory bean class.* @since 5.2*/String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";/*** Return an instance (possibly shared or independent) of the object* managed by this factory.* <p>As with a {@link BeanFactory}, this allows support for both the* Singleton and Prototype design pattern.* <p>If this FactoryBean is not fully initialized yet at the time of* the call (for example because it is involved in a circular reference),* throw a corresponding {@link FactoryBeanNotInitializedException}.* <p>As of Spring 2.0, FactoryBeans are allowed to return {@code null}* objects. The factory will consider this as normal value to be used; it* will not throw a FactoryBeanNotInitializedException in this case anymore.* FactoryBean implementations are encouraged to throw* FactoryBeanNotInitializedException themselves now, as appropriate.* @return an instance of the bean (can be {@code null})* @throws Exception in case of creation errors* @see FactoryBeanNotInitializedException*/@NullableT getObject() throws Exception;/*** Return the type of object that this FactoryBean creates,* or {@code null} if not known in advance.* <p>This allows one to check for specific types of beans without* instantiating objects, for example on autowiring.* <p>In the case of implementations that are creating a singleton object,* this method should try to avoid singleton creation as far as possible;* it should rather estimate the type in advance.* For prototypes, returning a meaningful type here is advisable too.* <p>This method can be called <i>before</i> this FactoryBean has* been fully initialized. It must not rely on state created during* initialization; of course, it can still use such state if available.* <p><b>NOTE:</b> Autowiring will simply ignore FactoryBeans that return* {@code null} here. Therefore it is highly recommended to implement* this method properly, using the current state of the FactoryBean.* @return the type of object that this FactoryBean creates,* or {@code null} if not known at the time of the call* @see ListableBeanFactory#getBeansOfType*/@NullableClass<?> getObjectType();/*** Is the object managed by this factory a singleton? That is,* will {@link #getObject()} always return the same object* (a reference that can be cached)?* <p><b>NOTE:</b> If a FactoryBean indicates to hold a singleton object,* the object returned from {@code getObject()} might get cached* by the owning BeanFactory. Hence, do not return {@code true}* unless the FactoryBean always exposes the same reference.* <p>The singleton status of the FactoryBean itself will generally* be provided by the owning BeanFactory; usually, it has to be* defined as singleton there.* <p><b>NOTE:</b> This method returning {@code false} does not* necessarily indicate that returned objects are independent instances.* An implementation of the extended {@link SmartFactoryBean} interface* may explicitly indicate independent instances through its* {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean}* implementations which do not implement this extended interface are* simply assumed to always return independent instances if the* {@code isSingleton()} implementation returns {@code false}.* <p>The default implementation returns {@code true}, since a* {@code FactoryBean} typically manages a singleton instance.* @return whether the exposed object is a singleton* @see #getObject()* @see SmartFactoryBean#isPrototype()*/default boolean isSingleton() {return true;}
}
②创建类MyFactoryBean
package com.ling.spring6.iocxml.factorybean;import org.springframework.beans.factory.FactoryBean;public class MyFactoryBean implements FactoryBean<User> {@Overridepublic User getObject() throws Exception {return new User();}@Overridepublic Class<?> getObjectType() {return User.class;}
}
③配置bean
<bean id="user" class="com.atguigu.spring6.bean.UserFactoryBean"></bean>
④测试
package com.ling.spring6.iocxml.factorybean;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestUser {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("bean-factorybean.xml");User myFactoryBean = context.getBean("myFactoryBean", User.class);System.out.println(myFactoryBean);}
}
运行结果

可以看到得到的对象是User对象,而不是FactoryBean对象

实验十三:基于xml自动装配

自动装配:

根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值

①场景模拟
创建类UserController
package com.ling.spring6.iocxml.auto.controller;import com.ling.spring6.iocxml.auto.service.UserService;
import com.ling.spring6.iocxml.auto.service.UserServiceImpl;public class UserController {private UserService userService;public void setUserService(UserService userService) {this.userService = userService;}public void addUser(){System.out.println("Controller方法执行了...");userService.addUserService();
//        原生方法
//        UserService userService = new UserServiceImpl();
//        userService.addUserService();}
}
创建接口UserService
package com.ling.spring6.iocxml.auto.service;public interface UserService {public void addUserService();
}
创建类UserServiceImpl实现接口UserService
package com.ling.spring6.iocxml.auto.service;import com.ling.spring6.iocxml.auto.dao.UserDao;
import com.ling.spring6.iocxml.auto.dao.UserDaoImpl;public class UserServiceImpl implements UserService {private UserDao userDao;public void setUserDao(UserDao userDao) {this.userDao = userDao;}@Overridepublic void addUserService() {System.out.println("UserService方法执行了...");userDao.addUserDao();
//        原生方法
//        UserDao userDao = new UserDaoImpl();
//        userDao.addUserDao();}
}
创建接口UserDao
package com.ling.spring6.iocxml.auto.dao;public interface UserDao {public void addUserDao();
}
创建类UserDaoImpl实现接口UserDao
package com.ling.spring6.iocxml.auto.dao;public class UserDaoImpl implements UserDao {@Overridepublic void addUserDao() {System.out.println("UserDao方法执行了...");}
}
②配置bean

使用bean标签的autowire属性设置自动装配效果

自动装配方式:byType

  • byType:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值
  • 若在IOC中,没有任何一个兼容类型的bean能够为属性赋值(没有在配置文件中找到对应的描述信息),则该属性不装配,即值为默认值null
  • 若在IOC中,有多个兼容类型的bean能够为属性赋值(就是识别出某个接口有多个实现类),则抛出异常NoUniqueBeanDefinitionException
<bean id="userController" class="com.ling.spring6.iocxml.auto.controller.UserController" autowire="byType" />
<bean id="userService" class="com.ling.spring6.iocxml.auto.service.UserServiceImpl" autowire="byType"/>
<bean id="userDao" class="com.ling.spring6.iocxml.auto.dao.UserDaoImpl" />

自动装配方式:byName

  • byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值(在实现类中定义的属性名要跟配置文件中bean的id相同)
<bean id="userController" class="com.ling.spring6.iocxml.auto.controller.UserController" autowire="byName" />
<bean id="userService" class="com.ling.spring6.iocxml.auto.service.UserServiceImpl" autowire="byName"/>
<bean id="userDao" class="com.ling.spring6.iocxml.auto.dao.UserDaoImpl" />
③测试
package com.ling.spring6.iocxml.auto;import com.ling.spring6.iocxml.auto.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestUser {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("bean-auto.xml");UserController userController = context.getBean("userController", UserController.class);userController.addUser();}
}
运行结果

基于注解管理Bean(重要)

从 Java 5 开始,Java 增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码逻辑的情况下,在源代码中嵌入补充信息

Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配简化 Spring 的 XML 配置

Spring 通过注解实现自动装配的步骤如下:

  • 引入依赖
  • 开启组件扫描
  • 使用注解定义 Bean
  • 依赖注入

搭建子模块spring6-ioc-annotation

①搭建模块

搭建方式如:spring6-ioc-annotation

②引入配置文件

引入日志配置文件log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration><loggers><!--level指定日志级别,从低到高的优先级:TRACE < DEBUG < INFO < WARN < ERROR < FATALtrace:追踪,是最低的日志级别,相当于追踪程序的执行debug:调试,一般在开发中,都将其设置为最低的日志级别info:信息,输出重要的信息,使用较多warn:警告,输出警告的信息error:错误,输出错误信息fatal:严重错误--><root level="DEBUG"><appender-ref ref="spring6log"/><appender-ref ref="RollingFile"/><appender-ref ref="log"/></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><!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用--><File name="log" fileName="d:/spring6_log/test.log" append="false"><PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/></File><!-- 这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--><RollingFile name="RollingFile" fileName="d:/spring6_log/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="20"/></RollingFile></appenders>
</configuration>
③添加依赖
<dependencies><!--spring context依赖--><!--当你引入Spring Context依赖以后,表示将Spring的基础依赖引入了--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.1.14</version></dependency><!--junit--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.8.1</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-slf4j-impl</artifactId><version>2.19.0</version></dependency>
</dependencies>

开启组件扫描

​Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 context:component-scan 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。

<?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/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--开启组件扫描--><context:component-scan base-package="com.ling.spring6"/>
</beans>

注意:在使用 context:component-scan 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 中添加 context 相关的约束

情况一:最基本的扫描
<context:component-scan base-package="com.ling.spring6"/>
情况二:指定要排除的插件
<context:component-scan base-package="com.ling.spring6"><!-- context:exclude-filter标签:指定排除规则 --><!--type:设置排除或包含的依据type="annotation",根据注解排除,expression中设置要排除的注解的全类名type="assignable",根据类型排除,expression中设置要排除的类型的全类名--><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/><!--<context:exclude-filter type="assignable" expression="org.springframework.stereotype.Controller"/>-->
</context:component-scan>
ans>
情况三:仅扫描指定组件
<context:component-scan base-package="com.ling.spring6" use-default-filters="false"><!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 --><!-- use-default-filters属性:取值false表示关闭默认扫描规则 --><!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 --><!-- type:设置排除或包含的依据type="annotation",根据注解排除,expression中设置要排除的注解的全类名type="assignable",根据类型排除,expression中设置要排除的类型的全类名--><context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/><!--<context:include-filter type="assignable" expression="org.springframework.stereotype.Controller"/>-->
</context:component-scan>

使用注解定义 Bean

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

注解说明
@Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component s

这四个注解其实效果大差不差,不过一般来说:

@Component一般用于普通bean对象

@Repository一般用于Dao层

@Service一般用于Service层

@Controller一般用于Web层

测试
package com.ling.spring6.bean;import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;//@Component(value = "user") // 相当于 <bean id="user" class="com.ling.spring6.bean.User"></bean>
//@Repository
//@Service
@Controller
public class User {
}

实验一:@Autowired注入

单独使用@Autowired注解,默认根据类型装配。【默认是byType】

查看源码:

package org.springframework.beans.factory.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {boolean required() default true;
}

源码中有两处需要注意:

  • 第一处:该注解可以标记在哪里?
    • 构造方法上
    • 方法上
    • 形参上
    • 属性上
    • 注解上
  • 第二处:该注解有一个requied属性,默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错。如果required属性设置为false,表示注入的Bean存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。
①场景一:属性注入

创建UserDao接口

package com.ling.spring6.autowired.dao;public interface UserDao {public void add();
}

创建UserDaoImpl实现

package com.ling.spring6.autowired.dao;import org.springframework.stereotype.Repository;@Repository
public class UserDaoImpl implements UserDao{@Overridepublic void add() {System.out.println("Dao add...");}
}

创建UserService接口

package com.ling.spring6.autowired.service;public interface UserService {public void add();
}

创建UserServiceImpl实现

package com.ling.spring6.autowired.service;import com.ling.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService{// 注入Dao// 第一种方式:属性注入@Autowired // 根据类型找到对应的对象,完成注入private UserDao userDao;@Overridepublic void add() {System.out.println("Service add....");userDao.add();}
}

创建UserController类

package com.ling.spring6.autowired.controller;import com.ling.spring6.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController {// 注入Service// 第一种方式:属性注入@Autowired // 根据类型找到对应的对象,完成注入private UserService userService;public void add(){System.out.println("Controller...add...");userService.add();}
}
测试一
package com.ling.spring6.autowired;import com.ling.spring6.autowired.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestUserController {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");UserController controller = context.getBean(UserController.class);controller.add();}
}
测试结果

以上构造方法和setter方法都没有提供,经过测试,仍然可以注入成功。

②场景二:set注入

修改UserServiceImpl类

package com.ling.spring6.autowired.service;import com.ling.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService{// 注入Dao// 第二种方式:set方法注入private UserDao userDao;@Autowiredpublic void setUserDao(UserDao userDao) {this.userDao = userDao;}@Overridepublic void add() {System.out.println("Service add....");userDao.add();}
}

修改UserController类

package com.ling.spring6.autowired.controller;import com.ling.spring6.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController {// 注入Service// 第二种方式:set方法注入private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}public void add(){System.out.println("Controller...add...");userService.add();}
}
③场景三:构造方法注入

修改UserServiceImpl类

package com.ling.spring6.autowired.service;import com.ling.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService{// 注入Dao// 第三种方式:构造方法注入private UserDao userDao;@Autowiredpublic UserServiceImpl(UserDao userDao) {this.userDao = userDao;}@Overridepublic void add() {System.out.println("Service add....");userDao.add();}
}

修改UserController类

package com.ling.spring6.autowired.controller;import com.ling.spring6.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController {// 注入Service// 第三种方式:构造方法注入private UserService userService;@Autowiredpublic UserController(UserService userService) {this.userService = userService;}public void add(){System.out.println("Controller...add...");userService.add();}
}
④场景四:形参上注入

修改UserServiceImpl类

package com.ling.spring6.autowired.service;import com.ling.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService{// 注入Dao// 第四种方式:形参上注入private UserDao userDao;public UserServiceImpl(@Autowired UserDao userDao) {this.userDao = userDao;}@Overridepublic void add() {System.out.println("Service add....");userDao.add();}
}

修改UserController类

package com.ling.spring6.autowired.controller;import com.ling.spring6.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController {// 注入Service// 第四种方式:形参上注入private UserService userService;public UserController(@Autowired UserService userService) {this.userService = userService;}public void add(){System.out.println("Controller...add...");userService.add();}
}
⑤场景五:只有一个构造函数,无注解

修改UserServiceImpl类

package com.ling.spring6.autowired.service;import com.ling.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService{// 注入Dao// 第五种方式:只有一个有参构造,可以不用加注解private UserDao userDao;public UserServiceImpl(UserDao userDao) {this.userDao = userDao;}@Overridepublic void add() {System.out.println("Service add....");userDao.add();}
}

修改UserController类

package com.ling.spring6.autowired.controller;import com.ling.spring6.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController {// 注入Service// 第五种方式:只有一个有参构造,可以不用加注解private UserService userService;public UserController(UserService userService) {this.userService = userService;}public void add(){System.out.println("Controller...add...");userService.add();}
}

有参数的构造方法只有一个时,@Autowired注解可以省略

说明

有多个构造方法时呢?大家可以测试(再添加一个无参构造函数),测试报错

⑥场景六:@Autowired注解和@Qualifier注解联合

添加dao层实现UserRedisDaoImpl

package com.ling.spring6.autowired.dao;import org.springframework.stereotype.Repository;@Repository
public class UserRedisDaoImpl implements UserDao {@Overridepublic void add() {System.out.println("UserRedisDaoImpl add...");}
}

测试:测试异常

错误信息中说:不能装配,UserDao这个Bean的数量等于2(UserDao的实现类有两个)

怎么解决这个问题呢?当然要byName,根据名称进行装配了。

修改UserServiceImpl类

package com.ling.spring6.autowired.service;import com.ling.spring6.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService{// 注入Dao// 第六种方式:使用@Qualifier注解指定名称@Autowired@Qualifier(value = "userRedisDaoImpl")private UserDao userDao;@Overridepublic void add() {System.out.println("Service add....");userDao.add();}
}

总结
  • @Autowired注解可以出现在:属性上、构造方法上、构造方法的参数上、setter方法上。
  • 当带参数的构造方法只有一个,@Autowired注解可以省略。()
  • @Autowired注解默认根据类型注入。如果要根据名称注入的话,需要配合@Qualifier注解一起使用。

实验二:@Resource注入

@Resource和@AutoWired区别

@Resource注解也可以完成属性注入。那它和@Autowired注解有什么区别?

  • @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
  • @Autowired注解是Spring框架自己的。
  • @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
  • @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
  • @Resource注解用在属性上、setter方法上。
  • @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。

@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。

<dependency><groupId>jakarta.annotation</groupId><artifactId>jakarta.annotation-api</artifactId><version>2.1.1</version>
</dependency>
源码
package jakarta.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Resources.class)
public @interface Resource {String name() default "";String lookup() default "";Class<?> type() default Object.class;Resource.AuthenticationType authenticationType() default Resource.AuthenticationType.CONTAINER;boolean shareable() default true;String mappedName() default "";String description() default "";public static enum AuthenticationType {CONTAINER,APPLICATION;private AuthenticationType() {}}
}
①场景一:根据name注入

修改UserDaoImpl类

package com.ling.spring6.resource.dao;import org.springframework.stereotype.Repository;@Repository("myUserDao")
public class UserDaoImpl implements UserDao {@Overridepublic void add() {System.out.println("Dao add...");}
}

修改UserServiceImpl类

package com.ling.spring6.resource.service;import com.ling.spring6.resource.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;@Service("myUserService")
public class UserServiceImpl implements UserService {@Resource(name = "myUserDao")private UserDao userDao;@Overridepublic void add() {System.out.println("Service add....");userDao.add();}
}
②场景二:name未知注入

修改UserDaoImpl类

package com.ling.spring6.resource.dao;import org.springframework.stereotype.Repository;@Repository("myUserDao")
public class UserDaoImpl implements UserDao {@Overridepublic void add() {System.out.println("Dao add...");}
}

修改UserServiceImpl类

package com.ling.spring6.resource.service;import com.ling.spring6.resource.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;@Service("myUserService")
public class UserServiceImpl implements UserService {// 不指定名称,根据属性名称进行注入@Resourceprivate UserDao myUserDao;@Overridepublic void add() {System.out.println("Service add....");myUserDao.add();}
}

当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名

③场景三 其他情况

修改UserServiceImpl类

package com.ling.spring6.resource.service;import com.ling.spring6.resource.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;@Service("myUserService")
public class UserServiceImpl implements UserService {// 不指定名称,根据属性名称进行注入@Resourceprivate UserDao myUserDao;@Overridepublic void add() {System.out.println("Service add....");myUserDao.add();}
}

修改UserController类

package com.ling.spring6.resource.controller;import com.ling.spring6.resource.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Controller;@Controller("myUserController")
public class UserController {// 根据类型进行注入@Resourceprivate UserService userService;public void add(){System.out.println("Controller...add...");userService.add();}
}

在UserController类中,既没有定义name,根据属性名也找不到对应的对象,这个时候就是根据类型进行查找,但是所查找的类型的实现类要只有一个,不然会报错

@Resource的set注入可以自行测试

总结:

@Resource注解:默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个

Spring全注解开发

全注解开发就是不再使用spring配置文件了,写一个配置类来代替配置文件。

package com.ling.spring6.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration
@ComponentScan("com.ling.spring6")
public class SpringConfig {
}

测试类

package com.ling.spring6.resource;import com.ling.spring6.config.SpringConfig;
import com.ling.spring6.resource.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class TestUserControllerAnno {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);UserController userController = context.getBean(UserController.class);userController.add();}
}

这里需要注意的是,原先我们加载配置文件的时候,配置文件的名称需要加上双引号;但是现在我们加载的是一个配置类,加载配置类不需要加上双引号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/60372.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp—android原生插件开发(4uniapp引用aar插件)

本篇文章从实战角度出发&#xff0c;将UniApp集成新大陆PDA设备RFID的全过程分为四部曲&#xff0c;涵盖环境搭建、插件开发、AAR打包、项目引入和功能调试。通过这份教程&#xff0c;轻松应对安卓原生插件开发与打包需求&#xff01; 一、将android程序打包成aar插件包 直接使…

RedisTemplate序列化设置

前言 在使用 Redis 作为缓存数据库时&#xff0c;我们通常会使用 RedisTemplate 来简化与 Redis 进行交互的操作。而其中一个重要的配置项就是序列化设置&#xff0c;它决定了数据在存储到 Redis 中时的格式。本文将介绍如何进行 RedisTemplate 的序列化设置&#xff0c;以及一…

如何优化Elasticsearch的查询性能?

优化Elasticsearch查询性能可以从以下几个方面进行&#xff1a; 合理设计索引和分片&#xff1a; 确保设置合理的分片和副本数&#xff0c;考虑数据量、节点数和集群大小。根据数据量和节点数量调整分片数量&#xff0c;避免使用过多分片&#xff0c;因为每个分片都需要额外的…

ORU——ORAN 无线电单元参考架构

ORU ORU-开放无线电单元ORU 类型O-RU“A类”O-RU“B类” 参考相关文章 ORU-开放无线电单元 ORU&#xff08;开放无线电单元&#xff09;的目的是将天线发送和接收的无线电信号转换为数字信号&#xff0c;该数字信号可通过前传传输到分布式单元&#xff08;DU&#xff09;。考虑…

FFMPEG录屏(22)--- Linux 下基于X11枚举所有显示屏,并获取大小和截图等信息

众人拾柴火焰高&#xff0c;github给个star行不行&#xff1f; open-traa/traa traa is a versatile project aimed at recording anything, anywhere. The primary focus is to provide robust solutions for various recording scenarios, making it a highly adaptable tool…

多线程和线程同步复习

多线程和线程同步复习 进程线程区别创建线程线程退出线程回收全局写法传参写法 线程分离线程同步同步方式 互斥锁互斥锁进行线程同步 死锁读写锁api细说读写锁进行线程同步 条件变量生产者消费者案例问题解答加强版生产者消费者 总结信号量信号量实现生产者消费者同步-->一个…

FlinkPipelineComposer 详解

FlinkPipelineComposer 详解 原文 背景 在flink-cdc 3.0中引入了pipeline机制&#xff0c;提供了除Datastream api/flink sql以外的一种方式定义flink 任务 通过提供一个yaml文件&#xff0c;描述source sink transform等主要信息 由FlinkPipelineComposer解析&#xff0c…

Zustand浅学习

道阻且长&#xff0c;行而不辍&#xff0c;未来可期 之前只是会使用zustand,也没仔细看过zustand的文档&#xff0c;前段时间一个合约朋友问我前端的zustand怎么用&#xff0c;啊&#xff0c;这&#xff0c;是那个笑起来明媚的不像话的帅哥问我问题诶&#xff0c;那我得认真一下…

海量数据迁移:Elasticsearch到OpenSearch的无缝迁移策略与实践

文章目录 一&#xff0e;迁移背景二&#xff0e;迁移分析三&#xff0e;方案制定3.1 使用工具迁移3.2 脚本迁移 四&#xff0e;方案建议 一&#xff0e;迁移背景 目前有两个es集群&#xff0c;版本为5.2.2和7.16.0&#xff0c;总数据量为700T。迁移过程需要不停服务迁移&#…

【贪心算法】贪心算法三

贪心算法三 1.买卖股票的最佳时机2.买卖股票的最佳时机 II3.K 次取反后最大化的数组和4.按身高排序5.优势洗牌&#xff08;田忌赛马&#xff09; 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#…

Devops业务价值流:敏捷测试最佳实践

在迭代增量开发模式下&#xff0c;我们强调按照用户故事的优先级进行软件小功能的频繁交付。由于迭代周期紧凑&#xff0c;测试与开发活动往往并行进行&#xff0c;测试时间相对有限。为确保在这种快节奏的开发环境中依然能够保持产品质量&#xff0c;我们特制定以下测试阶段的…

el-table 纵向垂直表头处理

项目中表格展示会遇到需要纵向垂直表头情况&#xff0c;下面&#xff0c;我们基于el-table组件来实现这种表格。 以下是这次需要用到的数据表格&#xff0c;已知左侧违章名称是固定的&#xff0c;而月份是不固定的&#xff0c;在后端返回数据格式已确定的情况下&#xff0c;需…

HDFS和HBase跨集群数据迁移 源码

HDFS集群间数据迁移&#xff08;hadoop distcp&#xff09; hadoop distcp \ -pb \ hdfs://XX.14.36.205:8020/user/hive/warehouse/dp_fk_tmp.db/ph_cash_order \ hdfs://XX.18.32.21:8020/user/hive/warehouse/dp_fksx_mart.db/HBase集群间数据&#xff08;hbase ExportSnap…

浅谈单片机的gcc优化级别__以双音频信号发生器为例

IDE&#xff1a; CLion HOST&#xff1a; Windows 11 MinGW&#xff1a;x86_64-14.2.0-release-posix-seh-ucrt-rt_v12-rev0 GCC&#xff1a; arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi 一、简介 gcc有多种优化级别&#xff0c;一般不选择的情况下&#x…

Ceph MDS高可用架构探索:从零到一构建多主一备MDS服务

文章目录 Ceph实现MDS服务多主一备高可用架构当前 mds 服务器状态添加 MDS 服务器验证ceph集群当前状态当前的文件系统状态设置处于激活状态 mds 的数量MDS 高可用优化分发配置文件并重启 mds 服务 Ceph实现MDS服务多主一备高可用架构 Ceph 的元数据服务&#xff08;MDS&#…

PySpark 数据处理实战:从基础操作到案例分析

Spark 的介绍与搭建&#xff1a;从理论到实践_spark环境搭建-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交&#xff1a;本地与集群模式全解析-CSDN博客 Spark on YARN&#xff1a;Spark集群模式…

使用GPT-SoVITS训练语音模型

1.项目演示 阅读单句话 1725352713141 读古诗 1725353700203 2.项目环境 开发环境&#xff1a;linux 机器配置如下&#xff1a;实际使用率百分之二十几&#xff0c; 3.开发步骤 1.首先是准备数据集&#xff0c;要求是wav格式&#xff0c;一到两个小时即可&#xff0c; 2.…

Python学习从0到1 day27 Python 高阶技巧 ③ 设计模式 — 单例模式

此去经年&#xff0c;再难同游 —— 24.11.11 一、什么是设计模式 设计模式是一种编程套路&#xff0c;可以极大的方便程序的开发最常见、最经典的设计模式&#xff0c;就是我们所学习的面向对象了。 除了面向对象外,在编程中也有很多既定的套路可以方便开发,我们称之为设计模…

3.2 软件需求:面对过程分析模型

面对过程分析模型 1. 需求分析的模型概述1.1 面对过程分析模型-结构化分析方法1.2 结构化分析的过程 2. 功能模型&#xff1a;数据流图初步2.1 加工2.2 外部实体&#xff08;数据源点/终点&#xff09;2.3 数据流2.4 数据存储2.5 注意事项 3. 功能模型&#xff1a;数据流图进阶…

Android Studio 运行模拟器无法打开avd

问题&#xff1a;已经下载了HAXM 打开模拟器时还是提示未下载HAXM&#xff0c;无法打开avd 解决方案&#xff1a; 控制面板 -> 启动或关闭Windows功能&#xff0c;打开图下两项&#xff0c;后重启电脑重启Android Studio&#xff1a;