梦入琼楼寒有月,行过石树冻无烟

📖 earlier posts 📖

Spring boot Security

Spring boot Security即Spring推出的一个专门针对于Spring应用系统的安全框架,充分利用了spinrg框架的整体功能为Spinrg应用提供了安全访问控制的解决方案。在Spring boot Security之中或是网络安全中有两个非常重要的概念分别为**授权(Authorization)认证(Authentication)**两个,授权即确定用户在当前应用系统下所有的功能和权限,而认证即确认是否有级别,或是条件来进入当前系统身份。

Spring Security 请求授权

在Spring Security之中的适配器类里,通过重写configure(HTTOSecurity http)方法完成用户授权,在configure(HttpSecurity http)方法中,在匹配请求路径后,可以针对当前用户对请求进行安全的处理,Spring boot提供的安全方法如:

ID DA FA
anyRequest 匹配所有请求路径
access(String attribute) Spring EL表达式结果为true时可以访问
anonymous() 匿名可以访问
authenticated() 用户登入后可以访问
denyAll() 用户不能访问
fullyAuthenticated() 用户完全认证可以访问
permitAll() 任何用户都可以访问
remernberMe() 允许通过remember-me登入的用户访问
hasAnyAuthority(String) 参数表示权限 用户与群线任一权限相同即可访问
hasAnyRole(String) 参数表示角色 用户角色与其中任一角色相爱嗯痛即可访问
hasAuthority(String authority) 参数表示权限 用户权限与参数相同即可访问
hasIpAddress(String ipaddressExpression) 参数表示IP地址 用户IP和参数匹配即可访问
hasRole(String role) 参数表示角色 用户角色与参数相同即可访问

Spring boot Security 核心类

Spring boot Security 核心类共包括Authentication、SecurityContextHolder、UserDetails、UserDetailsService、GrentedAuthority、DaoAuthenticatioProvider、PasswordEncoder等核心类分别主要用于:

Authentication

authentication用于封装用户认证的信息接口,在用户登入之前spring Security将相关信息会分装成一个Authentication具体的实现类对象,登入后将会生成一个更加全面和包含用户权限的Authentication对象,之后将对象保存在SecurityContextHolder所持有的SecurityContext中。

SecurityContextHolder

SecurityContextHolder及是SecurityContext的所有类,SecurityContext中将会包含当前认证用户的详细信息。Spring Security使用一个Authentication对象描述当前认证用户的相关信息。

UserDetails

UserDetails是spring Security的一个核心接口,该接口定义了一些可以用户获取用户名、密码、权限等与认证信息相关的方法。

UserDatailsService

UserDatails是通过UserDatailsService的loadUserByUsername(String username)方法加载的,而UserDatailsService也是一个接口,也需要实现自己的价值来加载自定义UserDatails。所以在等于认证的时候Spring Security将会通过UserDatailsService的loadUserByUsername方法来获取对应的UserDatails进行验证。
而验证后将会通过该UserDetails赋予认证通过的Auhentication的principal,之后Auhentication保存在SecurityContetHelder之中,在开发的过程中,如果需要使用用户信息,可以通过SecurityContetHelder获取存放在SecurityContext中存放于UserDetails实力里的Authentication、principal方法。

GrantedAuthority

GrantedAuthority是一个接口,通常是通过UserDetailsService进行加载,然后赋值给UserDetails。在Authentication的getAuthorities()方法可以返回当前Authentication对象拥有的权限,即返回一个GrantedAuthority类型的数组。

DaoAuthenticationProvider

在Spring Security的安全框架中,默认使用DaoAuthenticationProvider来实现AuthenticationProvider接口进行用户的认证处理。DaoAuthenticationProvider进行认证的时候需要一个UserDrtailsService来获取用户信息的UserDetails。

PasswordEncoder

在Spring Security安全框架中,通过PasswordEncoder接口完成对密码的加密,PasswordEncoder有多种实现,其中包含MD55、SHA-256的加密等,开发时可直接使用即可。在Spring boot之中,使用BCryptPasswordEncoder加密是比较不错的选择,BCryptPasswordEncoder使用BCrypt的强散列哈兮加密进行实现,并可由客户端指定加密的强度,从字面意思上来理解就是说强度越高、安全性就越高。

本书作为Spring boot 和Thymeleaf基础入门指导书,暂不对Spring Security有过多描述,作者也曾尝试着实现Spring Security,但实在太多,已经超出了本书的字数和章节范围,有兴趣的读者可自行阅读,下一次将会另出一本专门针对Spring Security的书籍。

Spring boot and Thymeleaf Data visit this Spring Data and Hibernate

本章节主要用于讲述和演示Spring boot配合Thymeleaf做数据访问的相关案例和技术演示,即通过本章章节“Spring boot and Thymeleaf Data visit”即“spring boot 和 Thymeleaf数据访问”。

本章将通过Spring Data JPA、Spring boot MyBatis、Spring boot REST、Spring boot MongoDB、Spring boot Redis、以及数据缓存Cache来进行讲解。Spring Data 是Spring 访问数据库的一大堆解决方案,包含大量的关系型数据库以及非关系型数据库的数据访问解决方案。

Spring Data JPA

Spring Data JPA从名称中我们可以得到,Spring Data JPA是Spring Data的子项目,在学习Spring Data Jpa之前,我们先学习下Hibernate,与其说是Spring Data JPA,更不如说是Hibernate,因为Spring Data JPA的功能,基本上都是由Hibernate所实现的。

从Spring Data JPA中我们可得知,Spring Data JPA的全程是Java Persistence API,即Java持久性映射,而Java持久性映射则是由官方提出的Java持久化规范,通过注解或XML描述对象的一种映射关系,将内存中的实体对象来操纵数据库,Spring Data JPA通过提供基于JPA的仓库极大的简化了JAP的写法,在不写实现类的时候,即可实现数据库的访问操作。

Spring Data 体系结构

ID DA FA FD
1 Persistence 用于获取EntityManagerFactory实例的静态方法类 EntityManagerFactory
2 EntityManagerFactory 是一个EntityManager的工厂类,创建并管理许多EntityManager的实例 EntityManager
3 EntityManager 是一个接口,用于查询实例并控制对象的持久化操作
4 Entity Entity是作为记录存储在数据库中的持久性对象
5 Persistence Unit Persistence Unit 定义了一组所有的实体类,在应用程序中由 EntityManager实例来管理(实体类集表示存储中的数据) EntityManager
6 EntityTransaction EntityTransaction class与EntityManager class有对应的关系,对于每个EntityManage,都是由EntityTransaction类进行维护 EntityTransaction
7 Query 由每一个JPA大熔炉实现的借口,用于获取满足条件的关系对相爱

Spring Data 对象映射关系

在对象映射关系(Object Relational Mapping)中,Java Object到数据库表的映射过程被称之为对象关系映射,对象关系映射被充当关系数据库和Java class\object之间的桥梁

Hibernate

Hibernate是一个开源的对象关系映射框架,对JDBC进行了非常轻量级的对象封装,从文档中我们可以得知,他将普通Java对象,与数据库建立映射关系,是一个全自动的对象关系映射框架,Hibernate由于通过普通Java对象与数据库建立映射关系,所以从而可达到自动生成SQL语句并执行的功能,让开发人员摆脱烦恼,一盏到底。

Hibernate and Spring Data JPA 区别

Jap可以理解为是一个标准接口,而Hibernate则是一个实现,但是功能什么的都是JPA所提供的,Hibernate主要通过三个组件来实现,分别时hibernate-annotation、hibernate-entitymanager、Hibernate三个组件来实现:

hibernate-annotation

hibernate-annotation是Hibernate支持annotatio方法配置的基础,主要包括了标准的Jpa annotation以及Hibernate自身功能的annotation。

hibernate-core

hibernate-core是Hibernate的核心,主要功能则是实现,因为最主要的,还是需要看到的。

hibermate-entitymanager

hibermate-entitymanager实现了标准的JPA,是一个hibernate-core和JPA的适配器,并不直接提供对象映射关系(Object Relational Mapping),而是对hibernate-core进行封装,让他符合JPA的代码规范。

Spring data jpa + hibernate

在项目创建完成后,通过访问http://localhost:8080/demo/save/来进行插入数据,之后当显示插入成功后访问http://localhst:8080/demo/findAll来查看所有用户信息,当然我们还可以通过访问http://localhost:8080/demo/findByUsernameLike?username=k,来进行模糊搜索,除此之外,我们还可以通过访问http://localhost:8080/demo/getOne?id=1来通过id进行查找信息。

构建项目

Spring data jpa and thymeleaf

在开始对项目编写之前,需要使用Spring Initializrl来创建spring boot项目,并添加Tthymeleaf、Spring Data JPA、Spring boot Web的支持,然后对项目pom.xml进行一定的配置。

pom.xml

pom.xml在本次的过程中主要用于添加MySQL依赖,其MySQL版本为5.6.50,但由于版本过高并没有直接使用此版本的依赖而是使用5.1.45的数据库依赖,以下是pom.xml信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

在这个过程中,如果xml配置文件出现问题,而这里的问题指的是xml文件下在mysql部分中存在红色的波浪线,可通过重新下载maven依赖包来解决:

mvn -U idea:idea

application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# [servlet]
server.servlet.context-path=/demo
# [thymeleaf] 关闭html缓存
spring.thymeleaf.cache=false
# [datasource] 指定数据库
spring.datasource.url=jdbc:mysql://localhost:3306/test?
# 数据库帐号与密码 username&password
spring.datasource.username=root
spring.datasource.password=toor
# 加载数据库驱动 com.mysql.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# [jpa] 指定数据库类型为MySQL
spring.jpa.database=mysql
# 是否在日志中显示SQL语句
spring.jpa.show-sql=true
# 是否自动创建、更新数据库表等配置信息,如果存在则不需要创建,不存在时才创建
spring.jpa.hibernate.ddl-auto=update

在spring boot的全局配置文件application.properties中,配置关闭thymleaf缓存来达到不用重启就能看到修改后结果的需求,并指定其目录、数据库、数据库类型等功能,详情可通过阅读配置文件即可。

实体

MyUser.java

在com.demo下创建example,然后在创建entity包,之后创建MyUser实体类,定义表名称为“user_name”和主键username、age、gender、id等

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
package com.demo.example.entity;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "user_name")
public class MyUser implements Serializable {
private static final long serialVersionUID = 1L;
/*
|-user_name 表中的主键分别为
|----|- id int
|----|- age int
|----|- username string
|----|- gender string
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int age;
private String username;
private String gender;

// id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}

// age
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}

// username
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}

// gender
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}

数据访问层

UserRepository.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.demo.example.repository;

import java.util.List;
import com.demo.example.entity.MyUser;
import org.springframework.data.jpa.repository.JpaRepository;


public interface UserRepository extends JpaRepository<MyUser, Integer> {
public MyUser findByUsername(String username);
public List<MyUser>findByUsernameLike(String username);
}

创建业务层

UserService.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.demo.example.service;

import java.util.List;
import com.demo.example.entity.MyUser;

public interface UserService {
public void saveAll();
public List<MyUser>findAll();
public MyUser findByUsername(String username);
public List<MyUser>findByUsernameLike(String username);
public MyUser getOne(int id);
}

UserServicelmpl.java

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
package com.demo.example.service;

import java.util.ArrayList;
import java.util.List;
import com.demo.example.entity.MyUser;
import com.demo.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;

@Override
public void saveAll() {
MyUser myUser1 = new MyUser();
myUser1.setUsername("kunlunsiqu");
myUser1.setGender("N");
myUser1.setAge(20);
MyUser myUser2 = new MyUser();
myUser2.setUsername("flydata");
myUser2.setGender("M");
myUser2.setAge(21);
MyUser myUser3 = new MyUser();
myUser3.setUsername("gscssd");
myUser3.setGender("N");
myUser3.setAge(30);
List<MyUser> users = new ArrayList<MyUser>();
users.add(myUser1);
users.add(myUser2);
users.add(myUser3);
userRepository.saveAll(users);
}

@Override
public List<MyUser> findAll() {
return userRepository.findAll();
}
@Override
public MyUser findByUsername(String username) {
return userRepository.findByUsername(username);
}
@Override
public List<MyUser> findByUsernameLike(String username) {
return userRepository.findByUsernameLike("%" + username + "%");
}

@Override
public MyUser getOne(int id) {
return userRepository.getOne(id);
}
}

控制器类

UserController.java

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.demo.example.service.UserService;


@Controller
public class UserController {
@Autowired
private UserService userService;

@RequestMapping("/save")
@ResponseBody
public String save() {
userService.saveAll();
System.out.println("UserController");
return "保存成功";
}
@RequestMapping("/findByUsername")
public String findByUsername(String username, Model model) {
model.addAttribute("title","根据名称查询");
model.addAttribute("auser", userService.findByUsername(username));
return "showAuser";
}
@RequestMapping("/getOne")
public String getOne(int id, Model model) {
model.addAttribute("title","根据名称id查询");
model.addAttribute("auser", userService.getOne(id));
return "showAuser";
}
@RequestMapping("findAll")
public String findAll(Model model) {
model.addAttribute("title", "查询所有");
model.addAttribute("allUsers", userService.findAll());
return "showAll";
}
@RequestMapping("/findByUsernameLike")
public String findByUsernameLike(String username,Model model) {
model.addAttribute("title","用户名模糊查询");
model.addAttribute("allUsers", userService.findByUsernameLike(username));
return "showAll";
}
}

Thymeleaf

showAll.html

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
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>showAll</title>
</head>
<body>
<h5>Java Spring Data JPA and MySQL + Thymeleaf</h5>
<p th:text="${title}"></p>
<hr/>
<table>
<tr>
<th>序号</th>
<th>名称</th>
<th>性别</th>
<th>年龄</th>
</tr>
<tr th:each="user: ${allUsers}">
<td>
<p th:text="${user.getId()}"></p>
</td>
<td>
<p th:text="${user.getUsername()}"></p>
</td>
<td>
<p th:text="${user.getGender()}"></p>
</td>
<td>
<p th:text="${user.getAge()}"></p>
</td>
</tr>
</table>
</body>
</html>

showAuser.html

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
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>showAuser</title>
</head>
<body>
<h5>Java Spring Data JPA and MySQL + Thymeleaf</h5>
<p th:text="${title}"></p>
<hr/>
<table>
<tr>
<th>序号</th>
<th>姓名</th>
<th>性别</th>
<th>年龄</th>
</tr>
<tr>
<td>
<p th:text="${auser.id}"></p>
</td>
<td>
<p th:text="${auser.username}"></p>
</td>
<td>
<p th:text="${auser.gender}"></p>
</td>
<td>
<p th:text="${auser.age}"></p>
</td>
</tr>
</table>
</body>
</html>

Spring boot and Thymeleaf Web

在上述文章之中我们主要撰写并列举了常用的Thymeleaf标签及表达式,通过上述文章我们将会在本章中列举一些真实的项目案例如表单的提交、国际化页面的撰写等。

表单

表单是在前端页面中不可缺失的一部分,通常应用在搜索、登入/注册、提交等页面之中,通过本书列举的Thymeleaf例子可更容易编写由Thymeleaf的动态页面:

login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h5>Thymeleaf is login</h5>
<hr />
<form action="#" th:action="@{/login}" th:object="${loginBean}" method="post">
<p>username: <input type="text" th:field="*{username}" th:placeholder="username" /></p>
<p>age: <input type="text" th:field="*{age}" th:placeholder="age" /></p>
<p><input type="submit" value="submit" /><input type="reset" value="reset" /></p>
</form>
</body>
</html>

result.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>result</title>
</head>
<body>
<h5>Thymeleaf submit an result</h5>
<hr/>
<p th:text="'username: ' + ${loginBean.username}" />
<p th:text="'age: ' + ${loginBean.age}" />
<a href="/toLogin" th:text="@{toLogin}"></a>
</body>
</html>

LoginController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.model.LoginBean;

@Controller
public class LoginController {
@RequestMapping("toLogin")
public String toLogin(Model model) {
model.addAttribute("loginBean",new LoginBean());
return "login";
}

@RequestMapping("login")
public String greetingSubmit(@ModelAttribute LoginBean loginBean) {
// LoginBean username&age
System.out.println("Test one data:" + loginBean.getUsername());
System.out.println("Test two data:" + loginBean.getAge());
return "result";
}
}

LoginBean.java

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

public class LoginBean {
String username;
int age;

// username
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}

// age
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

国际化(i18n)

国际化是一个大型网站中必备的一个服务之一,主要用于服务于不同种族、宗教信仰、国家和地区的人们沟通的一个非常重要的工具,通过Thymeleaf来实现站点国际化服务无非是一件非常不错的事情,甚至比原生的更加顺利。
至于标题中的国际化 i18n的意思为 国际化英文单词 internationalization,18为中间的字符数,是“国际化”的简称,通常在资讯领域,国际化是指让产品、出版物、软件,无需做大的改变就可适应不同的地区和语言需要,而对程序来说则是```不需要修改内部代码的情况下,根据不同语言及其地区显示相应的页面。

i18n properties

本书我们通过IDEA作为主要的集成开发环境进行开发,首先建立一个spring boot web + thymeleaf 的maven项目即通过spring Initializr工具构建的web项目,在src/main/resources/static/中新建目录为i18n用于存储i18n语言文件。

之后右键resources/static/i18n/目录选择 New->Resource Bundle,出现Create Resource Bundle窗口下,此时可通过项目语言(Project Locales)移动到(>) 语言添加(Locales to Add)

在然后双击在IDEA中双击 src/main/resources/static/i18n/Resource Bundle 'home'出现.properties配置页面,点击“+”号建立其各语言变量名。

home.properties

1
# 为默认语言文件,可为空,但文件不可不存在。

home_en_US.properties

1
2
home.i8n=en_US
home.title=Hello,world!

home_zh_CN.properties

1
2
home.i8n=zh_CN
home.title=你好世界!

application.yml

在application.yml中,主要存放于和application.properties同级目录下,用于配置我们的i18n文件所在目录和项目的编码信息,通过设置application.yml可设置 i18n文件目录及文件名(相对目录,不要使用绝对目录)

1
2
3
4
spring:
messages:
basename: static/i18n/home
encoding: UTF-8

application.properties

1
2
spring.thymeleaf.cache=false
spring.messages.basename=static/i18n/home

拦截器

LocaleConfigucation.java

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
package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import java.util.Locale;

@Configuration
public class LocaleConfigucation {
@Bean
public LocaleResolver localeResolver() {
// 默认语言
SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
sessionLocaleResolver.setDefaultLocale(Locale.US);
// 断点测试拦截器是否执行
System.out.println("yes");
return sessionLocaleResolver;
}

@Bean
public WebMvcConfigurer localeInterceptor() {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor();
localeInterceptor.setParamName("lang");
interceptorRegistry.addInterceptor(localeInterceptor);
}
};
}
}

首页文件

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h5>Java Thymeleaf i18n <span th:text="#{home.i8n}"></span> </h5><br>
<a href="?lang=zh_CN">中文</a>/<a href="/?lang=en_US">English</a>
<hr/>
<h5 th:text="#{home.title}"></h5>
</body>
</html>

Spring Boot

spring与spring boot的明显区别可以从配置文件出发观察,spring开发框架虽然强大但问题在于“配置文件过多”,造成开发的效率降低且不方便,就如spring mvc而看,spring mvc共需要配置两个xml才可输出一个“hello,world”,所以就造成了开发效率和部署缓慢的问题。而spring boot的出现大而解决了这种看似不是问题又非常无语的过程。

spring boot 概述

spring boot是由pivotal团队提供的全新框架,其设计的初衷和目的就是来简化spring应用部署及开发过程,名面上说就是不用在开发的过程中遭受spring xml折磨,使用spring boot框架可以用于做到一专注于spring的应用开发,从而不用关注太多的xml配置。
在spring boot框架中,使用“约定优于配置(Convention Over Configuration)”的设计理念,针对企业应用而开发,提供了符合各种应用场景的spring-boot-starter的自动化配置依赖横块,这些模块和spring boot都是基于“开箱即用”的原则,从而免于spring的各种折磨。

spring 主要特征

ID DA
约定优于配置 springboot遵循约定优于配置的规则很少需要配置,多数情况主要使用默认配置即可
独立运行 springboot可以以jar包的形式独立进行运行,可以使用“java -jar xxx”运行或者在项目的主程序中执行main方法执行springboot项目
内嵌web浏览器 内嵌servlet容器,可以选择内嵌tomcat等web容器
提供starter简化maven配置 springboot提供了一系列的starter pom来简化maven的依赖加载问题,基本上做到了自动化配置开箱即用。
自动化配置 spring springboot根据项目依赖(在类的路径中的jar包和class),自动化配置spring框架,减少了spring项目开发前的各种问题。
提供生产监控 springboot提供了基于http、ssh、telnet对运行的项目进行跟踪监控
无代码生成 and xml配置 springboot不是借助代码来生成的,而是通过条件注解来实现的,提倡使用java配置相结合的配置,方便快捷

Spring Cloud Apache Dubbo

Dubbos 是 Alibaba 于 2012 年开源,由于当时该项目是内部使用的一个分布式服务治理框架,并经历过多年的大规模业务验证,在短时间内收到很多开发者喜爱,但是处于某些原因在 2014 年停止了维护,所有就出现了当当网的 DubboX,但在 2017 年 9 月,Alibaba 又重启了 Dubbo 项目的维护,于 2018 年 2 月 进入了 Apache 孵化,这意味着 Dubbo 框架并不是只属于 Alibaba ,还属于开源社区的贡献。

在 2019 年 5 月 Dubbo 正式从孵化器中毕业,正式成为 “Apache Dubbo”,意味着这是 Apache 的顶级项目。在目前 Dubbo 主要的作用就是监控和服务注册两个非常常用的功能,除此之外他还提供了服务治理、服务注册、监控、路由容错等功能。

促使 Dubbo 项目产生的两个痛点就是服务提供者服务的连接,通常我们使用的是 URL,但如果服务提供者出现故障或者扩容时就需要来维护服务消费者或者服务中心的地址,因此这种维护方式非常麻烦。除

此我们还需要通过监控来发现那些服务需要进行扩容,那一些服务是不需要这么大性能的,因此需要一个判断的根据,所以可以通过监控来根据不同服务访问的情况来合理调用服务资源,提高机器的利用率。

因此 Dubbo 也类似一个 RPC 框架,但和普通类似框架不同的是它还提供了一个服务治理的功能

Spring Boot in Apache Dubbo and Nacos

Dubbo 除了支持 Spring Cloud 之外,它还支持 Spring Boot 的生态来实现微服务,因此我们在讲解 Spring Cloud Dubbo 之外还需要了解下 ZooKeeper 和 Apache Dubbo 所实现出的微服务效果。

服务接口

为了方便我们的的共用实体类,因此我们可以新建一个普通的 maven 文件,当开饭完成后导出为 jar 包,之后就可以在其他需要的时候直接调用依赖即可:

可以理解为就是将实体类安装本地依赖中,然后用的时候直接引入依赖和工具类即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package model;
/**
* 定义接口
*
* @author kunlun
* @date 2021/8/5
*/
public interface heyService {
/**
* 返回接口
* @param load 服务消费者是
* @return
*/
String getData(String load);
}

当写好之后我们直接在项目的父目录运行 mvn compile install 直接安装到本地即可,之后可以通过其 pom.xml 定义的名称进行引入本地依赖。

1
2
3
4
5
<dependency>
<groupId>org.example</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

Spring Cloud Alibaba


近年来随着互联网技术的发展,Alibaba 在微服务的贡献出的项目也非常得多,如 Dubbo 在 2021 年进行开源,之后迅速进行发展,直到 Spring Cloud 的出现来打破了传统的维度,之后 2017 年 Dubbo 项目又重新开始活跃,并拥抱 Spring Cloud 标准,也成为了目前国内为数不多可以替代 Netfilx 全家桶的一系列产品。

Spring Cloud Alibaba 系列的产品很受国内开发者喜爱,他也不仅仅是对本土开发者的支持和用户体验,而是这几年来 Alibaba 的产品经验,因此 Alibaba 的解决方案比 Netfile 更加更加人性化和好理解,但缺点在于文档支持没有 Netfile 的生态那么好。

对于 Spring Cloud Alibaba 中主要的组建就是 Dubbo、Nacos、Sentinel、Seata、RocketMQ

Dubbo

一个并不简单的 RPC 框架,用于实现多个系统之间的高性能、透明化调用、服务治理(注册、监控、路由、容错)

ZooKeeper

可以根据自身特性实现出注册中心,但他本身就是一个分布式协调中间件,因此实现注册中心也是合情合理。

Nacos

致力与解决为服务中的统一配置,服务注册与发现等问题,因此可以通过他来实现服务发现、服务配置、服务元数据和流量管理等

Sentinel

面向分布式服务架构的轻量级服务控制组建,可以实现对流量的降级和熔断以及一系列的限流规则。

Seata

开源的分布式事务解决方案,致力与在微服务中提供高性能和简单易用的分布式事务服务,提供了 AT、TCC、Saga、XA 事务模式

RocketMQ

是一个低延迟、高可靠、可伸缩易用于分布式消息中间件,经过 Alibaba 多年的 双 11 验证,因此具有了高吞吐、低延迟、海量消息堆积等特点,同时还提供了顺序消息,事物消息,定时消息,消息重试、消息追踪等特点。

Spring MVC

Spring MVC中的“MVC”,全称即“Model View Contorotoller,视图、模型、控制器”的三个部分,以这三个部分以最低的耦合进行协同工作,从而提高应用的可扩展性和维护性,而Spring MVC 基于MVC思想的而开发的一个框架,实现了Web MVC设计模式的轻量级的web框架。
而 Model、View、Controller,即模型、视图、控制器的大概设计为:

ID DA
Model 表示程序核心,包含了程序的数据,而数据可以是单个对象或对象的一个集合
View 以特定格式表示所提供的信息,他通常使用JSP—+JSTL用于创建视图页面
Controller 控制器包含了应用程序的业务逻辑,而通常使用@Controller的注解将用于表示此类是一个控制器

Spring MVC 工作的原理

Dispatcher Select

从上图可的知Spring MVC的所有请求都经过了Dispatcher Select即分发器/分派器来统一进行分发,之后将轻轻偶发送在Controller(控制器)之前,需要使用Spring MVC提供的Handler Mapping(处理器映射)来寻找处理器映射,从而找到指定的控制器。

Controller

Controller即控制器,主要完成和处理用户的请求,Controller处理完用户请求后,将会返回ModelAndView(模型和视图)对象给DispatcherSelect(分发器Serlect)而Controller则包含了模型(Model)/视图(View)。

这里作者从两个角度去考虑,如从宏观的角度而看,DispatcherServlet则是一个前端控制器i,而从微观的角度而看,DispatcherServlet则是一个请求处理过程的分派器或控制器。

ViewResolver

ViewResolver接口即视图解析程序,主要工作和职能就是在Web应用中负责查找View,然后将相应的结果渲染给访问者。

Java Spring JDBC

Java Spring JDBC即互联网数据库开发的基础,Spring框架提供了JDBC的模板模式,即JDBCTemplate,可简化很多的沉于代码,但是在实际情况中jdbcTemeplate并不常用,但这对我们学习和理解Spring 和之后的微服务都会起到非常重要的作用,可容易我们以后理解微服务的流程、过程等,本书主要参考了tutorialspoint内的文档而成.
在正式开始学习之前,需要使用以下Jar外部依赖包:

aspectjweaver-1.9.2.jar
commons-logging-1.2.jar
mysql-connector-java-8.0.22.jar
org.springframework.transaction-3.1.0.release.jar
spring-aop-5.1.4.RELEASE.jar
spring-beans-5.1.4.RELEASE.jar
spring-context-5.1.4.RELEASE.jar
spring-core-5.1.4.RELEASE.jar
spring-expression-5.1.4.RELEASE.jar
spring-jdbc-5.1.4.RELEASE.jar
spring-tx-5.1.4.RELEASE.jar

以上jar外部部分依赖包可下载spring框架来引入,而另一部分依赖包需要自行进行查找和下载,可通过https://www.findjar.com/ jar包搜索来下载想要的jar包

数据访问接口

TestuserDAO.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package Dao;
import java.util.List;
import javax.sql.DataSource;

public interface TestuserDAO {

// 用于初始化数据库连接
public void setDaotaSource(DataSource ds);

// 用于初始化数据库连接
public void create(String name, int age);

// 用于列出方法的记录
public Student getStudent(int id);

// 用于列出表中的所有记录
public List<Student> listStudents();

// 用于删除的方法
public void delete(int id);

// 用于更新方法即学生表中的记录
public void update(int id, int age);
}

Student.java

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 Dao;

public class Student {
private int age;
private int id;
private String name;

// age
public void setAge(int age) {
this.age = age;
}
public int getage() {
return age;
}

// id
public void setId(int id) {
this.id = id;
}
public int getid() {
return id;
}

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

StudentMapper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;

public class StudentMapper implements RowMapper<Student> {
public Student mapRow(ResultSet rs,int rowNum) throws SQLException {
Student s = new Student();
s.setId(rs.getInt("id"));
s.setAge(rs.getInt("age"));
s.setName(rs.getString("name"));
return s;
}
}

实现类

StudentJDBCTemplate.java

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

import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class StudentJDBCTemplate implements TestuserDAO {
private DataSource dataSource;
private JdbcTemplate jdbcTemplateObject;

public void setDataSource(DataSource dataSource) {
// TODO Auto-generated method stub
this.dataSource = dataSource;
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}


public void create(String name, int age) {
// TODO Auto-generated method stub
String SQL = "insert into testuser (name,age) values (?,?)";
jdbcTemplateObject.update(SQL,name,age);
System.out.println("Name:" + name + "Age:" + age);
return;
}


public Student getStudent(int id) {
// TODO Auto-generated method stub
String SQL = "select * from testuser where id = ?";
Student student = jdbcTemplateObject.queryForObject(SQL,
new Object[] {id},new StudentMapper());
return student;
}


public List<Student> listStudents() {
// TODO Auto-generated method stub
String SQL = "select * from testuser";
List <Student> students = jdbcTemplateObject.query(SQL, new StudentMapper());
return students;
}


public void delete(int id) {
// TODO Auto-generated method stub
String SQL = "delete from testuser where id = ?";
jdbcTemplateObject.update(SQL, id);
System.out.println("Delete id:" + id);
return;
}


public void update(int id, int age) {
// TODO Auto-generated method stub
String SQL = "update testuser set age = ? where id = ?";
jdbcTemplateObject.update(SQL,age,id);
System.out.println("Update id:" + id);
return;
}


@Override
public void setDaotaSource(DataSource ds) {
// TODO Auto-generated method stub

}

}

MainApp.java

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

import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import Dao.StudentJDBCTemplate;
public class MainApp {
public static void main(String args[]) {
ApplicationContext application = new ClassPathXmlApplicationContext("Beans.xml");
StudentJDBCTemplate s =
(StudentJDBCTemplate)application.getBean("StudentJDBCTemplate");

System.out.println("_______________)s.create");
s.create("sunone", 21);
s.create("sunsun", 22);

System.out.println("_______________)List<Student> student");
List<Student> student = s.listStudents();
for (Student record: student) {
System.out.println("age:" + record.getage());
System.out.println("id:" + record.getid());
System.out.println("name:" + record.getname());
}

System.out.println("________________)s.update");
s.update(520,1314);

System.out.println("________________)Student student");
Student students = s.getStudent(2);
System.out.println("id:" + students.getid());
System.out.println("age:" + students.getage());
System.out.println("name:" + students.getname());
}
}

src

Beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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-3.0.xsd ">

<!-- Initialization for data source -->
<bean id="dataSource"
class = "org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name = "driverClassName" value = "com.mysql.cj.jdbc.Driver"/>
<property name = "url" value = "jdbc:mysql://localhost:3306/testsp"/>
<property name = "username" value = "username"/>
<property name = "password" value = "password"/>
</bean>

<!-- Definition for studentJDBCTemplate bean -->
<bean id = "StudentJDBCTemplate"
class = "Dao.StudentJDBCTemplate">
<property name = "dataSource" ref = "dataSource" />
</bean>

</beans>

以上code可能会运行出错为:
Exception in thread “main” java.lang.NoSuchMethodError: org.springframework.dao.support.DataAccessUtils.nullableSingleResult(Ljava/util/Collection;)Ljava/lang/Object;
at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:784)
at Dao.StudentJDBCTemplate.getStudent(StudentJDBCTemplate.java:30)
at Dao.MainApp.main(MainApp.java:30)
细心的读者可通过搜索引擎自行解决,这个报错可能是spring与maven内的依赖版本过高、也可是jar依赖包导入顺序造成的。

Java Spring Bean

Benan即Spring应用中的IoC容器可以创建、装配、配置的应用组件对象,而这些组件通常会被称之为“Spring Bean”。

Spring Bean 实例化

通常我们在面向对象编程中如果需要使用某某某对象需要事先对其实例化,同样在Spring框架中想使用Spring容器中的Bean即“春豆”,Spring共提供了三种方法,分别为构造方法、静态工厂实例化、实例工厂实例化,而在这其中最为经常使用的无非就是构造方法实例化

实例化包 instance

调用构造方法实例化 BeanClass.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package instance;

public class BeanClass {
public String name;

public BeanClass() {
name = "调用构造方法实例化";
}

public BeanClass(String s) {
name = s;
}
}

调用静态方法实例化 BeanInstanceFactory.java

instance;
1
2
3
4
5
6

public class BeanInstanceFactory {
public BeanClass createBeanClassInstance() {
return new BeanClass("调用实例工厂方法实例化");
}
}

调用实例工厂实例化 BeanStaticFactory.java

1
2
3
4
5
6
7
8
package instance;

public class BeanStaticFactory {
private static BeanClass beanInstance = new BeanClass("调用静态的方法实例化");
public static BeanClass createInstance() {
return beanInstance;
}
}

建立测试包 config

实例化类 JavaConfig.java

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

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import instance.BeanClass;
import instance.BeanInstanceFactory;
import instance.BeanStaticFactory;

@Configuration
public class JavaConfig {

// 构造方法实例化
@Bean("beanClass")
public BeanClass getBeanClass() {
return new BeanClass();
}

// 静态工厂实例化
@Bean("beanStaticFactory")
public BeanClass getBeanStaticFactory() {
return BeanStaticFactory.createInstance();
}

// 势力工厂实例化
@Bean("beanInstanceFactory")
public BeanClass getBeanInstanceFactory() {
BeanInstanceFactory beaninstancefactory = new BeanInstanceFactory();
return beaninstancefactory.createBeanClassInstance();
}


}

测试类 TestBean.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package config;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import instance.BeanClass;

public class TestBean {
public static void main (String args[]) {
AnnotationConfigApplicationContext application =
new AnnotationConfigApplicationContext(JavaConfig.class);
// 调用构造方法实例化
BeanClass beanclassone = (BeanClass)application.getBean("beanClass");
System.out.println(beanclassone + beanclassone.name);

// 调用静态的方法实例化
BeanClass beanclasstwo = (BeanClass)application.getBean("beanStaticFactory");
System.out.println(beanclasstwo + beanclasstwo.name);

// 调用实例工厂方法实例化
BeanClass beanclassthree = (BeanClass)application.getBean("beanInstanceFactory");
System.out.println(beanclassthree + beanclassthree.name);
}
}

Bean 作用域

在Java Spring Bean作用域中,其中Bean的实例定义了六种作用域,主要可通过@Scope来实现 :

ID DA FA
singleton 默认使用的作用域,通过使用singleton在Spring中只有一个Bean实例 1
prototype Spring 容器每次获取Prototype定义的Bean,容器都将创建一个新的Bean实例 2
request 在HTTP请求中容器将返回一个Bean实例,仅可以在Web Spring应用上下文中使用 3
application 在每个ServletContexr对象上创建一个实例,即同一个应用共享一个Bean实例,仅可以在Web Spring应用程序上进行使用 4
websocket 为每个WebSocket对象中创建一个Bean实例,仅可以在Web Spring应用程序上下文中进行使用 5

通常在以上的六种作用域中,singleton和prototype是最常用的两种,而其他的作用域使用在Web Spring应用程序上下文中,通过下一个实例来演示Bean作用域。

Config

ScopeConfig.java
1
2
3
4
5
6
7
8
9
package config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("service")
public class ScopeConfig {
}
TestScope.java
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
package config;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import service.PrototypeService;
import service.SingletonService;

public class TestScope {
public static void main(String args[]) {
AnnotationConfigApplicationContext application =
new AnnotationConfigApplicationContext(ScopeConfig.class);
SingletonService singletonserviceone = application.getBean(SingletonService.class);
SingletonService singletonservicetwo = application.getBean(SingletonService.class);

System.out.println(singletonserviceone);
System.out.println(singletonservicetwo);

PrototypeService prototypeserviceone = application.getBean(PrototypeService.class);
PrototypeService prototypeservicetwo = application.getBean(PrototypeService.class);
System.out.println(prototypeserviceone);
System.out.println(prototypeservicetwo);

application.close();
}
}

Service

PrototypeService.java
1
2
3
4
5
6
7
8
9
package service;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope("prototype")
public class PrototypeService {
}
SingletonService.java
1
2
3
4
5
6
7
package service;

import org.springframework.stereotype.Service;

@Service // 默认为 singleton 相当于@Scope("singleton");
public class SingletonService {
}

从上图的运行结果可以的出@Service与@Scope(“prototype”)两个一起使用和单个使用的不同是最后在“PrototypeService”中输出的信息会随之改变,如下:

service.SingletonService@41089d
service.SingletonService@41089d
service.PrototypeService@a068d1
service.PrototypeService@1cb5951

Bean 的初始化和销毁

在Java Spring Bean中,存在生命周期,即当一个bean被实例化的时候,他需要执行一些可用的状态,同样,如不需要则在容器中移除。通常情况下我们使用**@Bean注解的initMethod、destroyMethod**属性来对Bean进行初始化和销毁:

Config

JavaConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import service.MyService;

@Configuration
public class JavaConfig {
@Bean(initMethod="initService",destroyMethod="destroyService")
public MyService getMyService() {
return new MyService();
}
}

TesInitAndDestroy.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package service;

public class MyService {
public void initService() {
System.out.println("initService - MyService");
}
public MyService() {
System.out.println("构造方法 - MyService");
}
public void destroyService() {
System.out.println("destroyService - MyService");
}
}

Service

MyService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package service;

public class MyService {
public void initService() {
System.out.println("initService - MyService");
}
public MyService() {
System.out.println("构造方法 - MyService");
}
public void destroyService() {
System.out.println("destroyService - MyService");
}
}

Java Spring AOP

Spring AOP(面向切面,Asoect-Oriented Programming)

Spring AOP(面向切面,Asoect-Oriented Programming)即面向切面变成,依赖并与OO(面向对象,Object Oriented)即面向对象编程相辅相成,这主要体现在当面向切面过程时,先对业务逻辑进行抽象,而这个业务逻辑抽象是由面向对象(OO,Object Oriented)。在面向切面编程之中,最基本的单元是切面(Aspect):

Asoect-Oriented Programming 面向切面术语

ID DA FA
切面 (Aspect) 至封装横切到系统功能(事物处理)的类; 1
连接点 (Joinpoint) 指程序运行中的一些时间点,如方法的异常或抛出 2
切入点 (Pointcut) 指需要处理的连接点 3
通知 (增强处理) 由切面添加到特定的连接点(满足切入点规则)的一段代码 4
引入 (Introduction) 允许在现有的实现类中添加自定义的方法以及属性 5
目标对象 (Target Object) 指所有被通知的对象 6
代理 (Proxy) 通知应用到目标对象之后,被动态创建的对象 7
织入 (Weaving) 将切面代码插入到目标对象上,从而生成代理对象的不同 8

通俗点的来讲就是:

ID DA FA
切面 就是将所有共有或集成的功能实现,如我们可以将一块一块的组建合并为一个切面 1
通知 也就是且面的具体实现,以指定的切入点作为规则来寻找或添加的一段代码 2
连接点 也就是说程序运行中能够插入切面的连接地点 3
切入点 即用于定义通知应该切入到那些的链接点上(不同的通知用于需要切入不同的连接点上) 4
目标对象 也就是书所有被通知的对象,都是目标对象 5
代理对象 当通知到目标对象之后,就会被变成一个动态的对象 代理对象的功能等于目标对象的核心业务逻辑功能+共有对象功能是程序运行中的产物 6
织入 将切面代码插入到目标的对象上,生成代理对象的不同 7

切面(Aspect)

通过上图可以看出,通过切面Aspect分别在,在业务类1和业务2中可以看出,分别加入了日志记录、性能统计、安全控制、事物处理等操作。也就是说在一个类中,加入了日志记录、性能统计、安全控制、事物处理等功能或构造方法。

切入点(Pointcut)

切入点(Pointcut)是指需要处理的连接点,在Spring AOP面向切面编程中,所有的方法执行都是连接点,而切入点是一个描述信息,修饰的是链接点,可通过切入点来链接需要被处理,而切面、连接点、切入点的关系如下;
而连接点即程序运行中的调用或异常抛出,但切入点主要处理需要处理的连接点(在Spring 面向切面中,所有方法都是连接点),而切面指的就是事物处理的类。

AspectJ 基于注解开发

AspectJ是一个面向切面AOP的一个框架,扩展了Java语言和定义了AOP语法,基于注解的开发AsoectJ会比和XML配置开发Aspect便捷许多没而Spring中通知的目标方法的连接点位置主要可以分为以下六种类型:

ID DA FA
环绕通知 在目标方法执行前和执行后的实施增强 1
前置通知 在目标方法执行前实施的增强 2
后置返回通知 在目标方法成够执行后实施增强 3
最终通知即后置通知 目标方法执行后进行增强,与后置返回通知不同的不管发生异常,都要执行该通知 4
异常通知 在方法抛出异常后实施增加 5
引入通知 在目标类中添加一些新的方法和属性,用于修改目标类 6

AspectJ 注解

ID DA FA
@Aspect 声明一个切面 1
@Pointcut 用于定义切入点,使用需要定义一个切入点的Viod方法,且方法体为空的普通方法 2
@Before 定义前置通知,通常为其指定value属性值 3
@AfterReturning 定义后置返回通知,使用时为其指定value属性值 4
@Around 用于定义一个环绕通知,在使用是也通常为其制定为value属性值 5
@AfterThrowing 定义一个异常通知,使用是通常为其指定value属性值 6
@After 用于定义后置即最终通知,使用时通常使用value属性值 7

前提条件

在开发项目之前,需要使用六个Spring核心的依赖包,分别为aop、aspects、beans、context、core、expression,之后还需要commons-logging,下载地址为:“https://downloads.apache.org//commons/logging/binaries/commons-logging-1.2-bin.zip”,然后还需要一个外部第三方依赖包为aspectjweaver.jar,下载地址为:“https://repo1.maven.org/maven2/org/aspectj/aspectjweaver/1.9.2/aspectjweaver-1.9.2.jar”

创建实现类的接口 aspectj.dao

TestDao.java
1
2
3
4
5
6
7
package aspectj.dao;

public interface TestDao {
public void save();
public void modify();
public void delete();
}

TestDaoImpl.java

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
package aspectj.dao;

import org.springframework.stereotype.Repository;

@Repository("testDao")
public class TestDaoImpl implements TestDao {
// @Override 用于方法重写
@Override
public void save() {
// TODO Auto-generated method stub
System.out.println("save - TestDaoImpl");
}

@Override
public void modify() {
// TODO Auto-generated method stub
System.out.println("modify - TestDaoImpl");

}

@Override
public void delete() {
// TODO Auto-generated method stub
System.out.println("delete - TestDaoImpl");
}

}

创建切面类 aspectj.annotaition

MyAspect.java
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
package aspectj.annotaition;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect // 声明切面
@Component // 让此且面成为Spring容器管理的Bean
public class MyAspect {
/*
* Pointcut 定义了切入点
* 而"execution(* aspectj.dao.*.*(..))"定义了切入点表达式;
* 而切入点表达式的意思大概是匹配 “aspectj.dao“包中任意类的方法执行;
* 而execution()是表达式的主体;
* ”*“表示所有返回类型,而使用”*“可代表所有类型;
* 其中"aspectj.dao"表示的是需要匹配的包名,第二个"*"是类型,代表所有匹配的类
* 第三个”*“表示方法名,使用他表示所有方法;
* "(...)"是方法参数,其中”...“表示任意参数
*/
@Pointcut("execution(* aspectj.dao.*.*(..))")
private void myPointCut() {

}

@Before("myPointCut()")
public void before(JoinPoint joinpoint) {
System.out.println("前置通知");
System.out.println("目标类对象:" + joinpoint.getTarget()
+ "被增强处理的方法:" + joinpoint.getSignature().getName());
}

// 后置返回通知
@AfterReturning("myPointCut()")
public void afterReturing(JoinPoint joinpoint) {
System.out.println("后置返回通知:" + "模拟删除临时文件");
System.out.println("被增强处理的方法" + joinpoint.getSignature().getName());
}

// 环绕通知
@Around("myPointCut()")
public Object around(ProceedingJoinPoint procedingjoinpoint) throws Throwable {
// run
System.out.println("环绕开始,执行目标方法前模拟开启事物");
Object object = procedingjoinpoint.proceed();

// end
System.out.println("环绕结束,执行目标方法后模拟关闭事物");
return object;
}

// 异常通知
@AfterThrowing(value="myPointCut()",throwing="e")
public void except(Throwable e) {
System.out.println("异常通知" + "程序执行异常" + e.getMessage());
}

// 最终通知
@After("myPointCut()")
public void after() {
System.out.println("最终通知,模拟释放");
}
}

配置类 aspectj.config

AOPTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package aspecti.config;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import aspectj.dao.TestDao;

public class AOPTest {
public static void main(String args[]) {
// 初始化AnnotationConfigApplicationContext
AnnotationConfigApplicationContext application = new AnnotationConfigApplicationContext(AspectjAOPConfig.class);

// 从容器中获取增强后的目标对象
TestDao testDaoAdvice = application.getBean(TestDao.class);
// 执行方法
testDaoAdvice.save();
System.out.println("-----------");
testDaoAdvice.modify();
System.out.println("-----------");
testDaoAdvice.delete();
// 关闭
application.close();
}
}
AspectjAOPconfig.java
1
2
3
4
5
6
7
8
9
10
11
12
package aspecti.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration // 声明一个配置类
@ComponentScan("aspectj") // 自动扫描指定包下的注解
@EnableAspectJAutoProxy // 开启Spring对Aspectj的支持
public class AspectjAOPConfig {

}
📖 more posts 📖