第四章:Spring上
4.1:Spring简介
-
Spring
概述官网地址:
https://spring.io/
。Spring
是最受欢迎的企业级的java
应用程序开发框架,数以百万的来自世界各地的开发人员使用Spring
框架来创建性能好、易于测试、可重用的代码。Spring
框架是一个开源的Java
平台,它最初是由Rod Johnson
编写的,并且于2003年5月首次在Apache 2.0
许可下发布。Spring
是轻量级的框架,其基础版本只有2MB
左右的大小。Spring
框架的核心特性是可以用于开发任何Java
应用程序,但是在Java EE
平台上构建web
应用程序是需要扩展的。Spring
框架的目标是使J2EE
开发变得更容易使用,通过启用基于POJO
编程模型来促进良好的编程实践。
-
Spring
家族
-
Spring Framework
Spring
基础框架,可以视为Spring
基础设施,基本上任何其他Spring
项目都是以Spring Framework
为基础的。-
Spring Framework
特性-
非入侵式
使用
Spring Framework
开发应用程序是,Spring
对应用程序本身的结构影响非常小。对领域模型可以做到零污染,对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于Spring Framework
开发应用程序时结构清晰、简洁优雅。 -
控制反转
IOC————Inversion of Control
,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境资源准备好,我们享受资源注入。 -
面向切面编程
AOP————Aspect Oriented Programming
,在不修改资源代码的基础上增强代码功能。 -
容器
Spring IOC
是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。 -
组件化
Spring
实现了使用简单的组件配置组合成一个复杂的应用。在Spring
中可以使用XML
和Java
注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。 -
声明式
很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
-
一站式
在
IOC
和AOP
的基础上可以整合各种企业应用的开源框架和优秀的第三方类库,而且Spring
旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在Spring Framework
的基础上全部使用Spring
来实现。
-
-
Spring Framework
五大功能模块功能模块 功能介绍 Core Container
核心容器,在 Spring
环境下使用任何功能都必须基于IOC
容器AOP & Aspects
面向切面编程 Testing
提供了对 Junit
或TestNG
测试框架的整合Data Access/ Integration
提供了对数据访问/集成的功能 Spring MVC
提供了面向 Web
应用程序的集成功能
-
4.2:IOC
-
IOC
容器-
IOC
思想IOC
:Inversion of Control
,翻译过来是反转控制。-
获取资源的传统方式
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
-
反转控制方式获取资源
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推动给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
-
DI
DI
:Dependency Injection
,翻译过来是依赖注入。
DI
是IOC
的另一种表述方式:即组件以一些预先定义好的方式接收来自于容器的资源注入,相对于IOC
而言,这种表述更直接。
IOC
就是一种反转控制的思想,而DI
是对IOC
的一种具体实现。 -
-
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_helloworld_07
。 -
引入依赖
<packaging>jar</packaging> <dependencies> <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.1</version> </dependency> <!-- junit测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
-
创建类:
HelloWorld
package com.wang.pojo; public class HelloWorld { public void sayHello() { System.out.println("Hello, spring"); } }
-
创建
spring
的配置文件
<!-- bean: 配置一个bean对象,将对象交给IOC容器管理 id: bean的唯一标识,不能重复 class: 设置bean对象所对应的类型 --> <bean id="helloworld" class="com.wang.pojo.HelloWorld"></bean>
-
测试
@Test public void test() { // 获取IOC容器 ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); // 获取IOC容器中的bean HelloWorld helloworld = (HelloWorld)ioc.getBean("helloworld"); helloworld.sayHello(); }
-
-
-
实验二:获取
bean
创建一个新的模块,
spring_ioc_xml_08
,并引入spring_helloworld_07
模块pom.xml
的jar
包。创建一个Student
实体类。package com.wang.pojo; public class Student { private Integer sid; private String name; private Integer age; private String gender; // 如下省略空参构造、全参构造、get/set方法、toString方法 }
<!-- 创建spring-ioc.xml配置文件配置一个bean对象 --> <bean id="studentOne" class="com.wang.pojo.Student"></bean>
-
根据
id
获取由于
id
属性指定了bean
的唯一标识,所以根据bean
标签的id
属性可以精确获取到一个组件对象。 -
根据类型获取
// 当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个 // 若没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException // 若有多个类型匹配的bean,此时抛出异常:NoUniqueBeanDefinitionException @Test public void testIOC() { ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml"); Student student = ioc.getBean(Student.class); System.out.println(student); }
-
根据
id
和类型// 如果组件实现了接口,根据接口类型可以获取bean,但是要求bean唯一 @Test public void testIOC() { ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml"); Student student = ioc.getBean("studentOne", Student.class); // 其中Student实现了Perso接口,且只有唯一的Student类实现了Person接口 // Person person = ioc.getBean(Person.class); System.out.println(person); }
根据类型来获取
bean
时,在满足bean
唯一性的前提下,其实只是看:对象 instanceof 指定的类型
的返回结果,只要返回结果为true
就可以认定为为和类型匹配,能够获取到。
-
-
实验三:依赖注入值
setter
注入<bean id="studentTwo" class="com.wang.pojo.Student"> <!-- property: 通过成员变量的set方法进行赋值 name:设置需要赋值的属性名(和set方法有关) value:设置为属性所赋的值 --> <property name="sid" value="1001"></property> <property name="name" value="张三"></property> <property name="age" value="23"></property> <property name="gender" value="男"></property> </bean>
@Test public void testDI() { ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml"); Student student = ioc.getBean("studentTwo", Student.class); System.out.println(student); }
-
实验四:依赖注入之构造器注入
<!-- constructor-arg标签属性 name: 指定参数名 index: 指定参数所在位置的索引(从0开始) --> <bean id="studentThree" class="com.wang.pojo.Student"> <constructor-arg value="1002"></constructor-arg> <constructor-arg value="李四"></constructor-arg> <constructor-arg value="女"></constructor-arg> <constructor-arg value="24"></constructor-arg> </bean>
@Test public void testDI() { ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml"); Student student = ioc.getBean("studentThree", Student.class); System.out.println(student); }
-
实验五:特殊值处理
-
字面量赋值
<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 --> <property name="name" value="张三"></property>
-
null
值<property name="gender"><null/></property>
-
xml
实体<!-- value属性值为<王五> --> <property name="name" value="<王五>"></property>
-
CDATA
节<!-- CDATA中的C代表Character,是文本、字符的含义、CDATA就表示纯文本数据。 XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析,所以CDATA节中写什么符号都随意 --> <property name="name"> <value><![CDATA[<王五>]]></value> </property>
-
-
实验六:为类类型属性值赋值
-
创建一个
Clazz
类package com.wang.pojo; public class Clazz { private Integer cid; private String cname; // 如下省略空参构造、全参构造、get/set方法、toString方法 }
-
在
Stduent
中添加一个属性private Clazz clazz;
-
方式一:引用外部以声明的
bean
<bean id="studentFive" class="com.wang.pojo.Student"> <property name="sid" value="1004"></property> <property name="name" value="赵六"></property> <property name="age" value="26"></property> <property name="gender" value="男"></property> <!-- ref:引用IOC容器中的某个bean的id --> <property name="clazz" ref="clazzOne"></property> </bean> <bean id="clazzOne" class="com.wang.pojo.Clazz"> <property name="cid" value="1111"></property> <property name="cname" value="最强王者班"></property> </bean>
-
方式二:级联属性赋值
<bean id="studentFive" class="com.wang.pojo.Student"> <property name="sid" value="1004"></property> <property name="name" value="赵六"></property> <property name="age" value="26"></property> <property name="gender" value="男"></property> <!-- ref:引用IOC容器中的某个bean的id --> <property name="clazz" ref="clazzOne"></property> <!-- 级联的方式,要保证提前为clazz属性赋值或者实例化 --> <property name="clazz.cid" value="2222"></property> <property name="clazz.cname" value="远大前程班"></property> </bean>
-
方式三:内部
bean
<bean id="studentFive" class="com.wang.pojo.Student"> <property name="sid" value="1004"></property> <property name="name" value="赵六"></property> <property name="age" value="26"></property> <property name="gender" value="男"></property> <property name="clazz"> <!-- 内部bean,只能在当前bean的内部使用,不能直接通过IOC容器获取 --> <bean id="clazzInner" class="com.wang.pojo.Clazz"> <property name="cid" value="2222"></property> <property name="cname" value="远大前程班"></property> </bean> </property> </bean>
-
-
实验七:为数组类型属性赋值
-
在
Student
类添加一个属性private String[] hobby;
-
配置
bean
<bean id="studentFive" class="com.wang.pojo.Student"> <property name="sid" value="1004"></property> <property name="name" value="赵六"></property> <property name="age" value="26"></property> <property name="gender" value="男"></property> <property name="hobby"> <array> <value>抽烟</value> <value>喝酒</value> <value>烫头</value> </array> </property> </bean>
-
-
实验八:为集合类型属性赋值
-
为
List
集合类型属性赋值-
在
Clazz
类中添加一个students
属性private List<Student> students;
-
方式一:在内部配置
bean
<bean id="clazzOne" class="com.wang.pojo.Clazz"> <property name="cid" value="1111"></property> <property name="cname" value="最强王者班"></property> <property name="students"> <list> <ref bean="studentOne"></ref> <ref bean="studentTwo"></ref> <ref bean="studentThree"></ref> </list> </property> </bean>
-
方式二:在外部配置
bean
<bean id="clazzOne" class="com.wang.pojo.Clazz"> <property name="cid" value="1111"></property> <property name="cname" value="最强王者班"></property> <property name="students" ref="studentList"></property> </bean> <!-- 配置一个集合类型额bean,需要使用util的约束 --> <util:list id="studentList"> <ref bean="studentOne"></ref> <ref bean="studentTwo"></ref> <ref bean="studentThree"></ref> </util:list>
-
-
为
Map
集合类型属性赋值-
创建
Teacher
类package com.wang.pojo; public class Teacher { private Integer tid; private String tname; }
-
在
Student
类中添加一个属性private Map<String, Teacher> teacherMap;
-
方式一:在内部配置
bean
<bean id="studentFive" class="com.wang.pojo.Student"> <property name="sid" value="1004"></property> <property name="name" value="赵六"></property> <property name="age" value="26"></property> <property name="gender" value="男"></property> <property name="teacherMap"> <map> <entry key="10086" value-ref="teacherOne"></entry> <entry key="10010" value-ref="teacherTwo"></entry> </map> </property> </bean> <bean id="teacherOne" class="com.wang.pojo.Teacher"> <property name="tid" value="10086"></property> <property name="tname" value="大宝"></property> </bean> <bean id="teacherTwo" class="com.wang.pojo.Teacher"> <property name="tid" value="10010"></property> <property name="tname" value="小宝"></property> </bean>
-
方式二:在外部配置
bean
<bean id="studentFive" class="com.wang.pojo.Student"> <property name="sid" value="1004"></property> <property name="name" value="赵六"></property> <property name="age" value="26"></property> <property name="gender" value="男"></property> <property name="teacherMap" ref="studentMap"></property> </bean> <util:map id="studentMap"> <entry key="10086" value-ref="teacherOne"></entry> <entry key="10010" value-ref="teacherTwo"></entry> </util:map>
-
-
-
实验九:
p
命名空间 引入
p
命名空间后,可以通过以下方式为bean
的各个属性赋值。<bean id="studentSix" class="com.wang.pojo.Student" p:sid="1005" p:name="小明" p:teacherMap-ref="studentMap"></bean>
-
实验十:引入外部属性文件
-
加入依赖
<!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <!-- 数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.31</version> </dependency>
-
创建外部文件
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC jdbc.username=root jdbc.password=abc123
-
创建
spring-datasource.xml
并引入外部文件<!-- 引入jdbc.properties, 之后可以通过${key}的方式访问value--> <context:property-placeholder location="jdbc.properties" ></context:property-placeholder>
-
配置
bean
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean>
-
测试,新建
DataSourceTest.java
文件@Test public void testDataSource() throws SQLException { ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-datasource.xml"); DruidDataSource dataSource = ioc.getBean(DruidDataSource.class); System.out.println(dataSource.getConnection()); }
-
-
实验十一:
bean
的作用域在
Spring
中可以通过配置bean
标签的scope
属性来指定bean
的作用域范围,各取值含义参加下表:取值 含义 创建对象的时机 singleton
(默认)在 IOC
容器中,这个bean
的对象始终为单实例IOC
容器初始化时prototype
这个 bean
在IOC
容器中有多个实例获取 bean
时如果是在
WebApplicationContext
环境下还会有另外两个作用域(但不常用):取值 含义 request
在一个请求范围内有效 session
在一个会话范围内有效 <!-- 创建spring-scope.xml配置文件配置一个bean对象 --> <bean id="student" class="com.wang.pojo.Student" scope="prototype"> <property name="sid" value="1001"></property> <property name="name" value="张三"></property> </bean>
@Test public void testScope() { ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-scope.xml"); Student student1 = ioc.getBean(Student.class); Student student2 = ioc.getBean(Student.class); System.out.println(student1 == student2); // false }
-
实验十二:
bean
生命周期-
具体的生命周期过程
- 实例化:
bean
对象创建(调用无参构造器)。 - 依赖注入:给
bean
对象设置属性。 - 后置处理器的
postProcessBeforeInitialization
:bean
对象初始化之前操作(由bean
的后置处理器负责)。 - 初始化,需要通过
bean
的init-method
:对象的初始化(需要配置bean
时指定初始化方法)。 - 后置处理器的
postProcessAfterInitialization
:bean
对象初始化之后操作(由bean
的后置处理器负责)。 IOC
容器关闭时销毁,需要通过bean
的destroy-method
属性指定销毁的方法:bean
对象销毁(需在配置bean
时指定销毁方法)。
- 实例化:
-
注意:
- 若
bean
的作用域为单例时,生命周期的前三个步骤会在获取IOC
容器时执行。 - 若
bean
的作用域为多例时,生命周期的前三个步骤会在获取bean
时执行。
- 若
-
创建
User
类package com.wang.pojo; public class User { private Integer id; private String username; private String password; private Integer age; // 如下省略空参构造、全参构造、get/set方法、toString方法 // 空参构造和这一个set方法按下面方式改动即可,其他的不变 public User() { System.out.println("生命周期1:实例化"); } public void setId(Integer id) { System.out.println("生命周期2:依赖注入"); this.id = id; } public void initMethod() { System.out.println("生命周期3:初始化"); } public void destroyMethod() { System.out.println("生命周期4:销毁"); } }
-
创建
spring-lifecyc.xml
配置文件配置bean
对象<bean id="user" class="com.wang.pojo.User" init-method="initMethod" destroy-method="destroyMethod"> <property name="id" value="1"></property> <property name="username" value="admin"></property> <property name="password" value="123456"></property> <property name="age" value="23"></property> </bean>
-
bean
的后置处理器
bean
的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor
接口,且配置到IOC
容器中,需要注意的是,bean
后置处理器不是单独针对某一个bean
生效,而是针对IOC
容器中所有bean
都会执行。package com.wang.process; public class MyBeanPostProcessor implements BeanPostProcessor { // 此方法在bean的生命周期初始化之前执行 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("MyBeanPostProcessor--->后置处理器postProcessBeforeInitialization"); return bean; } // 此方法在bean的生命周期初始化之后执行 @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("MyBeanPostProcessor--->前置处理器postProcessAfterInitialization"); return bean; } }
<bean id="myBeanPostProcessor" class="com.wang.process.MyBeanPostProcessor"></bean>
-
测试
@Test public void test() { ConfigurableApplicationContext ioc = new ClassPathXmlApplicationContext("spring-lifecycle.xml"); User user = ioc.getBean(User.class); System.out.println(user); ioc.close(); }
-
-
实验十三:
FactoryBean
-
简介
FactoryBean
是spring
提供的一种整合第三方框架的常用机制。和普通的bean
不同,配置一个FactoryBean
类型的bean
,在获取bean
的时候得到的并不是class
属性中配置的这个类的对象,而是getObject()
方法的返回值。通过这种机制,Spring
可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。package org.springframework.beans.factory; public interface FactoryBean<T> { String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType"; @Nullable T getObject() throws Exception; @Nullable Class<?> getObjectType(); default boolean isSingleton() { return true; } }
-
创建类
UserFactoryBean
package com.wang.factory; public class UserFactoryBean implements FactoryBean<User> { @Override public User getObject() throws Exception { return new User(); } @Override public Class<?> getObjectType() { return User.class; } }
-
配置
bean
<bean class="com.wang.factory.UserFactoryBean"></bean>
-
测试
@Test public void testFactoryBean() { ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-factory.xml"); User user = ioc.getBean(User.class); System.out.println(user); }
-
-
实验十四:基于
xml
的自动装配自动装配:根据指定的策略,在
IOC
容器中匹配某一个bean
,自动为指定的bean
中所依赖的类类型或接口类型属性赋值。-
场景模拟
-
dao
层:package com.wang.dao; public interface UserDao { void saveUser(); }
package com.wang.dao.impl; public class UserDaoImpl implements UserDao { @Override public void saveUser() { System.out.println("保存成功"); } }
-
service
层:package com.wang.service; public interface UserService { void saveUser(); }
package com.wang.service.impl; public class UserServiceImpl implements UserService { private UserDao userDao; public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void saveUser() { userDao.saveUser(); } }
-
controller
层:package com.wang.controller; public class UserController{ private UserService userService; public void setUserService(UserService userService){ this.userService = userService; } public void saveUser() { userService.saveUser(); } }
-
配置
bean
:手动配置<!-- 创建spring-autowire-xml.xml配置文件配置bean对象 --> <bean id="userController" class="com.wang.controller.UserController"> <property name="userService" ref="userService"></property> </bean> <bean id="userService" class="com.wang.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <bean id="userDao" class="com.wang.dao.impl.UserDaoImpl"></bean>
-
测试
@Test public void testAutowire() { ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-autowire-xml.xml"); UserController userController = ioc.getBean(UserController.class); userController.saveUser(); }
-
-
使用
byType
方式自动装配<!-- 自动装配的策略: 1. no, default: 表示不装配,即bean中的属性不会自动匹配某个bean为属性值,此时属性使用默认值 2. byType:根据要赋值的属性的类型,在IOC容器中匹配某个bean,为属性赋值 注意: 1. 若通过类型没有找到任何一个类型匹配的bean,此时不装配,属性使用默认值。 2. 若通过类型找到了多个类型匹配的bean,此时会抛出异常: NoUniqueBeanDefinitionException。 --> <bean id="userController" class="com.wang.controller.UserController" autowire="byType"></bean> <bean id="userService" class="com.wang.service.impl.UserServiceImpl" autowire="byType"></bean> <bean id="userDao" class="com.wang.dao.impl.UserDaoImpl"></bean>
-
使用
byName
方式自动装配<!-- byName:将要赋值的属性的属性名作为bean的id在IOC容器中匹配某个bean,为属性赋值。当类型匹配的bean有多个时,此时可以使用byName实现自动装配 --> <bean id="userController" class="com.wang.controller.UserController" autowire="byName"></bean> <bean id="userService" class="com.wang.service.impl.UserServiceImpl" autowire="byName"></bean> <bean id="userDao" class="com.wang.dao.impl.UserDaoImpl"></bean>
-
-
基于注解管理
bean
-
实验一:标记与扫苗
-
注解
和
XML
配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。 本质上:所有一切的操作都是
Java
代码来完成的,XML
和注解只是告诉框架中的Java
代码如何执行。 -
扫描
Spring
为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后跟注解进行后续操作。 -
新建模块
创建一个新的模块,
spring_ioc_annotation_09
,并引入下面的jar
包。<packaging>jar</packaging> <dependencies> <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.1</version> </dependency> <!-- junit测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
-
标识组件的常用注解
@Component
:将类标识为普通组件。@Repository
:将类标识为持久层组件。@Service
:将类标识为业务层组件。@Controller
:将类标识为持久层组件。
通过源码我们得知:
@Repository
、@Service
、@Controller
这三个注解只是在@Component
注解的基础上起了三个新名字。对于Spring
使用IOC
容器管理这些组件来说没有区别。所以@Repository
、@Service
、@Controller
这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。 -
创建组件
-
dao
层:package com.wang.dao; public interface UserDao { }
package com.wang.dao.impl; @Repository public class UserDaoImpl implements UserDao { }
-
service
层:package com.wang.service; public interface UserService { }
package com.wang.service.impl; @Service public class UserServiceImpl implements UserService { }
-
controller
层:package com.wang.controller; @Controller public class UserController { }
-
-
扫描组件
-
情况一:最基本的扫描方式
<!-- 创建spring-ioc-annotation.xml配置文件--> <context:component-scan base-package="com.wang"></context:component-scan>
-
情况二:指定要排除的组件
<!-- context:exclude-filter: 排除扫描 type: 设置排除扫描的方式 annotation: 根据注解的类型进行排除, expression需要设置排除的注解的全类名 assignable: 根据类的类型进行排除, expression需要设置排除的类的全类名 --> <context:component-scan base-package="com.wang" use-default-filters="false"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!-- <context:exclude-filter type="assignable" expression="com.wang.controller.UserController"/>--> </context:component-scan>
-
情况三:仅扫描指定组件
<!-- context:include-filter: 包含扫描 注意: 需要在context:component-scan标签中设置use-default-filters="false" use-default-filters: 默认为true, 设置的包下所有的类是否都需要扫描 --> <context:component-scan base-package="com.wang" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
-
-
测试
@Test public void test() { ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc-annotation.xml"); UserController userController = ioc.getBean(UserController.class); System.out.println(userController); UserService userService = ioc.getBean(UserService.class); System.out.println(userService); UserDao userDao = ioc.getBean(UserDao.class); System.out.println(userDao); }
-
组件所对应的
bean
的id
通过注解+扫描所配置的
bean
的id
,默认值为类的小驼峰,即类名的首字母为小写的结果。可以通过标识组件的注解的value
属性值设置bean
的自定义的id
。
-
-
实验二:基于注解的自动装配
-
场景模拟
-
dao
层:void saveUse();
@Override public void saveUse() { System.out.println("保存成功"); }
-
service
层:void saveUser();
@Autowired private UserDao userDao; @Override public void saveUser() { userDao.saveUse(); }
-
controller
层:@Autowired private UserService userService; // @Autowired // public UserController(UserService userService) { // this.userService = userService; // } // @Autowired // public void setUserService(UserService userService) { // this.userService = userService; // } public void saveUser() { userService.saveUser(); }
-
-
Autowired
注解能够标识的位置- 标识在成员变量上,此时不需要设置成员变量的
set
方法。 - 标识在
set
方法上。 - 标识在为当前成员变量赋值的有参构造上。
- 标识在成员变量上,此时不需要设置成员变量的
-
Autowired
注解的原理- 默认通过
byType
的方式,在IOC
容器中通过类型匹配某个bean
为属性赋值。 - 若有多个类型匹配的
bean
,此时会自动转换为byName
的方式实现自动装配的效果。 - 若
byType
和byName
的方式都无法实现自动装配,即IOC
容器中有多个类型匹配的bean
且这些bean
的id
要赋值的属性的属性名都不一致,此时抛异常:NoUniqueBeanDefinitionException
。 - 此时可以在要赋值的属性上,添加一个注解
@Qualifier
,通过该注解的value
属性值,指定某个bean
的id
,将这个bean
为属性赋值。
- 默认通过
-
Autowired
注解的注意事项 若
IOC
容器中没有任何一个类型匹配的bean
,此时抛出异常:NoSuchBeanDefinitionException
。在@Autowired
注解中有个属性required
,默认值为true
,要求必须完成自动装配,可以将required
设置为false
,此时能装配则装配,无法装配则使用属性的默认值。
-
-