一、IoC容器
IoC是Inversion of Control的简写,翻译为“控制反转”.它不是一门技术,而是一种设计思想
Spring通过IoC容器来管理(1)所有Java对象的实例化和初始化,(2)控制对象与对象之间的依赖关系
我们将由IoC容器管理的Java对象称为Spring Bean,它与用关键字new创建的Java对象没有任何区别
即用IoC容器来放对象,并管理对象从创建到销毁的全部过程
IoC容器放对象,使用Map集合
(1)控制反转
- 控制反转是一种思想
- 控制反转是为了降低程序的耦合度,提高程序的扩展力
- 控制反转,反转的是什么:
- 将对象的创建权力交出去,交给IoC容器负责
- 将对象和对象之间关系的维护权交出去,交给IoC容器负责
- 控制反转这种思想如何实现呢?DI(Dependency Injection):依赖注入
(2)依赖注入
- 在配置文件中配置好,针对于某个类我们想要什么样的对象,把这个信息交给IoC。然后由IoC创建、返回对象
- DI(Dependency Injection):依赖注入,依赖注入体现了控制反转的思想(初始化)
- 依赖注入:是指Spring在创建对象的过程中,将对象的属性的通过配置进行注入
- 依赖注入常见的实现方式包括两种:
- 第一种:set注入
- 第二种:构造注入
- 结论:IoC是一种控制反转的思想,而DI是IoC的一种具体实现
- Bean管理说的是:Bean对象的创建,以及Bean对象中属性的赋值
(3)IOC容器在Spring的实现
- Spring的IoC容器就是IoC思想的一个落地的产品实现。在创建bean之前,首先需要创建IoC容器
- Spring提供了实现IoC容器的两种方式:
- BeanFactory:这是IoC容器的基本实现,是Spring内部使用的接口。面向Spring本身,不提供给开发人员使用
- ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是BeanFactory
- ApplicationContext的主要实现类:
- ApplicationContext的主要实现类介绍:
类型名 简介 ClassPathXMLApplicationContext 读取类路径下的XML格式的配置文件创建IoC容器对象 FileSystemXMLApplicationContext 通过文件系统路径读取XML格式的配置文件创建IoC容器对象 ConfigurableApplicationContext ApplicationContext的子接口,包含一些扩展方法refresh()和close(),让ApplicationContext具有启动、关闭和刷新上下文的能力 WebApplicationContext 专门为Web应用准备,基于Web环境创建IoC容器对象,并将对象存入ServletContext域中
二、基于XML管理Bean
(1)搭建子模块spring-ioc-xml
- 我们把spring-first的依赖都让父工程管理(即将spring-first的pom.xml文件中的dependencies标签和该标签里的所有内容放到spring6的pom.xml文件中,然后刷新):
- 搭建好子模块spring-ioc-xml后发现:因为此时它继承了父模块的所有依赖项
- 要在模块中使用spring,就必须有spring的配置文件,所以我们在spring-ioc-xml的src/main/resources中创建bean.xml(名字可以随便起)
- 还要引入log4j2的配置文件:
- 在spring-ioc-xml的src/main/java下新建一个类
(2)实验一:获取bean
准备操作:
- 方式一:根据id获取
- id属性指定了bean的唯一标识
- 演示:
package com.atguigu.spring6.iocxml; 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.xml"); //获取bean User user = (User)context.getBean("user"); } }
-
Loaded 1 bean definitions from class path resource [bean.xml]意思是加载spring配置文件
-
Creating shared instance of singleton bean 'user'意思是创建了一个单例对象
- 方式二:根据类型获取
- 此时根据id得到对象和根据类型得到对象,二者返回的是同一个对象(因为单例)
- 演示:
package com.atguigu.spring6.iocxml; 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.xml"); //1.根据id获取bean User user1 = (User)context.getBean("user"); System.out.println("根据id获取bean:" + user1); //2.根据类型获取bean User user2 = context.getBean(User.class); System.out.println("根据类型获取bean:" + user2); } }
- 方式三:根据id和类型
- 演示:
package com.atguigu.spring6.iocxml; 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.xml"); //1.根据id获取bean User user1 = (User)context.getBean("user"); System.out.println("根据id获取bean:" + user1); //2.根据类型获取bean User user2 = context.getBean(User.class); System.out.println("根据类型获取bean:" + user2); //3.根据id和类型获取bean User user3 = context.getBean("user", User.class); System.out.println("根据id和类型获取bean:" + user3); } }
- user1,user2和user3的地址值都是一样的
- 演示:
- 用后两种方式时,因为已经指定了类型,所以不需要我们强转
- 注意:在bean.xml文件中给同一个类配置bean标签时,id值只能有一个
- 假如,我们给同一个类配置不同的id值,那么在用类型获取bean对象时就会报错
- 我们先把用id获取bean对象的代码注释掉,然后运行
- 但是我们发现,如果我们用id获取bean对象,或者用id和类型获取bean对象,就不会报错。因为根据id就可以精准定位
- expected single matching bean but found 2: user,user1该错误信息表示:期望获得一个单实例的bean,但是获取了两个
- 也就是说,一个bean标签对应着一个单实例的bean。此时我们对同一个类设置了两个bean标签,我们分别用它们的id来获取bean,可以看到,这确实是两个不同的bean
package com.atguigu.spring6.iocxml; 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.xml"); //1.根据id获取bean User user = (User)context.getBean("user"); System.out.println("根据id获取bean:" + user); User user1 = (User)context.getBean("user1"); System.out.println("根据id获取bean:" + user1); } }
- 扩展知识:
- 如果一个接口有一个实现类,我们可以根据该接口得到bean吗
- 如果一个接口有多个实现类,我们可以根据该接口得到bean吗
- 我们写一个接口UserDao,再写一个类UserDaoImpl,让它实现UserDao
- 这么写就类似于new UserDaoImpl,因为接口是不能new的
- 在新建类里测试一下,根据接口类型获取实现类的对象:上面四张图是代码,然后我们运行main方法,发现可以通过接口类型得到实现类的对象:
- 如果一个接口有多个实现类,我们能通过接口类型得到实现类的对象吗
- 运行main方法发现,如果一个接口有多个实现类对象,那就无法通过接口类型获取实现类的对象了
(3)实验二:依赖注入之setter注入
- 类有属性,创建对象的过程中,给属性设置值
- 第一种方式:基于set方法完成
- 第二种方式:基于构造器完成
- 创建一个Book类:
package com.atguigu.spring6.iocxml.di; public class Book { private String bname; private String author; //生成set方法 public void setBname(String bname) { this.bname = bname; } public void setAuthor(String author) { this.author = author; } //生成有参数的构造器 public Book(String bname, String author) { this.bname = bname; this.author = author; } //生成无参数的构造器 public Book() { } //toString方法 @Override public String toString() { return "Book{" + "bname='" + bname + '\'' + ", author='" + author + '\'' + '}'; } public static void main(String[] args) { //set方法注入 Book book = new Book(); book.setBname("java"); book.setAuthor("尚硅谷"); //构造器注入 Book book1 = new Book("c++","尚硅谷"); } }
- 配置bean标签时为属性赋值(此时我们重新建了一个配置文件,名字可以随便起。Spring相关的配置文件都要放在src/main/resources下面):name对应类的属性名、value对应类的属性值
- 测试代码如下,因为我们此时使用的是bean-di.xml文件,所以文件名别填错
package com.atguigu.spring6.iocxml.di; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestBook { @Test public void testSetter(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml"); Book book = context.getBean(Book.class); System.out.println(book); } }
- 可以看到,通过setter注入成功(所谓注入,其实就是给属性赋值)
- 基于setter注入的前提是类中有无参构造器和setter方法。基于setter注入,其实就等价于使用类中的setter方法
(4)实验三:依赖注入之构造器注入
- 基于构造器注入的前提是类中有无参构造器。基于构造器注入,其实就等价于使用类中的有参构造器
- 构造器注入,要这么写配置文件中的bean标签(可以用name也可以用index,index=0表示第一个属性,index=1表示第二个属性......)
- 为了测试效果更明显,我们在Book类的setter方法中加一条语句:
- 测试代码:
package com.atguigu.spring6.iocxml.di; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestBook { @Test public void testConstructor(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml"); //因为现在Book类有两个id,所以我们不能根据类型获取bean了 Book book = (Book)context.getBean("bookCon"); System.out.println(book); } }
- 可以看到,在bean标签里使用constructor-arg标签,等价于调用有参构造器来初始化对象
- 总结:在Spring的配置文件中一旦使用bean标签,就等价于给类创建了一个实例对象(id有点像实例对象的名字)。如果在bean标签中,使用了property标签,就等于通过类里的setter方法给对象的属性赋值(先用无参构造器创建对象,再用setter方法给属性赋值)。如果在bean标签中,使用了constructor标签,就等于调用了类里的有参构造器
(5)实验四:特殊值处理
- 字面量赋值:字面量就是数据本身。例如,int a = 10;10就是字面量
- 使用value属性给bean的属性赋值时,Spring会把给value属性的值看作字面量。比如,这个“前端开发”就是字面量
- 如果我们此时,给Book类再添加一个属性,并为这个属性设置setter方法
- 如果我们在注入时,给该属性赋null,该怎么做呢?
- xml实体:小于号在XML文档中是用来定义标签的开始的,不能随便使用。假如我们想给属性赋的值中出现了<该怎么办呢?例如
- 解决xml实体的方式一:转义,小于号(<)用<表示,大于号(>)用>表示。这样就不会报错了
- 解决xml实体的方式二:CDATA区。XML解析器看到CDATA区就知道这里是纯文本,就不会当作XML标签或属性来解析,所以在CDATA区中写什么符号都可以
- 在<![CDATA[在这里面写什么符号都可以]]>
- 测试一下,发现成功
(6)实验五:为对象类型属性赋值
方式一:引用外部bean
- 准备两个类,员工类和部门类
- 代码:
package com.atguigu.spring6.iocxml.ditest; //员工类 public class Emp { //对象类型的属性 员工属于某个部门 private Dept dept; //员工名称 private String ename; //员工年龄 private Integer age; public void setDept(Dept dept) { this.dept = dept; } public void setEname(String ename) { this.ename = ename; } public void setAge(Integer age) { this.age = age; } public void work(){ System.out.println(ename+"emp work..."+age); dept.info(); } }
package com.atguigu.spring6.iocxml.ditest; //部门类 public class Dept { private String dname; public void setDname(String dname) { this.dname = dname; } public void info(){ System.out.println("部门名称:" + dname); } }
- 我们新建一个Spring配置文件(文件名称可以随意,但是Spring配置文件必须放在src/main/resources内)
<?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.创建两个类对象emp和dept 2.在emp的bean标签里,用property引入dept的bean --> <bean id="dept" class="com.atguigu.spring6.iocxml.ditest.Dept"> <property name="dname" value="安保部"></property> </bean> <bean id="emp" class="com.atguigu.spring6.iocxml.ditest.Emp"> <!--基本类型属性注入--> <property name="ename" value="lucy"></property> <property name="age" value="50"></property> <!--对象类型属性注入--> <property name="dept" ref="dept"></property> </bean> </beans>
- 为对象类型的属性注入,不要用value,而是用ref。ref后面跟的是,要注入的对象的id
- 测试一下,成功
方式二:内部bean
- 内部bean方式代码演示:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--第二种方式:内部bean注入--> <bean id="emp2" class="com.atguigu.spring6.iocxml.ditest.Emp"> <property name="ename" value="mary"></property> <property name="age" value="20"></property> <property name="dept"> <bean id="dept2" class="com.atguigu.spring6.iocxml.ditest.Dept"> <property name="dname" value="财务部"></property> </bean> </property> </bean> </beans>
- 测试一下,发现成功
- 内部bean就是在bean标签里再定义一个bean
- 外部bean就是在bean标签里用到在它外面定义的bean
方式三:级联属性赋值
- 级联赋值代码演示:
package com.atguigu.spring6.iocxml.ditest; //员工类 public class Emp { //对象类型的属性 员工属于某个部门 private Dept dept; //员工名称 private String ename; //员工年龄 private Integer age; public void setDept(Dept dept) { this.dept = dept; } public void setEname(String ename) { this.ename = ename; } public void setAge(Integer age) { this.age = age; } public Dept getDept() { return dept; } public String getEname() { return ename; } public Integer getAge() { return age; } public void work(){ System.out.println(ename+"emp work..."+age); dept.info(); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--第三种方式:级联赋值--> <bean id="dept3" class="com.atguigu.spring6.iocxml.ditest.Dept"> <property name="dname" value="技术研发部"></property> </bean> <bean id="emp3" class="com.atguigu.spring6.iocxml.ditest.Emp"> <property name="ename" value="tom"></property> <property name="age" value="30"></property> <property name="dept" ref="dept3"></property> <property name="dept.dname" value="测试部"></property> </bean> </beans>
- 测试一下,发现成功:
- 需注意:级联赋值必须为对象类型的属性提供getter方法!!!我们可以理解为,(1)先通过getter方法得到该对象,(2)然后再通过该对象所属的类中提供的setter方法为该对象的属性赋值
(7)实验六:为数组类型属性赋值
- 修改Emp类的代码,为员工类添加一个新属性,该属性为数组类型
package com.atguigu.spring6.iocxml.ditest; import java.util.Arrays; //员工类 public class Emp { //对象类型的属性 员工属于某个部门 private Dept dept; //员工名称 private String ename; //员工年龄 private Integer age; //爱好 private String[] loves; public void setDept(Dept dept) { this.dept = dept; } public void setEname(String ename) { this.ename = ename; } public void setAge(Integer age) { this.age = age; } public void setLoves(String[] loves) { this.loves = loves; } public Dept getDept() { return dept; } public String getEname() { return ename; } public Integer getAge() { return age; } public void work(){ System.out.println(ename+"emp work..."+age); dept.info(); System.out.println(Arrays.toString(loves)); } }
- 为了方便,我们再创建一个Spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--注入数组类型的属性--> <bean id="dept" class="com.atguigu.spring6.iocxml.ditest.Dept"> <property name="dname" value="技术部"></property> </bean> <bean id="emp" class="com.atguigu.spring6.iocxml.ditest.Emp"> <!--普通类型的属性--> <property name="ename" value="lucy"></property> <property name="age" value="20"></property> <!--对象类型的属性--> <property name="dept" ref="dept"></property> <!--数组类型的属性--> <property name="loves"> <array> <value>吃饭</value> <value>睡觉</value> <value>敲代码</value> </array> </property> </bean> </beans>
- 测试一下,发现成功:
(8)实验七:为集合类型属性赋值
- 为List集合类型属性赋值:首先我们修改部门类,为它添加一个List集合类型的属性
package com.atguigu.spring6.iocxml.ditest; import java.util.List; //部门类 public class Dept { //一个部门有很多员工 private List<Emp> empList; private String dname; public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public List<Emp> getEmpList() { return empList; } public void setEmpList(List<Emp> empList) { this.empList = empList; } public void info() { System.out.println("部门名称:"+dname); for (Emp emp:empList) { System.out.println(emp.getEname()); } } }
然后我们新建一个Spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="empone" class="com.atguigu.spring6.iocxml.ditest.Emp"> <property name="ename" value="lucy"></property> <property name="age" value="20"></property> </bean> <bean id="emptwo" class="com.atguigu.spring6.iocxml.ditest.Emp"> <property name="ename" value="mary"></property> <property name="age" value="30"></property> </bean> <bean id="dept" class="com.atguigu.spring6.iocxml.ditest.Dept"> <property name="dname" value="技术部"></property> <property name="empList"> <list> <ref bean="empone"></ref> <ref bean="emptwo"></ref> </list> </property> </bean> </beans>
-
测试一下,发现成功:
- 为Map集合类型属性赋值(先创建两个类,Student类和Teacher类,然后配置一个Spring的xml文件):
package com.atguigu.spring6.iocxml.dimap; import java.util.Map; //学生类 public class Student { private Map<String,Teacher> teacherMap; private String sid; private String sname; 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 Map<String, Teacher> getTeacherMap() { return teacherMap; } public void setTeacherMap(Map<String, Teacher> teacherMap) { this.teacherMap = teacherMap; } public void run(){ System.out.println("学生编号:" + sid + "学生名称:" + sname); System.out.println(teacherMap); } }
package com.atguigu.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; } @Override public String toString() { return "Teacher{" + "teacherId='" + teacherId + '\'' + ", teacherName='" + teacherName + '\'' + '}'; } }
<?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.在学生bean注入map集合类型属性 --> <bean id="teacherone" class="com.atguigu.spring6.iocxml.dimap.Teacher"> <!--注入普通类型属性--> <property name="teacherId" value="100"></property> <property name="teacherName" value="西门讲师"></property> </bean> <bean id="teachertwo" class="com.atguigu.spring6.iocxml.dimap.Teacher"> <!--注入普通类型属性--> <property name="teacherId" value="200"></property> <property name="teacherName" value="上官讲师"></property> </bean> <bean id="student" class="com.atguigu.spring6.iocxml.dimap.Student"> <!--注入普通类型属性--> <property name="sid" value="2000"></property> <property name="sname" value="张三"></property> <!--注入Map集合类型属性--> <property name="teacherMap"> <map> <entry> <key> <value>10010</value> </key> <ref bean="teacherone"></ref> </entry> <entry> <key> <value> 10086 </value> </key> <ref bean="teachertwo"></ref> </entry> </map> </property> </bean> </beans>
- 测试一下,发现成功:
- 如果集合里面的元素是基本数据类型或String类型,那么就用value。如果集合里面的元素是对象数据类型,那么就用ref
- 这是xml文件的约束,写明了xml文件中能用哪些标签,标签里能用哪些属性
- 使用util:list和util:map前,必须引入相应的命名空间,可以通过IDEA的提示功能选择
<?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/util http://www.springframework.org/schema/beans/spring-util.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd ">
- 注入List集合和Map集合的第二种方式(util:list和util:map的id可以随便写,只是ref的时候是根据id来的):
<?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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <!-- 1.创建三个对象 2.注入普通类型属性 3.使用util:类型 定义 4.在学生bean引入 --> <bean id="lessonone" class="com.atguigu.spring6.iocxml.dimap.Lesson"> <property name="lessonName" value="java开发"></property> </bean> <bean id="lessontwo" class="com.atguigu.spring6.iocxml.dimap.Lesson"> <property name="lessonName" value="前端开发"></property> </bean> <bean id="teacherone" class="com.atguigu.spring6.iocxml.dimap.Teacher"> <property name="teacherId" value="100"></property> <property name="teacherName" value="西门讲师"></property> </bean> <bean id="teachertwo" class="com.atguigu.spring6.iocxml.dimap.Teacher"> <property name="teacherId" value="200"></property> <property name="teacherName" value="欧阳讲师"></property> </bean> <bean id="student" class="com.atguigu.spring6.iocxml.dimap.Student"> <property name="sid" value="10000"></property> <property name="sname" value="lucy"></property> <!--注入list和map类型的属性--> <property name="lessonList" ref="lessonList"></property> <property name="teacherMap" ref="teacherMap"></property> </bean> <util:list id="lessonList"> <ref bean="lessonone"></ref> <ref bean="lessontwo"></ref> </util:list> <util:map id="teacherMap"> <entry> <key> <value>10010</value> </key> <ref bean="teacherone"></ref> </entry> <entry> <key> <value>10086</value> </key> <ref bean="teachertwo"></ref> </entry> </util:map> </beans>
- 其实使用util的方式,就是把原本写在bean标签里的list和map,写到bean标签外面来了
- 注意:按照上面的图写的xml文件写错了,有个地方要注意一下。就是我们要把beans的地方全换成util,有个地方漏了
- 测试一下,发现成功:
(9)实验八:p命名空间
- p命名空间是指这种(灰色部分):
- 注意:xsi:schemaLocation部分就不用像util一样修改了
- p命名空间注入:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <!-- 1.创建三个对象 2.注入普通类型属性 3.使用util:类型 定义 4.在学生bean引入 --> <bean id="lessonone" class="com.atguigu.spring6.iocxml.dimap.Lesson"> <property name="lessonName" value="java开发"></property> </bean> <bean id="lessontwo" class="com.atguigu.spring6.iocxml.dimap.Lesson"> <property name="lessonName" value="前端开发"></property> </bean> <bean id="teacherone" class="com.atguigu.spring6.iocxml.dimap.Teacher"> <property name="teacherId" value="100"></property> <property name="teacherName" value="西门讲师"></property> </bean> <bean id="teachertwo" class="com.atguigu.spring6.iocxml.dimap.Teacher"> <property name="teacherId" value="200"></property> <property name="teacherName" value="欧阳讲师"></property> </bean> <bean id="student" class="com.atguigu.spring6.iocxml.dimap.Student"> <property name="sid" value="10000"></property> <property name="sname" value="lucy"></property> <!--注入list和map类型的属性--> <property name="lessonList" ref="lessonList"></property> <property name="teacherMap" ref="teacherMap"></property> </bean> <util:list id="lessonList"> <ref bean="lessonone"></ref> <ref bean="lessontwo"></ref> </util:list> <util:map id="teacherMap"> <entry> <key> <value>10010</value> </key> <ref bean="teacherone"></ref> </entry> <entry> <key> <value>10086</value> </key> <ref bean="teachertwo"></ref> </entry> </util:map> <!--p命名空间注入--> <!--lessonList-ref后面跟的lessonList和teacherMap-ref后面跟的teacherMap都是id--> <bean id="studentp" class="com.atguigu.spring6.iocxml.dimap.Student" p:sid="100" p:sname="mary" p:lessonList-ref="lessonList" p:teacherMap-ref="teacherMap"> </bean> </beans>
(10)实验九:引入外部属性文件
- 加入数据库相关依赖(在pom.xml文件中,加入后记得刷新):
<!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <!-- 数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.15</version> </dependency>
- 创建外部属性文件(在src/main/resources中创建),properties格式:
jdbc.user=root jdbc.password=atguigu jdbc.url=jdbc:mysql://localhost:3306/spring?serverTimezone=UTC jdbc.driver=com.mysql.cj.jdbc.Driver
- 在src/main/resources中创建一个Spring的配置文件,并引入context命名空间,注意,此时还要添加xsi:schemaLocation的部分
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> </beans>
- 还要引入外部属性文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> </beans>
- 完成数据库信息的注入:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--完成数据库信息注入--> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClassName" value="${jdbc.driver}"></property> </bean> </beans>
- 测试代码:
package com.atguigu.spring6.iocxml.jdbc; import com.alibaba.druid.pool.DruidDataSource; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestJdbc { @Test public void demo1(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/spring?serverTimezone=UTC"); dataSource.setUsername("root"); dataSource.setPassword("atguigu"); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); } @Test public void demo2(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean-jdbc.xml"); DruidDataSource druidDataSource = context.getBean(DruidDataSource.class); System.out.println(druidDataSource.getUrl()); } }
- 总结:首先要配置相关依赖(MySQL驱动和数据源)→编写properties文件→编写Spring的配置文件(写context命名空间、通过context引入properties文件、将properties文件的值注入DruidDataSource对象)
- druidDataSource的属性有:username、password、url、driverClassName
(11)实验十:bean的作用域
- 在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,scope的取值含义参见下表:
取值 含义 创建对象的时机 singleton(默认) 在IoC容器中,这个bean的对象始终为单实例 IoC容器初始化时 prototype 这个bean在IoC容器中有多个实例 获取bean时 - 如果是在WebApplicationContext环境下,还会有另外几个作用域(但不常用):
取值 含义 request 在一个请求范围内有效 session 在一个会话范围内有效 - scope的取值为singleton,测试(三个文件的代码)
package com.atguigu.spring6.iocxml.scope; public class Orders { }
<?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"> <!--通过scope属性能配置单实例还是多实例--> <bean id="orders" class="com.atguigu.spring6.iocxml.scope.Orders" scope="singleton"></bean> </beans>
package com.atguigu.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); System.out.println(orders); } }
Creating shared instance of singleton bean 'orders'代表创建了一个单实例对象
- 我们再获取一个bean看运行结果可以发现,当scope是singleton时,bean标签创建的就是一个单实例对象
- scope的取值为prototype,测试(我们只修改了Spring的xml文件,将scope的取值改成了prototype)可以看到,获取的两个bean其实是不同的对象
- 并且通过比较可以发现,scope的取值是singleton时,bean会在IoC容器初始化时就创建好。scope的取值是prototype时,bean是在它被获取时才创建好。看日志文件就可以发现
(12)实验十一:bean生命周期
- 具体的生命周期过程:
- bean对象创建(调用无参构造器)
- 给bean对象设置属性
- bean的后置处理器(初始化之前)
- bean对象初始化(调用指定的初始化方法)
- bean的后置处理器(初始化之后)
- bean对象创建完成了,可以使用了
- bean对象销毁(配置指定销毁的方法)
- IoC容器关闭了
- 创建User类:
package com.atguigu.spring6.iocxml.life; public class User { private String name; public String getName() { return name; } public void setName(String name) { System.out.println("2.给bean对象设置属性值"); this.name = name; } //无参数构造 public User() { System.out.println("1.bean对象创建,调用无参数构造"); } //初始化方法 public void initMethod(){ System.out.println("4.bean对象的初始化,会调用指定的初始化的方法"); } //销毁方法 public void destroyMethod(){ System.out.println("7.bean对象销毁,调用指定的销毁方法"); } }
- 编写Spring的配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.atguigu.spring6.iocxml.life.User" scope="singleton" init-method="initMethod" destroy-method="destroyMethod"> <property name="name" value="lucy"></property> </bean> </beans>
- 测试类:
package com.atguigu.spring6.iocxml.life; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestUser { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-life.xml"); User user = context.getBean("user", User.class); System.out.println("6.bean对象创建完成了,可以使用了"); System.out.println(user); //该方法表示会进行销毁 context.close(); } }
- 进行测试(注意:要想使用close方法,就不能用ApplicationContext接口了):
- bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,并且配置到IoC容器中,需要注意的是,bean后置处理器并不淡出针对某一个bean生效,而是针对IoC容器中所有的bean生效
- 创建bean的后置处理器:
package com.atguigu.spring6.iocxml.life; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPost implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("3.后置处理器在初始化之前执行"); System.out.println(beanName + "::" + bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("5.后置处理器在初始化之后执行"); System.out.println(beanName + "::" + bean); return bean; } }
- 在IoC容器中配置后置处理器:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.atguigu.spring6.iocxml.life.User" scope="singleton" init-method="initMethod" destroy-method="destroyMethod"> <property name="name" value="lucy"></property> </bean> <bean id="myBeanPost" class="com.atguigu.spring6.iocxml.life.MyBeanPost"></bean> </beans>
- 加了后置处理器以后的测试效果:
(13)实验十二:FactoryBean
- FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候,得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值
- 创建一个User类:
package com.atguigu.spring6.iocxml.factorybean; public class User { }
- 创建一个实现FactoryBean接口的类:
package com.atguigu.spring6.iocxml.factorybean; import org.springframework.beans.factory.FactoryBean; public class MyFactoryBean implements FactoryBean<User> { @Override public User getObject() throws Exception{ return new User(); } @Override public Class<?> getObjectType(){ return User.class; } }
- 编写一个Spring的配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.atguigu.spring6.iocxml.factorybean.MyFactoryBean"></bean> </beans>
- 编写一个测试类:
package com.atguigu.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 user = (User)context.getBean("user"); System.out.println(user); } }
- 根据测试效果可以发现,虽然我们在配置bean时,对class的配置是myFactoryBean,但是得到的bean并不是这个类的对象,而是该类中的getObject方法返回的对象
(14)实验十三:基于xml自动装配
- 基本环境准备:创建三个软件包(controller、service、dao),controller里面创建一个类UserController。service里面创建一个类UserServiceImpl、一个接口UserService,dao里面创建一个类UserDaoImpl、一个接口UserDao
package com.atguigu.spring6.iocxml.auto.controller; import com.atguigu.spring6.iocxml.auto.service.UserService; import com.atguigu.spring6.iocxml.auto.service.UserServiceImpl; public class UserController { public void addUser(){ System.out.println("controller方法执行了..."); UserService userService = new UserServiceImpl(); userService.addUserService(); } }
package com.atguigu.spring6.iocxml.auto.service; public interface UserService { public void addUserService(); }
package com.atguigu.spring6.iocxml.auto.service; import com.atguigu.spring6.iocxml.auto.dao.UserDao; import com.atguigu.spring6.iocxml.auto.dao.UserDaoImpl; public class UserServiceImpl implements UserService{ @Override public void addUserService() { System.out.println("userService方法执行了"); UserDao userDao = new UserDaoImpl(); userDao.addUserDao(); } }
package com.atguigu.spring6.iocxml.auto.dao; public interface UserDao { public void addUserDao(); }
package com.atguigu.spring6.iocxml.auto.dao; public class UserDaoImpl implements UserDao{ @Override public void addUserDao() { System.out.println("userDao方法执行了"); } }
addUser方法里调用了addUserService方法、addUserService方法里调用了addUserDao方法
- 修改代码(UserController和UserServiceImpl):
package com.atguigu.spring6.iocxml.auto.controller; import com.atguigu.spring6.iocxml.auto.service.UserService; import com.atguigu.spring6.iocxml.auto.service.UserServiceImpl; import org.junit.jupiter.api.Test; public class UserController { private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } @Test public void addUser(){ System.out.println("controller方法执行了..."); userService.addUserService(); // UserService userService = new UserServiceImpl(); // userService.addUserService(); } }
package com.atguigu.spring6.iocxml.auto.service; import com.atguigu.spring6.iocxml.auto.dao.UserDao; import com.atguigu.spring6.iocxml.auto.dao.UserDaoImpl; public class UserServiceImpl implements UserService{ private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void addUserService() { System.out.println("userService方法执行了"); userDao.addUserDao(); // UserDao userDao = new UserDaoImpl(); // userDao.addUserDao(); } }
- 在resources中创建配置文件(这么写就是手动装配)
- 自动装配怎么写呢?
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userController" class="com.atguigu.spring6.iocxml.auto.controller.UserController" autowire="byType"> </bean> <bean id="userService" class="com.atguigu.spring6.iocxml.auto.service.UserServiceImpl" autowire="byType"> </bean> <bean id="userDao" class="com.atguigu.spring6.iocxml.auto.dao.UserDaoImpl"> </bean> </beans>
- 测试代码:
package com.atguigu.spring6.iocxml.auto; import com.atguigu.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的方式注入对象类型的属性的,现在通过自动装配的方式进行
- 使用bean标签的autowire属性设置自动装配效果。自动装配方式:byType
- byType:根据类型匹配IoC容器中的某个兼容类型的bean,为属性自动赋值(比如在UserController这个类中,有一个属性是UserService类型的,那么在IoC容器中找到UserService接口的实现类的对象,就会为该属性自动赋值)
- 若在IoC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值默认为null
- 若在IoC中,有多个兼容类型的bean能够为属性赋值,则抛出异常:NoUniqueBeanDefinitionException
- 根据名称装配:byName。比如UserController类中有个UserService类的属性,叫userService,那么如果在IoC容器中找到一个id为userService的对象,就会进行自动装配
- 我们根据代码测试一下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userController" class="com.atguigu.spring6.iocxml.auto.controller.UserController" autowire="byName"> </bean> <bean id="userService" class="com.atguigu.spring6.iocxml.auto.service.UserServiceImpl" autowire="byName"> </bean> <bean id="userDao" class="com.atguigu.spring6.iocxml.auto.dao.UserDaoImpl"> </bean> </beans>
- 当我们改变xml文件中对象的id,就可以发现不能自动装配成功了(因为UserController类中,UserService类型的属性名字叫userService,但是我们此时的对象id不是userService了,而是userServiceImpl。UserServiceImpl类中,UserDao类型的属性名字叫userDao,但是我们此时的对象id不是userDao了,而是userDaoImlp)可以看到爆红
三、基于注解管理Bean(⭐)
从Java5开始,Java增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息
Spring从2.5版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化Spring的XML配置
Spring通过注解实现自动装配的步骤如下:
- 引入依赖
- 开启组件扫描
- 使用注解定义bean
- 依赖注入
(1)搭建子模块spring6-ioc-annotation
- 引入日志模块log4j2.xml
- 创建一个Spring的配置文件
- 因为依赖已经在父模块中引入过,通过依赖的继承机制,子模块就不需要重复引入了
(2)开启组件扫描
- Spring默认不使用注解装配Bean,因此我们在Spring的XML文件中,通过context:component-scan元素开启Spring Beans的自动扫描功能。开启此功能后,Spring会自动扫描指定的包(base-package属性设置)及其子包下所有的类,如果类上使用了@Component注解,就将该类装配到容器中
- 情况一:最基本的扫描方式:
- 情况二:指定要排除的组件
<context:component-scan base-package="com.atguigu.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="com.atguigu.spring6.controller.UserController"/>--> </context:component-scan>
(1)<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>表示Controller这个注解就不进行扫描(2)<context:exclude-filter type="assignable" expression="com.atguigu.spring6.controller.UserController"/>表示UserController这个类就不进行扫描
- 情况三:仅扫描指定组件
<context:component-scan base-package="com.atguigu" 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="com.atguigu.spring6.controller.UserController"/>--> </context:component-scan>
(1)<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>表示只扫描Controller这个注解(2)<context:include-filter type="assignable" expression="com.atguigu.spring6.controller.UserController"/>表示只扫描UserController这个类
(3)使用注解定义bean
- Spring提供了多个注解,这些注解可以直接标注在Java类上,将它们定义成Spring Bean
注解 说明 @Component 该注解用于描述Spring中的Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如Service层、Dao层等。使用时只需将该注解标注在相应的类上即可 @Repository 该注解用于将Dao层(数据访问层)的类标识为Spring中的Bean,其功能和@Component相同 @Service 该注解用于将Service层(业务层)的类标识为Spring中的Bean,其功能和@Component相同 @Controller 该注解用于将Controller层(控制层)的类标识为Spring中的Bean,其功能和@Component相同 - 使用举例:value可以不写,比如类名为User,如果不写value的话,value的值默认为user
- 建一个User类,建一个TestUser类
package com.atguigu.spring6.bean; import org.springframework.stereotype.Component; @Component(value="user")//<bean id="user" class="..."> public class User { }
package com.atguigu.spring6.bean; 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.xml"); User user = context.getBean(User.class); System.out.println(user); } }
- 配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!--开启组件扫描--> <context:component-scan base-package="com.atguigu.spring6"></context:component-scan> </beans>
- 测试效果:
- 说明在User类上加了@Component注解,等价于创建了一个User类的对象
(4)实验一:@Autowired注入
单独使用@Autowired注解,默认根据类型装配
@Autowired注解源码:
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
源码中有两处需要注意:
- 第一处:该注解可以标注在哪里:
- 构造方法上
- 方法上
- 属性上
- 形参上
- 注解上
- 第二处: 该注解有一个required属性,默认值是true。表示在注入的时候要求被注入的bean是必须存在的,如果不存在则报错。如果required属性设置为false,表示被注入的bean存在或不存在都没关系,存在的话就注入,不存在也不报错
3.4.1场景一:属性注入
- 属性注入就是根据类型找到对象,进行注入的。类似于,基于XML的自动装配
- 创建三个软件包(controller、service、dao)。分别在这几个包中创建实现类和接口
- 代码:
package com.atguigu.spring6.autowired.controller; import com.atguigu.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......"); userService.add(); } }
package com.atguigu.spring6.autowired.service; public interface UserService { public void add(); }
package com.atguigu.spring6.autowired.service; import com.atguigu.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; @Override public void add(){ System.out.println("service......"); userDao.add(); } }
package com.atguigu.spring6.autowired.dao; public interface UserDao { public void add(); }
package com.atguigu.spring6.autowired.dao; import org.springframework.stereotype.Repository; @Repository public class UserDaoImpl implements UserDao{ @Override public void add(){ System.out.println("dao执行..."); } }
- 创建测试类:
package com.atguigu.spring6.autowired; import com.atguigu.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 userController = context.getBean(UserController.class); userController.add(); } }
- 解释:根据注解创建了三个bean(UserController、UserServiceImpl、UserDaoImpl),然后根据类型装配,UserController类中有一个Userservice类型的属性→给userService装配UserServiceImpl对象,UserServiceImpl中有一个UserDao类型的属性→给userDao装配UserDaoImlp对象
3.4.2场景二:set注入
- 修改代码(UserController和UserServiceImpl的):
package com.atguigu.spring6.autowired.controller; import com.atguigu.spring6.autowired.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller public class UserController { private UserService userService; @Autowired public void setUserService(UserService userService) { this.userService = userService; } public void add(){ System.out.println("controller......"); userService.add(); } }
package com.atguigu.spring6.autowired.service; import com.atguigu.spring6.autowired.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService{ private UserDao userDao; @Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void add(){ System.out.println("service......"); userDao.add(); } }
- 测试效果:
- 个人理解:还是通过类型找到对象,然后对象通过setter方法赋值
3.4.3场景三:构造方法注入
- 修改代码(UserController和UserServiceImpl的):
package com.atguigu.spring6.autowired.controller; import com.atguigu.spring6.autowired.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller public class UserController { private UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } public void add(){ System.out.println("controller......"); userService.add(); } }
package com.atguigu.spring6.autowired.service; import com.atguigu.spring6.autowired.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService{ private UserDao userDao; @Autowired public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } @Override public void add(){ System.out.println("service......"); userDao.add(); } }
- 测试效果:
- 个人理解:还是通过类型找到对象,然后对象通过构造器赋值
3.4.4场景四:形参上注入
- 就是不在构造器上加@Autowired,但是把@Autowired加在构造器形参上
- 修改代码(UserController和UserServiceImpl的):
package com.atguigu.spring6.autowired.controller; import com.atguigu.spring6.autowired.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller public class UserController { private UserService userService; public UserController(@Autowired UserService userService) { this.userService = userService; } public void add(){ System.out.println("controller......"); userService.add(); } }
package com.atguigu.spring6.autowired.service; import com.atguigu.spring6.autowired.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService{ private UserDao userDao; public UserServiceImpl(@Autowired UserDao userDao) { this.userDao = userDao; } @Override public void add(){ System.out.println("service......"); userDao.add(); } }
- 测试效果:
- 个人理解:还是通过类型找到对象,然后对象通过构造器赋值
3.4.5场景五:只有一个构造函数,无注解
- 当有参数的构造方法只有一个时,@AutoWired注解可以省略
- 修改代码:
package com.atguigu.spring6.autowired.controller; import com.atguigu.spring6.autowired.service.UserService; import org.springframework.stereotype.Controller; @Controller public class UserController { private UserService userService; public UserController(UserService userService) { this.userService = userService; } public void add(){ System.out.println("controller......"); userService.add(); } }
package com.atguigu.spring6.autowired.service; import com.atguigu.spring6.autowired.dao.UserDao; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService{ private UserDao userDao; public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } @Override public void add(){ System.out.println("service......"); userDao.add(); } }
- 测试通过:
3.4.6场景六:@Autowired注解和@Qualifier注解联合
- 比如,当接口UserDao有两个实现类UserDaoImpl和UserRedisDaoImpl时,由于这两个类上都加了@Repository注解,因此会生成它们各自对应的bean。当只根据类型查找时,会发现满足UserDao的对象有两个,这时就会报错(expected single matching bean but found 2: userDaoImpl,userRedisDaoImpl)
- 针对该问题,解决方案是:使用两个注解,根据名称注入
package com.atguigu.spring6.autowired.service; import com.atguigu.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{ @Autowired @Qualifier(value = "userRedisDaoImpl") private UserDao userDao; @Override public void add(){ System.out.println("service......"); userDao.add(); } }
- 个人理解:@Autowired就是根据类型找对象,@Qualifier就是在类型相同的情况下再根据对象名选择
(5)实验二:@Resource注入
@Resource注解也可以完成属性注入。那么它和@Autowired注解有什么区别呢?
- @Resource注解是JDK扩展包中的,也就是说它是JDK中的一部分。所以该注解是标准注解,更加具有通用性
- @Autowired注解是Spring框架自己的
- @Resource注解默认(1)根据名称装配byName,(2)未指定name时,使用属性名作为name。(3)通过name找不到的话,会自动启动通过类型byType装配
- @Autowired注解默认根据类型装配byType,如果想根据名称装配,则需要配合@Qualifier注解一起使用
- @Resource注解用在属性和setter方法上
- @Autowired注解用在属性、方法、构造器、形参上
@Resource注解属于JDK扩展包,所以不在JDK中,当然需要额外引入以下依赖:(如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖)
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
3.5.1根据name注入
- 如果@Resource后面没有跟(name="..."),那么默认value的值就是类中定义的属性名,然后根据属性名去找叫这个名字的对象。对象名是在@Component/@Controller/@Service/@Repository后面用(value="...")指定的,如果不指定,默认值是(比如类名叫UserController,那么默认值就是userController)
- 测试代码:
package com.atguigu.spring6.resource.controller; import com.atguigu.spring6.resource.service.UserService; import jakarta.annotation.Resource; import org.springframework.stereotype.Controller; @Controller(value = "myUserController") public class UserController { @Resource(name="myUserService") private UserService userService;//此时,指定了名称,那么我们就要找到名为myUserService的对象 public void add(){ System.out.println("controller......"); userService.add(); } }
package com.atguigu.spring6.resource.service; import com.atguigu.spring6.resource.dao.UserDao; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; @Service(value = "myUserService") public class UserServiceImpl implements UserService { @Resource private UserDao myUserDao; @Override public void add(){ System.out.println("service......"); myUserDao.add(); } }
package com.atguigu.spring6.resource.dao; import org.springframework.stereotype.Repository; @Repository(value = "myUserDao") public class UserDaoImpl implements UserDao { @Override public void add(){ System.out.println("dao执行..."); } }
package com.atguigu.spring6.resource.dao; import org.springframework.stereotype.Repository; @Repository(value = "myUserRedisDao") public class UserRedisDaoImpl implements UserDao { @Override public void add() { System.out.println("dao redis......"); } }
package com.atguigu.spring6.resource; import com.atguigu.spring6.resource.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 userController = context.getBean("myUserController",UserController.class); userController.add(); } }
package com.atguigu.spring6.resource.service; public interface UserService { public void add(); }
package com.atguigu.spring6.resource.dao; public interface UserDao { public void add(); }
- 测试成功
- 如果根据name没有找到,就会根据类型找
- 我们修改一下UserController中的代码,把UserService类型的属性的属性名改成“userService”。由于此时@Resource也没有给name显式赋值,因此根据属性名“userService”来找对象,而此时没有叫userService的对象,所以它会根据类型来找
3.5.2name未知注入
- 当name没有显式指定时,name就是该属性的属性名
- 演示看3.5.1
3.5.3其他情况
- 当name没有显式指定时,name就是该属性的属性名。当根据属性名也找不到对应的对象时,我们就根据该类型的类型来找,找到可以兼容该类型的对象
- 演示看3.5.1
(6)Spring全注解开发
- 上面我们虽然用了注解来生成对象和给属性进行注入,但是我们还是写了一个配置文件来开启组件扫描的。全注解开发就是不再使用Spring配置文件来开启组件扫描了,而是写了一个配置类来代替配置文件
- 用配置类来代替配置文件,开启组件扫描。配置类写在src/main/java中
package com.atguigu.spring6.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan("com.atguigu.spring6") public class SpringConfig { }
配置类上有两个注解,一个是@Configuration,一个是@ComponentScan。@Configuration说明这是一个配置类,@ComponentScan("包名")和配置文件中<context:component-scan base-package="com.atguigu.spring6"></context:component-scan>作用一样
- 在测试程序中进行修改,把加载配置文件改成加载配置类
package com.atguigu.spring6.resource; import com.atguigu.spring6.config.SpringConfig; import com.atguigu.spring6.resource.controller.UserController; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class TestUserController { public static void main(String[] args) { //加载配置类 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserController userController = context.getBean("myUserController",UserController.class); userController.add(); } }
把原来的ClassPathXMLApplicationContext改成AnnotationConfigApplicationContext