容器: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 容器对象 |
ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些扩展方法 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 < 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();}
}
这里需要注意的是,原先我们加载配置文件的时候,配置文件的名称需要加上双引号;但是现在我们加载的是一个配置类,加载配置类不需要加上双引号