Spring
Spring5 框架
1. Spring 概念
1.1 概述
(1)概念
- Spring 是 轻量级 的 开源 的 JavaEE 框架
- Spring 可以解决企业应用开发的复杂性
- Spring 有两个核心部分:IOC 和 AOP
- IOC:控制反转,把创建对象的过程交给 Spring 进行管理
- AOP:面向切面,不修改源代码的情况下,进行功能增强
- Spring 的特点:
- 方便解耦,简化开发
- Aop 编程支持
- 方便程序测试
- 方便和其他框架进行整合
- 方便进行事务操作
- 降低 API 开发难度
1.2 入门案例
(1)案例:
**下载 Spring5 **
官网地址:https://spring.io/
点击 github 图标进入(貌似进不去…)
下载地址:https://repo.spring.io/artifactory/release/org/springframework/spring
打开 idea 工具,创建普通 Java 工具
导入 spring5 相关 jar 包
用 spring 的方式创建对象
创建普通类,在这个类创建普通方法
1
2
3
4
5public class User {
public void add() {
System.out.println("add...");
}
}创建 spring 的配置文件,在配置文件配置创建的对象
Spring 配置文件使用 xml 格式
1
<bean id="user" class="com.thr.spring5.User"></bean>
进行测试代码编写
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class TestSpring5 {
public void testAdd() {
// 1. 加载 spring 的配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
// 2. 获取配置创建的对象
User user = context.getBean("user", User.class);
System.out.println(user);
user.add();
}
}
1.3 Spring 家族
(1)项目列表:https://spring.io/projects
(2)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 应用程序的集成功能
2. IOC 容器
2.1 IOC 底层原理
(1)什么是 IOC?
a> 获取资源的传统方式:
自己做饭:买菜、洗菜、择菜、改刀、炒菜,全过程参与,费时费力,必须清楚了解资源创建整个过程中的全部细节且熟练掌握,在应用程序中的组件需要获取资源时,传统的方式是组件 主动 的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率
b> 反转控制方式获取资源:
点外卖:下单、等、吃,省时省力,不必关心资源创建过程的所有细节,反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向 —— 改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式
c> DI:Dependency Injection ,翻译过来是 依赖注入(对当前 spring 所管理的对象中的属性进行赋值) ,DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如: setter 方法)接受来自于容器 的资源注入。相对于IOC 而言,这种表述更直接。
所以结论是: IOC 就是一种反转控制的思想,而 DI 是对 IOC 的一种具体实现。
- Inversion of Control(IOC) 控制反转,是面向对象编程中的一种设计原则,用来减低代码间的耦合度
- 控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
- 前头做的入门案例就是 IOC 实现
(2)IOC 底层原理
主要用到 xml 解析、工厂模式、反射
画图讲解:
- 原始方式
- 工厂模式(感觉只是耦合度转移了…)
IOC 过程(这样改的话只要改 xml 即可…)
2.2 IOC 接口
Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。 IOC 容器中管理的组件也叫做 bean 。在创建bean 之前,首先需要创建 IOC 容器。 Spring 提供了 IOC 容器的两种实现方式:
a> BeanFactory:
这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
b> ApplicationContext:
BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用
ApplicationContext 而不是底层的 BeanFactory
(1)IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂
(2)Spring 提供 IOC 容器实现的两种方式:(两个接口)
- BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用
- 加载配置文件时不会创建对象,在获取(使用)对象的时候才去创建
- ApplicationContext:BeanFactory 接口的子接口,提供了更多更强大的功能,一般由开发人员进行使用
- 加载配置文件的时候,就会把在配置文件对象进行创建
后者慢启动但是快响应,后者较前者稍好些?
(3)ApplicationContext 接口的实现类:
ctrl + H 打开类的结构
表格:
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() , 让 ApplicationContext 具有启动、关闭和刷新上下文的能力 |
WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象, 并将对象引入存入 ServletContext 域中 |
2.3 IOC 操作 Bean 管理
(1)什么是 Bean 管理
- Bean 管理指的是两个操作
- Spring 创建对象
- Spring 注入属性
(2)Bean 管理操作的两种方式
- 基于 xml 配置文件方式
- 基于注解方式实现
1 | /** |
2.4 IOC 基于 xml 操作 Bean 管理
2.4.1 基于 xml 方式创建对象
(1)在 spring 配置文件中使用 bean 标签,在标签里添加对应属性,就可以实现对象创建
1 | <!-- 配置 User类对象创建 --> |
(2)在 bean 标签里的常用属性
- id 属性:唯一标识(给对象取一个别名)
- class 属性:类全路径(包类路径)
- name 属性:类似与 id,区别是可以加特殊符号,现在用的少
(3)创建对象时,默认也是执行的无参数的构造方法完成对象的创建(如果写了有参则把无参覆盖掉,会报错)
2.4.2 基于 xml 方式注入属性
(1)DI:依赖注入,**就是注入属性(为当前类中的属性进行赋值)**,需在创建对象的基础上完成(是 IOC 的一种具体实现)
(2)第一种注入方式:使用 set 方法进行注入
创建类,定义属性和对应的 set 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Book {
// 创建属性
private String bookName;
private String bookAuthor;
//创建属性对应的 set 方法
public void setBookName(String bookName) {
this.bookName = bookName;
}
public void setBookAuthor(String bookAuthor) {
this.bookAuthor = bookAuthor;
}
public void testDemo() {
System.out.println(bookName + ":" + bookAuthor + ":" + address);
}
}在 spring 配置文件中配置对象创建,配置属性注入
1
2
3
4
5
6
7
8
9
10<!-- set 方法注入属性 -->
<!-- 1. 创建对象 -->
<bean id="book" class="com.thr.spring5.Book">
<!-- 2. 在 bean 标签里使用 properties 标签完成属性注入
name: 类里面属性名称
value: 向属性注入的值
-->
<property name="bookName" value="易筋经"></property>
<property name="bookAuthor" value="达摩"></property>
</bean>测试
1
2
3
4
5
6
7
8
9
10
11public void testBook() {
// 1. 加载 spring 的配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
// 2. 获取配置创建的对象
Book book = context.getBean("book", Book.class);
System.out.println(book); // com.thr.spring5.Book@1b26f7b2
book.testDemo(); // 易筋经:达摩
}
(2)第二种注入方式:使用有参构造进行注入
创建类:定义属性,创建属性对应的有参构造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Orders {
// 属性
private String orderName;
private String address;
// 有参构造
public Orders(String orderName, String address) {
this.orderName = orderName;
this.address = address;
}
public void testDemo() {
System.out.println(orderName + ":" + address);
}
}在 spring 的配置文件中进行配置
1
2
3
4
5
6
7
8
9
10
11
12
13<!-- 3. 有参构造方法注入属性 -->
<!-- 3.1 创建对象 -->
<bean id="orders" class="com.thr.spring5.Orders">
<!-- 3.2 在 bean 标签里使用 constructor-arg 标签完成属性注入
name: 类里面属性名称
value: 向属性注入的值
也可以用索引值
index: 0 表示有参构造的第一个参数, 以此类推
-->
<constructor-arg name="orderName" value="电脑"></constructor-arg>
<constructor-arg name="address" value="china"></constructor-arg>
<!-- <constructor-arg index="0" value="手机"></constructor-arg> -->
</bean>测试:
1
2
3
4
5
6
7
8
9
10
11
12public void testOrders() {
// 1. 加载 spring 的配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
// 2. 获取配置创建的对象
Orders orders = context.getBean("orders", Orders.class);
System.out.println(orders); // com.thr.spring5.Orders@78186a70
orders.testDemo(); // 电脑:china
}
}
(3)简化方式:p 名称空间注入,使用 p 名称空间注入,可以简化 xml 配置方式(底层其实还是 set 方法注入,只是写法上做了简化)
添加 p 名称空间在配置文件中
1
2<beans xmlns:p="http://www.springframework.org/schema/p">
</beans>进行属性注入,在 bean 标签里面进行操作
1
2
3<!-- 4. p 名称空间注入(需要有 set 方法) -->
<bean id="book" class="com.thr.spring5.Book" p:bookName="九阳神功" p:bookAuthor="无名氏">
</bean>
2.4.3 基于 xml 方式注入空值和特殊符号
(1)空值
1 | <!-- 设置 null 值 --> |
(2)特殊符号
1 | <!-- 属性值中包含特殊符号, 两种方式 |
2.4.4 注入属性-外部 bean
(1)创建两个类 service 类和 dao 类
1 | public class UserService { |
1 | public class UserDaoImpl implements UserDao { |
(2)在 service 调用 dao 里面的方法
1 | public class UserService { |
(3)在 spring 配置文件中进行配置
1 | <!-- 1. service 和 dao 对象创建 --> |
(4)测试
1 | public class TestBean { |
2.4.5 注入属性-内部 bean
(1)一对多关系:eg.部门(1)和员工(多)
(2)在实体类中表示一对多关系,员工表示所属部门 –> 使用对象类型属性进行表示
1 | // 部门类 |
1 | // 员工类 |
(3)在 spring 配置文件中进行配置
1 | <!-- 内部 bean --> |
(4)测试
1 |
|
2.4.6 注入属性-级联赋值
(1)在内部 bean 的基础上重新写一个 bean 文件
第一种写法
1
2
3
4
5
6
7
8
9
10
11
12
13<!-- 级联赋值 -->
<bean id="emp" class="com.thr.spring5.bean.Emp">
<!-- 设置两个普通属性 -->
<property name="ename" value="lucy"></property>
<property name="gender" value="girl"></property>
<!-- 设置对象类型属性(用级联赋值) -->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.thr.spring5.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>第二种写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- 级联赋值 -->
<bean id="emp" class="com.thr.spring5.bean.Emp">
<!-- 设置两个普通属性 -->
<property name="ename" value="lucy"></property>
<property name="gender" value="girl"></property>
<!-- 设置对象类型属性(用级联赋值) -->
<property name="dept" ref="dept"></property>
<!-- 第二种方法, 但要注意生成 get 方法 -->
<property name="dept.dname" value="技术部"></property>
</bean>
<bean id="dept" class="com.thr.spring5.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>测试
1
2
3
4
5
6
7
8
9
10
11
public void testBean3() {
// 1. 加载 spring 配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("bean4.xml");
// 2. 获取配置创建的对象
Emp emp = context.getBean("emp", Emp.class);
emp.add(); // lucy:girl:Dept{dname='财务部'}
}
2.4.7 xml 注入集合属性
(1)分类:
- 注入数组类型属性
- 注入 List 集合类型属性
- 注入 Map 集合类型属性
(2)具体步骤
创建类,定义数组,list,set,生成对应的 set 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36public class Stu {
// 1. 数组类型的属性
private String[] courses;
// 2. list 集合类型属性
private List<String> list;
// 3. map 集合类型属性
private Map<String,String> maps;
// 4. set 集合类型属性
private Set<String> sets;
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public void setSets(Set<String> sets) {
this.sets = sets;
}
public void test() {
System.out.println(Arrays.toString(courses));
System.out.println(list);
System.out.println(maps);
System.out.println(sets);
}
}在 spring 配置文件进行配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34<!-- 1. 集合类型属性注入 -->
<bean id="stu" class="com.thr.spring5.collectiontype.Stu">
<!-- 1.1 数组类型属性注入 -->
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<!-- 1.2 list 类型属性注入 -->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<!-- 1.3 map 类型属性注入 -->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!-- 1.4 set 类型属性注入 -->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>测试
1
2
3
4
5
6
7
8
9
10
11
12public void testCollection() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
Stu stu = context.getBean("stu", Stu.class);
stu.test();
}
// 输出
// [java课程, 数据库课程]
// [张三, 李四]
// {JAVA=java, PHP=php}
// [MySQL, Redis]
(3)在集合里设置对象的值
建一个 course 类
1
2
3
4
5
6
7
8// 课程类
public class Course {
private String cname; // 课程名称
public void setCname(String cname) {
this.cname = cname;
}
}在 Stu 类中增加属性
1
2
3
4
5
6
7
8
9
10// 5. 学生所学多门课程
private List<Course> courseList;
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
public void setCourses(String[] courses) {
this.courses = courses;
}xml 中配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!-- 2.1 注入 list 集合类型, 值是对象(用 ref 标签中的 bean 属性) -->
<bean id="stu" class="com.thr.spring5.collectiontype.Stu">
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
<!-- 创建多个 course 对象 -->
<bean id="course1" class="com.thr.spring5.collectiontype.Course">
<property name="cname" value="Spring5框架课程"></property>
</bean>
<bean id="course2" class="com.thr.spring5.collectiontype.Course">
<property name="cname" value="MyBatis框架课程"></property>
</bean>
(4)把集合注入部分提取出来
在 spring 配置文件中引入名称空间 util
1
2
3
4
5
6
7<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
</beans>使用 util 标签完成 list 集合注入提取
1
2
3
4
5
6
7
8
9
10
11<!-- 1. 提取 list 集合类型属性注入 -->
<util:list id="bookList">
<value>易筋经</value>
<value>九阴真经</value>
<value>九阳神功</value>
</util:list>
<!-- 2. 提取 list 集合类型属性注入使用 -->
<bean id="book" class="com.thr.spring5.collectiontype.Book">
<property name="list" ref="bookList"></property>
</bean>测试
1
2
3
4
5
6public void testCollection2() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean2.xml");
Book book = context.getBean("book", Book.class);
book.test();
}
2.5 工厂 bean
(1)Spring 有两种类型的 bean ,一种普通 bean,另外一种工厂bean(FactoryBean 是 Spring 里头内置的一种 bean)
- 普通 bean:在配置文件中定义 bean 类型就是返回类型
- 工厂 bean:在配置文件中定义 bean 类型可以和返回类型不一样
(2)怎么做?
创建类,让这个类作为工厂 bean,实现接口 FactoryBean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class MyBean implements FactoryBean<Course> {
// 定义返回 bean
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}
public Class<?> getObjectType() {
return null;
}
public boolean isSingleton() {
return false;
}
}实现接口里面的方法,在实现的方法中定义返回的 bean 类型
1
2
3
4
5
6
7
public void test3() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean3.xml");
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}1
2<bean id="myBean" class="com.thr.spring5.factorybean.MyBean">
</bean>
2.6 Bean 的作用域和生命周期
(1)bean 的作用域
在 Spring 里面,默认情况下,bean 是单实例对象
在 Spring 里面,如何设置创建 bean 实例是单实例还是多实例
在 spring 配置文件 bean 标签里有属性(scope)用于设置单实例还是多实例
scope 常用的两个属性值(还有比如 request,session 等):
第一个值:默认值,singleton,表示单实例对象
第二个值:prototype,表示是多实例对象
1
2
3<bean id="book" class="com.thr.spring5.collectiontype.Book" scope="prototype">
<property name="list" ref="bookList"></property>
</bean>1
2
3
4
5
6
7
8
9
10public void testCollection2() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean2.xml");
Book book1 = context.getBean("book", Book.class);
Book book2 = context.getBean("book", Book.class);
// book.test();
// 地址不同 --> 多实例对象
System.out.println(book1); // com.thr.spring5.collectiontype.Book@2641e737
System.out.println(book2); // com.thr.spring5.collectiontype.Book@727803de
}
singleton 和 prototype 区别
- singleton 单实例,prototype 多实例
- 设置 scope 值是 singleton 的时候,加载 spring 配置文件时候就会创建单实例对象
- 设置 scope 值是 prototype 的时候,不是在加载 spring 配置文件时候创建单实例对象,而是在调用 getBean 方法的时候创建多实例对象
(2)bean 的生命周期
生命周期
- 从对象创建到对象销毁的过程
bean 的生命周期步骤
- 通过构造器创建 bean 实例(无参构造)
- 为 bean 的属性设置值和对其它 bean 的引用(调用 set 方法)
- 调用 bean 的初始化的方法(需要进行配置初始化的方法)
- bean 可以使用了(对象获取到了)
- 当容器关闭的时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
代码演示
编写类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class Orders {
private String oname;
// 无参构造
public Orders() {
System.out.println("第一步: 执行无参构造创建 bean 实例");
}
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步: 调用 set 方法设置属性值");
}
// 创建执行的初始化方法
public void initMethod() {
System.out.println("第三步: 执行初始化的方法");
}
// 创建执行的销毁方法
public void destroyMethod() {
System.out.println("第五步: 执行销毁的方法");
}
}配置 xml
1
2
3
4<!-- 演示 bean 的生命周期 -->
<bean id="orders" class="com.thr.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property>
</bean>测试
1
2
3
4
5
6
7
8
9
10
11
12
13public void testBean3() {
// ApplicationContext context =
// new ClassPathXmlApplicationContext("bean4.xml");
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步: 获取创建的 bean 实例对象");
System.out.println(orders);
// 手动让 bean 实例销毁
// ((ClassPathXmlApplicationContext) context).close();
context.close(); // 上面改了的话, 这里就可以直接用了(因为原来的那个没有实现这个方法, 需要转换成其子类才行?)
}
bean 的后置处理器(加上这个,bean 的生命周期一共有 7 步)
- 通过构造器创建 bean 实例(无参构造)
- 为 bean 的属性设置值和对其它 bean 的引用(调用 set 方法)
- 把 bean 实例传递 bean 后置处理器的方法(postProcessBeforeInitialization)
- 调用 bean 的初始化的方法(需要进行配置初始化的方法)
- 把 bean 实例传递 bean 后置处理器的方法(postProcessAfterInitialization)
- bean 可以使用了(对象获取到了)
- 当容器关闭的时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
演示添加 后置处理器 的效果
创建类,实现接口 BeanPostProcessor,创建后置处理器
1
2
3
4
5
6
7
8
9
10
11
12
13public class MyBeanPost implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前执行的方法");
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后执行的方法");
return bean;
}
}配置文件
1
2<!-- 配置后置处理器 -->
<bean id="myBeanPost" class="com.thr.spring5.bean.MyBeanPost"></bean>
2.7 xml 自动装配
基于 xml 自动装配用的少,一般是用注解
(1)什么是自动装配
- 根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入
(2)演示自动装配的过程
根据 属性名称(byName) 或者 属性类型(byType) 自动注入
1
2
3
4
5
6
7
8
9
10
11<!-- 实现自动装配
bean 标签属性 autowire, 配置自动装配
autowire 属性常用两个值:
byName 根据属性名称注入 => 注入值 bean 的 id 值和类属性名称一样
byType 根据属性类型注入 =>
-->
<bean id="emp" class="com.thr.spring5.autowire.Emp" autowire="byName">
<!-- 外部 bean 注入对象属性(这里是手动装配) -->
<!-- <property name="dept" ref="dept"></property> -->
</bean>
<bean id="dept" class="com.thr.spring5.autowire.Dept"></bean>
2.8 引入外部属性文件
(1)直接配置数据库信息
配置德鲁伊连接池
引入德鲁伊连接池依赖 jar 包
1
2
3
4
5
6
7<!-- 直接配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/hsp_db02"></property>
<property name="username" value="root"></property>
<property name="password" value="thr"></property>
</bean>
(2)引入外部属性文件配置数据库的连接池
创建外部属性文件,properties 格式文件,写数据库信息
1
2
3
4prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/hsp_db02
prop.username=root
prop.password=thr把外部 properties 属性文件引入到 spring 配置文件中
引入 context 名称空间
1
2
3
4<beans 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>在 spring 配置文件使用标签引入外部属性文件
1
2
3
4
5
6
7
8
9
10
11<!-- 2. 引入外部属性文件配置数据库连接池 -->
<!-- 2.1 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 2.2 配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.username}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
2.9 IOC 基于注解方式操作 Bean 管理
2.9.1 注解
(1)什么是注解
- 注解是代码特殊标记
- 格式:@注解名称(属性名称=属性值,属性名称=属性值…..)
(2)使用注解
- 注解作用在类上面,方法上面,属性上面都可
- 使用注解目的:简化 xml 配置
2.9.2 创建对象
下面四个注解的功能是一样的,都可以用来创建 bean 实例
(1)Spring 提供了四个注解
- @Component
- @Service
- @Controller
- @Repository
(2)基于注解方式实现对象的创建
引入依赖
开启组件扫描
添加 context 名称空间,然后配置 xml
1
2
3
4
5<!-- 1. 开启组件扫描
1.1 如果要扫描多个包, 多个包之间用逗号隔开
1.2 也可直接扫描包的上层目录 com.thr
-->
<context:component-scan base-package="com.thr.spring5.dao, com.thr.spring5.service"></context:component-scan>创建类,在类上面创建对象注解
1
2
3
4
5
6
7
8
9
10
11import org.springframework.stereotype.Component;
// 在注解里面 value 属性值可以省略不写, 直接 @Component
// 默认值是首字母小写的类名称
// UserService --> userService
// 类似 <bean id="userService" class=".." />
public class UserService {
public void add() {
System.out.println("service add....");
}
}测试
1
2
3
4
5
6
7
8
9public void testService() {
// 加载配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService); // com.thr.spring5.service.UserService@53ca01a2
userService.add(); // service add....
}
}
2.9.3 组件扫描中的细节配置
1 | <!-- 示例1: |
2.9.4 注入属性
(1)**@Autowired**:根据属性类型进行自动装配
第一步:把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解
1
2
3
4
5
6
7
public class UserDaoImpl implements UserDao {
public void add() {
System.out.println("dao add...");
}
}第二步:在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解
1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserService {
// 定义 dao 类型属性
// 不需要添加 set 方法
// 添加注入属性注解
private UserDao userDao;
public void add() {
System.out.println("service add....");
userDao.add();
}
}测试
1
2
3
4
5
6
7
8public void testService() {
// 加载配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService); // com.thr.spring5.service.UserService@53ca01a2
userService.add(); // service add.... dao add...
}
(2)**@Qualifier**:根据属性名称进行注入
这个注解的使用要和上面的 @Autowired 一起使用
1
2
3// 根据类型进行注入
// 根据属性名称注入(因为一个接口可能会有多个实现类)
private UserDao userDao;1
2
3
4
5
6
7
public class UserDaoImpl implements UserDao {
public void add() {
System.out.println("dao add...");
}
}
(3)**@Resource**:既可以根据类型注入,也可以根据名称注入
因为是 javax(java 的扩展包) 中的,不是 spring 里头的,所以官方不推荐使用
1
2
3
4
5import javax.annotation.Resource;
// @Resource // 根据类型进行注入
// 根据名称进行注入
private UserDao userDao;
(4)**@Value**:注入普通类型属性
1 |
|
2.9.5 完全注解开发
(1)创建配置类,替代 xml 配置文件
1 | // 作为配置类, 替代 xml 配置文件 |
(2)编写测试类
1 | public void testService2() { |
3. AOP
3.1 AOP 基本概念
(1)什么是 AOP
面向切面(方面)编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
通俗描述:不通过修改源代码的方式,在主干功能里添加新的功能,使用登录的例子说明如下,
3.2 AOP 底层原理
3.2.1 AOP 底层使用动态代理
(1)有两种情况动态代理
有接口情况,使用 JDK 动态代理
创建接口实现类代理对象,增强类的方法
没有接口情况,使用 CGLIB 动态代理
创建当前类子类的代理对象,增强类的方法
(2)JDK 动态代理实现思路
使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象
调用 newProxyInstance 方法:三个参数
第一个参数:ClassLoader 类加载器
第二个参数:Interface 增强方法所在的类,这个类实现的接口,支持多个接口
第三个参数:InvocationHandler 实现这个接口,创建代理对象,写增强的方法
3.2.2 编写 JDK 动态代理代码
(1)创建接口,定义方法
1 | // 接口 |
(2)创建接口的实现类,实现方法
1 | public class UserDaoImpl implements UserDao { |
(3)使用 Proxy 类创建接口代理对象
1 | public class JDKProxy { |
3.3 AOP 操作术语
(1)连接点
- 类里面的哪些方法可以被增强,这些方法称为连接点
(2)切入点
- 实际被真正增强的方法,称为切入点
(3)通知(增强)
- 实际增强的逻辑部分称为通知(增强)
- 通知有多种类型
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知(类似 finally)
(4)切面
- 是动作,把通知应用到切入点的过程
3.4 AOP 操作-准备工作
(1)Spring 框架一般基于 AspectJ 实现 AOP 操作
- AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spring 框架一起使用,进行 AOP 操作
(2)基于 AspectJ 实现 AOP 操作
- 基于 xml 配置文件
- 基于注解方式实现(一般使用注解)
(3)在项目工程里引入 AOP 相关依赖
(4)切入点表达式
作用:知道对哪个类里面的哪个方法进行增强
语法结构:execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
举例1:对 com.thr.dao.BookDao 类里面的 add 方法进行增强(两个点表示参数列表)
1
execution(* com.thr.dao.BookDao.add(..))
举例2:对 com.thr.dao.BookDao 类里面的 所有 方法进行增强
1
execution(* com.thr.dao.BookDao.*(..))
举例3:对 com.thr.dao 包里面的 所有 类,类里面的所有方法进行增强
1
execution(* com.thr.dao.*.*(..))
3.5 AOP 操作(AspectJ 注解)
3.5.1 基于注解操作步骤
(1)创建类,在类里面定义方法
1 | public class User { |
(2)创建增强类(编写增强逻辑)
在增强类里面,创建方法,让不同方法代表不同的通知类型
1
2
3
4
5
6
7
8// 增强类
public class UserProxy {
// 前置通知
public void before() {
System.out.println("before...");
}
}
(3)进行通知的配置
在 spring 的配置文件中,配置名称空间,开启注解扫描(可以写个类,也可以写个配置文件)
1
2
3
4
5
6
7
8
9
10
11
12
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.thr.spring5.aopAnnotation"></context:component-scan>
</beans>使用注解创建 User 和 UserProxy 对象
在增强类上面添加注解 @Aspect
在 spring 配置文件中开启生成代理对象
1
2
3
4
5<beans>
<!-- 开启 AspectJ 生成代理对象 -->
<!-- 有了这句话, 它就会去找我们上面有 @Aspect 注解的类, 自动给这个类生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>配置不同类型的通知
在增强类里面,在作为通知的方法上面添加通知类型的注解,并使用切入点表达式配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43// 增强类
//生成代理对象
public class UserProxy {
// 1. 前置通知
// @Before 注解, 表示作为前置通知, 即在我的增强方法的前面执行
public void before() {
System.out.println("before...");
}
// 2. 最终通知 after 表示在我的方法执行之后执行(即便有异常也会执行) 无论正常异常都会执行
public void after() {
System.out.println("after...");
}
// 3. 后置(返回)通知 afterReturning 表示在返回值之后执行 只有正常返回才会执行
public void afterReturning() {
System.out.println("afterReturning...");
}
// 3. 异常通知 有异常才执行
public void afterThrowing() {
System.out.println("afterThrowing...");
}
// 4. 环绕通知(方法之前和之后都执行)
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前...");
// 被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后...");
}
// 从Spring5.2.7开始,改成了根据其类型按照从高到低的优先级进行执行:@Around @Before @After @AfterReturning @AfterThrowing
}
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void testAopAnnotation() {
// 加载配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
// 得到对象
User user = context.getBean("user", User.class);
// 调用 user 中的 add 方法
user.add();
}
// 结果
/*
环绕之前...
before...
add....
环绕之后...
after...
afterReturning...
*/
3.5.2 基于注解操作细节
(1)相同的切入点抽取(用注解 @Pointcut)
1 | // 相同的切入点进行抽取 |
(2)若有多个增强类对同一个方法进行增强,可以设置增强类优先级
- 在增强类的上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
1 |
|
(3)完全使用注解开发
创建配置类,不需要创建 xml 配置文件
1
2
3
4
5
6// 完全注解开发
// 配置类
// 开启组件扫描
// 开启 Aspect 生成代理对象
public class ConfigAop {
}
3.6 AOP 操作(AspectJ 配置文件)
了解即可(一般不用)
(1)创建两个类,增强类和被增强类,创建方法
1 | // 被增强类 |
1 | // 增强类 |
(2)在 spring 配置文件中创建两个类对象
(3)在 spring 配置文件中配置切入点
1 | <beans> |
(4)测试
1 |
|
4. JdbcTemplate
4.1 概述
(1)什么是 JdbcTemplate?
- Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 可以方便实现对数据库的操作
(2)准备工作
引入相关 jar 包
在 spring 配置文件中配置数据库连接池
1
2
3
4
5
6
7
8<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///spring_db?characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="thr" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>配置 JdbcTemplate 对象,注入 DataSource
1
2
3
4
5<!-- JdbcTemplate 对象 -->
<bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入 dataSource(用 set 方法注入) -->
<property name="dataSource" ref="dataSource"></property>
</bean>创建 service 类,创建 dao 类,在 dao 注入 jdbcTemplate 对象(service层要用dao层的东西 所以需要注入dao对象 dao层需要连接池来连接数据库 所以要注入 jdbcTemplate 对象)
配置文件中开启组件扫描
1
2<!-- 开启组件扫描 -->
<context:component-scan base-package="com.thr"></context:component-scan>service 里面
1
2
3
4
5
6// 创建对象
public class BookService {
// 注入 dao
private BookDao bookDao;
}Dao 里面
1
2
3
4
5
6
public class BookDaoImpl implements BookDao {
// 注入 JdbcTemplate
private JdbcTemplate jdbcTemplate;
}
4.2 操作数据库-添加功能
(1)对于数据库的表创建实体类
(2)编写 service 和 dao
- 在 dao 里进行数据库添加操作
- 调用 JdbcTemplate 对象里面 update 方法实现添加操作
- 有两个参数,第一个参数:sql 语句;第二个参数:可变参数,设置 sql 语句值