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.rhkb.cn/news/469514.html

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

相关文章

微服务容器化部署实践(FontConfiguration.getVersion)

文章目录 前言一、整体步骤简介二、开始实战1.准备好微服务2.将各个微服务打包为镜像第一种第二种3. 将各个打包好的镜像,通过docker-compose容器编排,运行即可总结前言 docker容器化部署微服务: 将微服务容器化部署到 Docker 容器中是一个常见的做法,可以提高应用的可移…

提升法律文书处理效率的秘密武器:开源文档比对工具解析

本篇文章介绍了一款针对律师行业的免费开源文档比对工具&#xff0c;旨在解决法律文档的多版本比对难题。通过逐字、逐句精确比对、语义分析、批量处理等核心功能&#xff0c;该工具可高效识别文本差异&#xff0c;提升文书审查效率并降低错误风险。它支持多种文件格式&#xf…

把握鸿蒙生态崛起的机遇:开发者视角的探讨

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; 近年来&#xff0c;鸿蒙系统&#xff08;HarmonyOS&#xff09;的发展备受瞩目。随着其在智能手机、智能穿戴、车载系统和智能家居等领域的广泛应用&#xff0c;鸿蒙系统正逐渐形成与安卓、iOS并列的三足鼎立…

Linux权限和开发工具(3)

文章目录 1. 简单理解版本控制器Git1. 如何理解版本控制 2. Git的操作2.1 Git安装2.2 Git提交身份2.3 Git提交命令2.4 Git版本管理2.5 Git下的同步 3. gdb命令3.1解决gdb的难用问题3.2 gdb/cgdb的使用 1. 简单理解版本控制器Git 1. 如何理解版本控制 我们在做项目的时候可能会…

多线程和线程同步复习

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

WSL 2 中 FastReport 与 FastCube 的设置方法与优化策略

软件开发人员长期以来一直在思考这个问题&#xff1a;“我们如何才能直接在 Windows 中运行 Linux 应用程序&#xff0c;而无需使用单独的虚拟机&#xff1f;” WSL 技术为这个问题提供了一个可能的答案。WSL 的历史始于 2016 年。当时&#xff0c;其实现涉及使用 Windows 内核…

【前端】HTML标签汇总

目录 展示用户信息的标签 1.文本标签 span 2.标题标签 h1~h6 3.竖着布局的标签 div 4.段落标签 p 5.超链接标签 a 5.1跳转至网上的资源 5.2锚点 6.列表标签 6.1有序列表 ol 6.2无序列表 ul 7.图片标签 img 7.1相对路径 7.1.1兄弟关系 7.1.2叔侄关系 7.1.3表兄弟…

海外云手机在出海业务中的优势有哪些?

随着互联网技术的快速发展&#xff0c;海外云手机已在出海电商、海外媒体推广和游戏行业都拥有广泛的应用。对于国内的出海电商企业来说&#xff0c;短视频引流和社交平台推广是带来有效流量的重要手段。借助云手机&#xff0c;企业能够更高效地在新兴社交平台上推广产品和品牌…

电脑提示xinput1_3.dll丢失怎么办?游戏DLL修复方法详解

xinput1_3.dll 是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;它在Windows操作系统中扮演着重要的角色&#xff0c;特别是在处理游戏控制器和其他输入设备的交互方面。这个文件是Microsoft DirectX软件包的一部分&#xff0c;DirectX是微软公司开发的一个多媒体…

Spring资源加载模块,原来XML就这,活该被注解踩在脚下 手写Spring第六篇了

这一篇让我想起来学习 Spring 的时&#xff0c;被 XML 支配的恐惧。明明是写Java&#xff0c;为啥要搞个XML呢&#xff1f;大佬们永远不知道&#xff0c;我认为最难的是 XML 头&#xff0c;但凡 Spring 用 JSON来做配置文件&#xff0c;Java 界都有可能再诞生一个扛把子。 <…

讲讲关于SNMP与智能PDU插座

什么是SNMP 简单网络管理协议 (SNMP) 是一种应用层协议&#xff0c;主要用于网络管理中的设备监控和控制。通过 SNMP&#xff0c;网络管理员可以从管理站远程访问网络中的设备&#xff0c;获取设备的状态信息、配置参数&#xff0c;甚至控制设备的行为。SNMP 被广泛应用于 TCP/…

丹摩征文活动 | Kolors入门:从安装到全面活用的对比指南

文章目录 1 图像生成模型 Kolors2 部署流程3 部署服务3.1 安装 Anaconda3.2 Kolors 库下载3.3 创建虚拟环境 4 生成图片 1 图像生成模型 Kolors Kolors是由快手团队开发的大规模文本到图像生成模型&#xff0c;以其独特的潜在扩散技术而闻名。 Kolors通过在数十亿对文本和图像…

【go从零单排】通道select、通道timeout、Non-Blocking Channel Operations非阻塞通道操作

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 select 语句是 Go 的一种控制结构&#xff0c;用于等待多个通道操作。它类似于 s…

信息安全工程师(83)Windows操作系统安全分析与防护

一、Windows操作系统安全分析 系统漏洞&#xff1a; Windows操作系统由于其复杂性和广泛使用&#xff0c;可能存在一些已知或未知的漏洞。这些漏洞可能会被黑客利用&#xff0c;进行恶意攻击。微软会定期发布系统更新和补丁&#xff0c;以修复这些漏洞&#xff0c;提高系统的安…

计算机网络常见面试题(一):TCP/IP五层模型、TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议

文章目录 一、TCP/IP五层模型&#xff08;重要&#xff09;二、应用层常见的协议三、TCP与UDP3.1 TCP、UDP的区别&#xff08;重要&#xff09;3.2 运行于TCP、UDP上的协议3.3 TCP的三次握手、四次挥手3.3.1 TCP的三次握手3.3.2 TCP的四次挥手3.3.3 随机生成序列号的原因 四、T…

BFD8122防爆轻便移动工作灯

BFD8122防爆轻便移动工作灯 适用范围&#xff1a; 适用于炼油、化工、油田等易燃易爆场所小范围施工、检修、抢险应急照明。 结构特性 高亮度&#xff0c;灯具光通量&#xff1e;4000lm&#xff0c;6米中心照度&#xff08;聚光&#xff09;&#xff1e;1000lx&#xff0c;…

天地图入门|标注|移动飞行|缩放,商用地图替换

“天地图”是国家测绘地理信息局建设的地理信息综合服务网站。集成了来自国家、省、市&#xff08;县&#xff09;各级测绘地理信息部门&#xff0c;以及相关政府部门、企事业单位 、社会团体、公众的地理信息公共服务资源&#xff0c;如果做的项目是政府部门、企事业单位尽量选…

分布式----Ceph部署(上)

目录 一、存储基础 1.1 单机存储设备 1.2 单机存储的问题 1.3 商业存储解决方案 1.4 分布式存储&#xff08;软件定义的存储 SDS&#xff09; 1.5 分布式存储的类型 二、Ceph 简介 三、Ceph 优势 四、Ceph 架构 五、Ceph 核心组件 #Pool中数据保存方式支持两种类型&…

linux详解,基本网络枚举

基本网络枚举 一、基本网络工具 ifconfig ifconfig是一个用于配置和显示网络接口信息的命令行工具。它可以显示网络接口的P地址、子网掩码、MC地址等信息&#xff0c;还可以用于启动、停止或配置网络接口。 ip ip也是用于查看和管理网络接口的命令。 它提供了比ifconfig更…

组件间通信(组件间传递数据)

组件间通信(组件间传递数据) 在 Vue.js 中&#xff0c;组件间通信是开发者需要经常处理的任务&#xff0c;特别是在构建具有多层次组件的复杂应用时。根据组件之间的关系和数据流的复杂程度&#xff0c;可以采用不同的通信方式。以下是常用的几种组件间通信方式&#xff1a; …