0%

Spring

1、 Spring

1.1、 简介

  • Spring:春天 ——> 给软件行业带来了春天!

  • 2002年,首次推出了 Spring 框架的雏形:interface21 框架!

  • Spring 框架即以 interface21 框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版。

  • Rod Johnson,Spring Framework 创始人,著名作家,还是悉尼大学的音乐学博士。

  • spring理念:使现有的技术更加容易使用,本身是个大杂烩,整合了现有的技术框架!

  • SSH:Struts2 + Spring + Hibernate

  • SSM:SpringMvc + Spring + Mybatis

官网:https://spring.io/projects/spring-framework#overview

GitHub:https://github.com/spring-projects/spring-framework

maven 依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>

1.2、优点

  • Spring 是一个开源免费的框架(容器)!
  • Spring 是一个轻量级的、非入侵式的框架!
  • 控制反转(IOC),面向切面编程(AOP)!
  • 支持事务的处理,对框架整合的支持!

总结一句话:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!

1.3、组成

1.4、 拓展

现代化的 Java 开发,说白就是基于 Spring 的开发!

  • Spring Boot
    • 一个快速开发的脚手架。
    • 基于 SpringBoot 可以快速地开发单个微服务。
    • 约定大于配置!
  • Spring Cloud
    • SpringCloud 是基于 SpringBoot 实现的。

因为现在大多数公司都在使用 SpringBoot 进行快速开发,学习 SpringBoot 的前提,需要完全掌握 Spring 及 SpringMVC!起到承上启下的作用!

Spring弊端:发展了太久以后,违背了原来的理念!配置十分繁琐,人称配置地狱

2、 IOC理论推导

2.1、MVC三层架构

MVC (模型视图控制器)即 Model、View 和 Controller。

Model

  • 业务处理:业务逻辑(Service)
  • 数据持久层:CRUD(Dao)

View

  • 展示数据
  • 提供链接发起路由请求(a,img,form…)

Controller

  • 接受用户请求,request:请求参数、Session信息…
  • 交给业务层处理请求
  • 控制视图跳转

例:

1
登录--->接受用户的登录请求--->处理用户的请求(获取用户登录的参数:username,password)--->交给业务层处理登录业务(判断用户名账号密码是否正确:事务)--->Dao层查询用户名和密码是否正确--->数据库

2.2、实例

  1. UserDao 接口

    1
    2
    3
    public interface UserDao {
    void show();
    }
  2. UserDaoImpl 实现类

    1
    2
    3
    4
    5
    6
    public class UserDaoImpl implements UserDao{
    @Override
    public void show() {
    System.out.println("默认获取用户的数据!");
    }
    }
  3. UserService 业务接口

    1
    2
    3
    public interface UserService {
    void show();
    }
  4. UserServiceImpl 业务实现类

    1
    2
    3
    4
    5
    6
    7
    8
    public class UserServiceImpl implements UserService{
    private UserDao userDao = new UserDaoImpl();

    @Override
    public void show() {
    userDao.show();
    }
    }
  5. MyTest 测试类

    1
    2
    3
    4
    5
    6
    7
    public class MyTest {
    public static void main(String[] args) {
    // 用户实际调用的是业务层,Dao层他们不需要接触
    UserService userService = new UserServiceImpl();
    userService.show();
    }
    }
  6. Console 结果正常

    1
    2
    3
    默认获取用户的数据!

    Process finished with exit code 0

如果这时我们需要添加业务呢?

  1. 新增 Dao 实现类

    1
    2
    3
    4
    5
    6
    public class UserDaoMysqlImpl implements UserDao{
    @Override
    public void show() {
    System.out.println("Mysql获取用户的数据!");
    }
    }
  2. 修改业务实现类

    1
    2
    3
    4
    5
    6
    7
    8
    public class UserServiceImpl implements UserService{
    private UserDao userDao = new UserDaoMysqlImpl();

    @Override
    public void show() {
    userDao.show();
    }
    }
  3. Console 结果正常

    1
    2
    3
    Mysql获取用户的数据!

    Process finished with exit code 0

但每新增一个业务操作,我们就要去更改我们业务类吗?如果程序代码量十分庞大,修改一次的成本代价将十分昂贵,这显然不可能。

为此我们需要 set注入。

1
2
3
4
5
6
7
8
9
10
11
12
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoImpl();

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

@Override
public void show() {
userDao.show();
}
}
1
2
3
4
5
6
7
8
public class MyTest {
public static void main(String[] args) {
// 用户实际调用的是业务层,Dao层他们不需要接触
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(new UserDaoMysqlImpl());
userService.show();
}
}
  • 之前,程序是主动创建对象!控制权在程序员手上!
  • 现在,使用了 set 注入后,程序不再具有主动性,而是变成了被动的接收对象!

这种思想,从本质上解决了问题,我们不用再去管理对象的创建了。系统的耦合性大大降低,可以更加专注在业务的是线上!这就是IOC的原型!

2.3、IOC本质

控制翻转Ioc(Inversion of Control),是一种设计思想,DI (依赖注入)是一种实现Ioc的一种方法。在没有Ioc的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制翻转后将对象的创建转移给第三方,个人认为所谓控制反转就是获得依赖对象的方式反转了

采用 XML 方式配置 Bean 的时候,Bean 的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean 的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在 Spring 中实现的控制反转的是 Ioc 容器,其实现方式是依赖注入(Dependency Injection,DI)

3、HelloSpring

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Hello {
private String name;

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Hello{" +
"name='" + name + '\'' +
'}';
}
}

xml

1
2
3
4
5
6
7
8
9
<?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="hello" class="Hello">
<property name="name" value="余千禧"/>
</bean>
</beans>

测试类

1
2
3
4
5
6
7
8
9
10
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello);
}
}

打印结果

1
2
3
Hello{name='余千禧'}

Process finished with exit code 0

在上述代码中,自始至终我都没有使用 new 来创建 Hello 对象,对象创建和属性赋值全权交由 IOC 容器解决,它使得程序从原本主动的编程转变为被动的接受。

至目前为止,我们已经彻底不用再到程序中去改动了,要实现不同的操作,只需要在 xml 配置文件中进行修改,所谓 IOC,即对象由 Spring 创建、管理和装配!

4、IOC创建对象的方式

实体类

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
public class User {
private String name;
private String id;
private int score;

public void setName(String name) {
this.name = name;
}

public void setId(String id) {
this.id = id;
}

public void setScore(int score) {
this.score = score;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id='" + id + '\'' +
", score=" + score +
'}';
}

public User(String name, String id, int score) {
this.name = name;
this.id = id;
this.score = score;
System.out.println("User类已被有参构造方法创建!");
}

public User(){
System.out.println("User类已被无参构造方法创建!");
}
}

4.1、使用无参构造方法创建对象(默认)

1
2
3
4
5
6
7
<?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="User"/>
</beans>

4.2、 使用有参构造方法创建对象

(1) 根据参数名赋值

1
2
3
4
5
6
7
8
9
10
11
<?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="User">
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="yqx"/>
<constructor-arg name="score" value="10"/>
</bean>
</beans>

(2) 根据参数下标赋值

1
2
3
4
5
6
7
8
9
10
11
<?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="User">
<constructor-arg index="0" value="ysy"/>
<constructor-arg index="1" value="2"/>
<constructor-arg index="2" value="20"/>
</bean>
</beans>

(3) 根据参数类型赋值(相同则按顺序赋值)

1
2
3
4
5
6
7
8
9
10
11
<?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="User">
<constructor-arg type="java.lang.String" value="mc"/>
<constructor-arg type="java.lang.String" value="3"/>
<constructor-arg type="int" value="30"/>
</bean>
</beans>

注:在默认情况下,只要注册在 beans.xml 中,则无论是否调用 getBean 来获取对象都会在读取配置文件的时候创建好对象,因此返回的都是这个事先被创建好的对象

此处还没运行到 getBean 时,就已经打印了创建对象的语句。

可以看到 user1 和 user2 是同一个对象。

5、Spring 配置

5.1、bean 的配置

1
2
3
4
5
6
7
8
9
10
11
<!--
id: bean 的唯一标识
class: bean 对象所对应的全限定名(包名 + 类型)
name: 别名,但不唯一,可以用符号分隔
-->

<bean id="user" class="User" name="u1,u2 u3;u4">
<constructor-arg type="java.lang.String" value="mc"/>
<constructor-arg type="java.lang.String" value="3"/>
<constructor-arg type="int" value="30"/>
</bean>

5.2、别名

1
2
3
4
5
<!--
和 name 功能一致,可以给别名再取别名
-->
<alias name="user" alias="u5"/>
<alias name="u5" alias="u6"/>

5.3、import

import 一般用于团队开发使用,可以将多个配置文件导入合并成一个

假设,现在项目中有多个人开发,这三个人负责不同的类开发,不同的类需要注册在不同的 bean 中,我们可以用 import 将所有人的 beans.xml 合并成一个!

  • beans1.xml
  • beans2.xml
  • beans3.xml
  • applicaionContext.xml

applicationContext.xml 作为汇总的配置文件只需要在其中导入其它配置文件即可。

注:有多个重复的 bean 时,会合并一个。

1
2
3
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>

使用的时候直接使用总的配置文件就行。

6、依赖注入

6.1、构造器注入

详情查看IOC 创建对象的方式

6.2、Set方式注入 【重点】

  • 依赖注入:Set 注入!
    • 依赖:bean 对象的创建依赖于容器!
    • 注入:bean 对象中的所有属性由容器来注入!
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?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="address" class="Address">
<property name="address">
<value>宝山</value>
</property>
</bean>

<bean id="student" class="Student">
<!--普通值注入-->
<property name="name" value="yqx"></property>
<property name="age" value="20"></property>

<!--null-->
<property name="wife">
<null></null>
</property>

<!--引用类型-->
<property name="address" ref="address"></property>

<!--list-->
<property name="hobbies">
<list>
<value>唱歌</value>
<value>跳舞</value>
<value>逛B站</value>
</list>
</property>

<!--set-->
<property name="games">
<set>
<value>风来之国</value>
<value>以撒的结合</value>
<value>杀戮尖塔</value>
</set>
</property>

<!--array-->
<property name="books">
<array>
<value>高等数学</value>
<value>线性代数</value>
<value>概率论</value>
</array>
</property>

<!--map-->
<property name="card">
<map>
<entry key="The Sun" value="太阳"></entry>
<entry key="The Star" value="星星"></entry>
<entry key="The Hanged Man" value="倒吊人"></entry>
</map>
</property>

<!--properties-->
<property name="info">
<props>
<prop key="height">175</prop>
<prop key="weight">60</prop>
</props>
</property>
</bean>
</beans>

打印内容

1
2
3
4
5
6
7
8
9
10
11
12
Student{
name='yqx',
address=Address{address='宝山'},
age=20,
books=[高等数学, 线性代数, 概率论],
hobbies=[唱歌, 跳舞, 逛B站],
card={The Sun=太阳, The Star=星星, The Hanged Man=倒吊人},
games=[风来之国, 以撒的结合, 杀戮尖塔],
wife='null',
info={height=175, weight=60}}

Process finished with exit code 0

6.3、拓展方式注入

p (parameter) 命名空间,即 set 方式注入。

1
2
3
4
5
6
7
8
xmlns:p="http://www.springframework.org/schema/p"

<!--以下两种方式等价-->
<bean id="user1" class="User" p:name="yqx" p:age="18"/>
<bean id="user1" class="User">
<property name="name" value="yqx"/>
<property name="age" value="18"/>
</bean>

c (constructor) 命名空间,即构造器注入。

1
2
3
4
5
6
7
xmlns:c="http://www.springframework.org/schema/c"

<bean id="user2" class="User" c:name="deflory" c:age="20"/>
<bean id="use2" class="User">
<constructor-arg name="name" value="deflory"/>
<constructor-arg name="age" value="20"/>
</bean>

命名空间注入简化了配置文件的代码,但需要显示声明命名空间。

1
2
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

6.4、Bean 的作用域

作用域 描述
singleton 该作用域将 bean 的定义限制在每一个 IoC 容器中的一个单一实例(默认)。
prototype 该作用域将单一 bean 的定义限制在任意数量的对象实例。
request 该作用域将 bean 的定义限制为 HTTP 请求。只在 web-aware Spring ApplicationContext 的上下文中有效。
session 该作用域将 bean 的定义限制为 HTTP 会话。 只在web-aware Spring ApplicationContext的上下文中有效。
global-session 该作用域将 bean 的定义限制为全局 HTTP 会话。只在 web-aware Spring ApplicationContext 的上下文中有效。

singleton 单例模式(默认)

1
2
3
4
<bean id="user" class="User" scope="singleton">
<constructor-arg name="name" value="deflory"/>
<constructor-arg name="age" value="20"/>
</bean>
1
2
3
4
5
6
7
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user1 = context.getBean("user", User.class);
User user2 = context.getBean("user", User.class);
System.out.println(user1 == user2); // true
}

prototype 原型模式

1
2
3
4
<bean id="user" class="User" scope="prototype">
<constructor-arg name="name" value="deflory"/>
<constructor-arg name="age" value="20"/>
</bean>
1
2
3
4
5
6
7
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user1 = context.getBean("user", User.class);
User user2 = context.getBean("user", User.class);
System.out.println(user1 == user2); // false
}

7、Bean 的自动装配

7.1、ByName自动装配

会自动寻找与属性类名相同的 bean_id (首字母小写)。

1
2
3
4
5
6
7
8
9
<?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="cat" class="Cat"/>
<bean id="dog" class="Dog"/>
<bean id="person" class="Person" autowire="byName"/>
</beans>

7.2、ByName自动装配

会自动寻找与属性相同类型的 bean (该类型必须唯一)。

1
2
3
4
5
6
7
8
9
<?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 class="Cat"/>
<bean class="Dog"/>
<bean id="person" class="Person" autowire="byType"/>
</beans>

总结:在使用 ByName 时,需要指定和类名小写相同的 bean_id;

​ 在使用 ByType 时,与属性同类型的 bean 必须唯一。

7.3、使用注解实现自动装配

使用前提:导入约束并开启注解支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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:annotation-config/>
<!-- bean definitions go here -->

</beans>

@Autowired 注解

默认使用 ByType 的方式,不行则使用 ByName。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.springframework.beans.factory.annotation.Autowired;

public class Person {
@Autowired
private Dog dog;
@Autowired
private Cat cat;

public Dog getDog() {
return dog;
}

public Cat getCat() {
return cat;
}
}

可以使用 @Qualifier 来指定 bean_id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class Person {
@Autowired
private Dog dog;

@Autowired
@Qualifier(value = "cat11")
private Cat cat;

public Dog getDog() {
return dog;
}

public Cat getCat() {
return cat;
}
}

@Resource 注解 (Java 原生注解)

默认使用 ByName 的方式,不行则使用 ByType。

功能更为强大,可以指定 bean_id,相当于上述两个注解的结合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import javax.annotation.Resource;

public class Person {
@Resource
private Dog dog;

@Resource(name = "cat11")
private Cat cat;

public Dog getDog() {
return dog;
}

public Cat getCat() {
return cat;
}
}

8、使用注解开发

必须指定扫描带有注解的包才能使用

1
<context:component-scan base-package="com.yqx.pojo"/>

8.1、属性注入

  • @Component 表明该类被 Spring 托管,id 为类名首字母小写。

  • @Value 给属性注入值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.yqx.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {
@Value("yqx")
private String name;

public String getName() {
return name;
}
}

8.2、注解的衍生

  • @Component 有几个衍生注解,在 web 开发中,会按照 mvc 三层架构分层!
    • Dao 【@Repository】
    • Service 【@Service】
    • Controller 【@Controller】

这四个注解功能都是一样的,都是将某个类注册到 Spring 中,装配 Bean。

8.3、作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.yqx.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class User {
@Value("yqx")
private String name;

public String getName() {
return name;
}
}

8.4、小结

xml 与 注解:

  • xml 更加万能,适用于任何场合!维护简单方便,只需修改配置文件。
  • 注解维护繁琐,分散在各个实体类中。

xml 与 注解最佳实践:

  • xml 用来管理 bean。
  • 注解只负责完成属性注入。

9、使用 Java 的方式配置 Spring

@Configuration

基于 Java 的配置选项,可以是你在不用配置 XML 的情况下编写大部分的 Spring。

  • @Configuration 用于类上,表示这个类可以使用 Spring IOC 容器作为 Bean 定义的来源。
  • @Bean 用于方法上,这个方法要返回一个对象,而该对象会被注册为在 Spring 应用程序上下文中的 Bean。

实体类

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {
@Value("deflory")
private String name;

public String getName() {
return name;
}
}

Java 配置类

1
2
3
4
5
6
7
8
9
10
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
@Bean
public User getUser(){
return new User();
}
}

上面的 Java 配置类等同于下面的 XML 配置。

需要注意的是,bean_id 和 @Bean 修饰的方法名一致。

1
2
3
<beans>
<bean id="getUser" class="User"/>
</beans>

注:虽然 @Bean 中是 return new User(),但默认还是单例模式。

@Import

@import 注解允许从另一个配置类中加载 @Bean 定义。

另一个配置类

1
2
3
4
5
6
7
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}

主配置类

1
2
3
4
5
6
7
8
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}

测试类

1
2
3
4
5
6
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(ConfigB.class);
A a = context.getBean(A.class);
B b = context.getBean(B.class);
}

10、代理模式

代理模式的分类:

  • 静态代理
  • 动态代理

10.1、 静态代理

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决。
  • 真实角色:被代理的角色。
  • 代理角色:代理真实角色,添加额外操作。
  • 客户:访问代理对象的人。

代码:

  1. 接口

    1
    2
    3
    4
    5
    package demo01;

    public interface Rent {
    void rent();
    }
  2. 真实角色

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package demo01;

    public class Landlord implements Rent{

    @Override
    public void rent() {
    System.out.println("房东要出租房子");
    }
    }
  3. 代理角色

    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
    package demo01;

    public class Agent implements Rent{
    Rent landlord;

    public Agent(Rent landlord) {
    this.landlord = landlord;
    }

    @Override
    public void rent() {
    findRenter();
    landlord.rent();
    negotiation();
    fee();
    }

    public void fee(){
    System.out.println("中介收取中介费");
    }

    public void findRenter(){
    System.out.println("中介寻找租客");
    }

    public void negotiation(){
    System.out.println("中介签订合同");
    }
    }
  4. 客户端访问代理角色

    1
    2
    3
    4
    5
    6
    7
    8
    package demo01;

    public class MyTest01 {
    public static void main(String[] args) {
    Rent agent = new Agent(new Landlord());
    agent.rent();
    }
    }

代理模式的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务。
  • 公共业务交代代理角色,实现了业务的分工。
  • 公共业务发生拓展的时候,方便集中管理。

缺点:

  • 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低。

10.2、动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,而非程序员手写的!
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理。
    • 基于接口:JDK 动态代理 【下述使用】
    • 基于类:cglib
    • java 字节码实现:javasist

需要了解两个类:

  • Proxy:代理
  • InvocationHandler:调用处理程序

优点:

  • 包含静态代理的优点。
  • 一个动态代理类代理的是一个接口,对应一类业务。

11、AOP

Spring 框架的一个关键组件是面向切面的编程(AOP)框架。面向切面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点。跨一个应用程序的多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。

11.1、通知类型

通知 描述
前置通知 在一个方法执行之前,执行通知。
后置通知 在一个方法执行之后,不考虑其结果,执行通知。
返回后通知 在一个方法执行之后,只有在方法成功完成时,才能执行通知。
抛出异常后通知 在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
环绕通知 在建议方法调用之前和之后,执行通知。

11.2、在 Spring 中使用 Aop

导入依赖包

1
2
3
4
5
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>

添加约束

1
2
3
4
5
6
7
8
9
10
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

(1) 原生 Spring Api 接口

xml 配置

1
2
3
4
5
6
7
8
9
10
11
<bean id="userService" class="com.yqx.service.UserServiceImpl"/>
<bean id="log" class="com.yqx.log.Log"/>

<!--方式一:使用原生的Spring API接口-->
<!--配置aop-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.yqx.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
</aop:config>

日志类,这里采用了多实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.yqx.log;

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice, AfterReturningAdvice {

@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(method.getName() + "方法执行完毕,返回值为:" + returnValue);
}

@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "中的" + method.getName() + "方法被调用了!");
}
}

Test 测试类

1
2
3
4
5
6
7
8
9
10
11
12
import com.yqx.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 动态代理代理的是接口,因此只能转为接口类型
UserService userService = (UserService) context.getBean("userService");
userService.add();
userService.query();
}
}

(2) 自定义类

日志类

1
2
3
4
5
6
7
8
9
10
11
package com.yqx.log;

public class MyLog {
public void before(){
System.out.println("前置通知");
}

public void after(){
System.out.println("后置通知");
}
}

xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<bean id="userService" class="com.yqx.service.UserServiceImpl"/>
<bean id="myLog" class="com.yqx.log.MyLog"/>

<aop:config>
<!--自定义切面-->
<aop:aspect ref="myLog">
<!--切入点-->
<aop:pointcut id="pointout" expression="execution(* com.yqx.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="pointout"/>
<aop:after method="after" pointcut-ref="pointout"/>
</aop:aspect>
</aop:config>

虽然方便,但无法获取类与方法的信息,功能不够强大。

(3) 注解

日志类

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
package com.yqx.log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AnnotationLog {
@Before("execution(* com.yqx.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("前置通知");
}

@After("execution(* com.yqx.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("后置通知");
}

@Around("execution(* com.yqx.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前");
System.out.println(joinPoint.getSignature());
System.out.println(joinPoint.getArgs());
joinPoint.proceed();
System.out.println("环绕后");
}
}

xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="userService" class="com.yqx.service.UserServiceImpl"/>
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.yqx.log"/>
</beans>

打印内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
环绕前
void com.yqx.service.UserService.add()
[Ljava.lang.Object;@6c6cb480
前置通知
添加了一条数据
后置通知
环绕后
环绕前
void com.yqx.service.UserService.query()
[Ljava.lang.Object;@3c46e67a
前置通知
查询了一条数据
后置通知
环绕后

Process finished with exit code 0

12、Mybatis-Spring

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

MyBatis-Spring 需要以下版本:

MyBatis-Spring MyBatis Spring Framework Spring Batch Java
2.0 3.5+ 5.0+ 4.0+ Java 8+
1.3 3.4+ 3.2.2+ 2.1+ Java 6+

12.1、整合方式一

所需依赖如下

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
<dependencies>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--mybatis与spring整合-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!--spring数据库连接-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
</dependencies>

同属 org.springframework 包下的两个依赖必须保证版本一致!!!不然会报错!!!

mybatis.xml,具体功能都整合到 spring-mybatis.xml 下,这里只放别名以及 setting 设置。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.yqx.pojo"/>
</typeAliases>
</configuration>

spring-mybatis.xml,一般存放和 sqlSession 有关的 bean,写好一般就不会再修改了。

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
<?xml version="1.0" encoding="UTF8"?>
<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="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>

<!--使用 SqlSessionFactoryBean来创建 SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--在classpath后面的*必不可少,缺少*的话后面的通配符不起作用-->
<property name="mapperLocations" value="classpath*:com/yqx/mapper/*.xml"/>
<!--绑定mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>

<!--使用SqlSessionTemplate创建sqlSession对象-->
<bean id="sessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>

applicationContext.xml,作为汇总的 xml,同时用于存放自己的 bean。

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF8"?>
<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">

<import resource="spring-mybatis.xml"/>
<bean id="userMapper" class="com.yqx.mapper.UserMapperImpl">
<property name="sqlSessionTemplate" ref="sessionTemplate"/>
</bean>
</beans>

因为要将对象交由 spring 的 ioc 容器托管,因此需要给 mapper 文件添加实现类,注入 SqlSessionTemplate。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.yqx.mapper;

import com.yqx.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;

public class UserMapperImpl implements UserMapper{

private SqlSessionTemplate sqlSessionTemplate;

public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate){
this.sqlSessionTemplate = sqlSessionTemplate;
}

@Override
public List<User> getAllUsers() {
return sqlSessionTemplate.getMapper(UserMapper.class).getAllUsers();
}
}

mapper.xml 如果不放在 resources 下,spring 是找不到的!!!报错警告: Property 'mapperLocations' was specified but matching resources are not found. 除非显式定义资源的位置!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>

12.2、整合方式二

UserMapperImpl 继承了 SqlSessionDaoSupport 就可以直接获取到 sqlSession。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.yqx.mapper;

import com.yqx.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> getAllUsers() {
return getSqlSessionTemplate().getMapper(UserMapper.class).getAllUsers();
}
}

因此 spring-config.xml 中并不需要托管 sqlSession,只需要在创建 UserMapperImpl 的 bean 的时候注入 sqlSessionFactory 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath*:com/yqx/mapper/*.xml"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>

</beans>

applicationContext

1
2
3
4
5
6
7
8
9
10
<?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">

<import resource="spring-mybatis.xml"/>
<bean id="userMapper" class="com.yqx.mapper.UserMapperImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>

13、声明式事务

13.1、ACID原则

  • 原子性(Atomic):事务中的各项操作,要么都做要么都不做,任何一项操作的失败都会导致整个事务的失败。
  • 一致性(Consistency):事务前后数据的完整性必须保持一致。
  • 隔离性(Isolation):事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事物的操作数据所干扰,多个并发事物之间要相互隔离。
  • 持久性(Durability):持久性是指一个事务一旦被提交,他对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

这里我们创建了一个混合操作 mixedOperations,其中 deleteUserById 的 sql 语句我故意写错了,而按照原子性来说,这个事务删除失败了,理应前面的插入数据也应该进行回滚。

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
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
public void mixedOperations() {
System.out.println("事务处理前");
for (User user : getAllUsers()) {
System.out.println(user);
}
System.out.println("======================================");
addUser(new User(1, "test", 0));
deleteUserById(1);
System.out.println("事务处理后");
for (User user : getAllUsers()) {
System.out.println(user);
}
}

@Override
public List<User> getAllUsers() {
return getSqlSessionTemplate().getMapper(UserMapper.class).getAllUsers();
}

@Override
public int addUser(User user) {
return getSqlSessionTemplate().getMapper(UserMapper.class).addUser(user);
}

@Override
public int deleteUserById(int id) {
return getSqlSessionTemplate().getMapper(UserMapper.class).deleteUserById(id);
}
}

但实际上,我的数据表中的的确确插入了这个 test 用户,这个时候就需要声明式事务来帮我们管理。

导入约束

1
2
3
4
5
6
7
8
9
10
11
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

配置声明式事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!--结合AOP实现事务的织入-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--为方法配置事务-->
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.yqx.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>