Spring5

Spring概述

简介

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。

官网 : http://spring.io/

官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/

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

组成

Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring Context:BeanFactory使Spring成为一个容器,而Context模块使它成为一个框架。这个模块扩展了BeanFactory的概念,增加了对国际化和校验的支持,提供了诸如邮件、JNDI访问和EJB集成等企业服务。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:使用JDBC经常导致大量重复代码——取得连接、创建语句、处理结果集,然后关闭连接。Spring的DAO模块抽取了这些重复代码。这个模块还提供了一个异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。
  • Spring ORM:为几种流行的ORM框架提供了集成方案,包括Hibernate、JDO和iBatis。Spring的事务管理也支持这些ORM框架。
  • Spring Web 模块:Web上下文模块建立于应用上下文模块智商,这个模块提供一些面向服务支持,例如实现文件上传的MultipartResolver,还提供了Spring和其他Web框架的集成,比如Struts。
  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

IoC基础

IOC本质

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

IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

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

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

控制反转 :

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .

依赖注入 : 就是利用set方法来进行注入的.

可以通过ClassPathXmlApplicationContext去浏览一下底层源码

所谓的IoC,一句话概括就是 : 对象由Spring 来创建 , 管理 , 装配 !

IOC创建对象方式

通过无参构造方法来创建

beans.xml

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="com.kuang.pojo.User">
<property name="name" value="kuangshen"/>
</bean>

</beans>

默认使用无参构造方法创建对象,然后使用set方法进行属性注入

通过有参构造方法来创建

beans.xml 有三种方式编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 第一种根据index参数下标设置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="kuangshen2"/>
</bean>
<!-- 第二种根据参数名字设置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<!-- name指参数名 -->
<constructor-arg name="name" value="kuangshen2"/>
</bean>
<!-- 第三种根据参数类型设置 不建议 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<constructor-arg type="java.lang.String" value="kuangshen2"/>
</bean>

在配置文件加载的时候。其中管理的对象都已经初始化了!

Spring配置

Bean的配置

1
2
3
4
5
6
7
8
9
10
<!--
id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
如果配置id,又配置了name,那么name是别名
name可以设置多个别名,可以用逗号,分号,空格隔开
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
classbean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
<property name="name" value="Spring"/>
</bean>

import

多个配置文件使用import来导入.

1
<import resource="{path}/beans.xml"/>

依赖注入

概念

  • 依赖注入(Dependency Injection,DI)。
  • 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源
  • 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配

构造器注入

我们在之前的案例已经讲过了

Set 注入

要求被注入的属性 , 必须有set方法,方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 则用 is开头

1、常量注入

1
2
3
<bean id="student" class="com.kuang.pojo.Student">
<property name="name" value="小明"/>
</bean>

2、Bean注入

注意点:这里的值是一个引用,ref

1
2
3
4
5
6
7
8
<bean id="addr" class="com.kuang.pojo.Address">
<property name="address" value="重庆"/>
</bean>

<bean id="student" class="com.kuang.pojo.Student">
<property name="name" value="小明"/>
<property name="address" ref="addr"/>
</bean>

3、数组/List/Set注入,分别使用标签array/list/set

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="student" class="com.kuang.pojo.Student">
<property name="name" value="小明"/>
<property name="address" ref="addr"/>
<property name="books">
<!-- array/list/set -->
<array>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</array>
</property>
</bean>

4、Map注入

1
2
3
4
5
6
<property name="card">
<map>
<entry key="中国邮政" value="456456456465456"/>
<entry key="建设" value="1456682255511"/>
</map>
</property>

5、Null注入

1
<property name="wife"><null/></property>

6、Properties注入

1
2
3
4
5
6
7
<property name="info">
<props>
<prop key="学号">20190604</prop>
<prop key="性别"></prop>
<prop key="姓名">小明</prop>
</props>
</property>

Bean的作用域

在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .

几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

Singleton

当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。Singleton作用域是Spring中的缺省作用域。

Prototype

当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:

1
2
3
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>

Request

当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

1
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>

针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。

Session

当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

1
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

Bean的自动装配

自动装配说明

  • 自动装配是使用spring满足bean依赖的一种方法
  • spring会在应用上下文中为某个bean寻找其依赖的bean。

Spring中bean有三种装配机制,分别是:

  1. 在xml中显式配置;
  2. 在java中显式配置;
  3. 隐式的bean发现机制和自动装配。

Spring的隐式装配涉及以下两个操作:

  1. 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
  2. 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。

环境准备

Dog类:

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

public Dog() {
}

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

public void shout(){
System.out.println(name+"汪汪叫");
}
}

User类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class User {

private String name;
private Dog dog;

public User() {
}

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

public void setDog(Dog dog) {
this.dog = dog;
}

public Dog getDog() {
return dog;
}
}

显式配置

在xml中显式配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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="dog" class="com.atomsk.pojo.Dog">
<property name="name" value="小黑"/>
</bean>

<bean id="user" class="com.atomsk.pojo.User">
<property name="name" value="atomsk"/>
<property name="dog" ref="dog"/>
</bean>
</beans>

在java中显式配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class BeanConfig {

@Bean
public Dog dog(){
Dog dog = new Dog();
dog.setName("小黑");
return dog;
}

@Bean
public User user(){
User user = new User();
user.setDog(dog);
user.setStr("atomsk");
return user;
}
}

隐式配置

xml的autowire属性(不建议使用)

byName(按名称自动装配)

测试:

1、修改上面xml显示配置中bean的配置,增加一个属性 autowire="byName"

1
2
3
4
5
6
7
8
9
···
<bean id="dog" class="com.atomsk.pojo.Dog">
<property name="name" value="小黑"/>
</bean>

<bean id="user" class="com.atomsk.pojo.User" autowire="byName">
<property name="name" value="atomsk"/>
</bean>
···

2、再次测试,结果依旧成功输出!

3、我们修改 dog 的 id

4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setDog就没执行,对象就没有初始化,所以调用时就会报空指针错误。

小结:

当一个bean节点带有 autowire="byName"的属性时。

  1. 将查找其类中所有的set方法名,例如setDog,获得将set去掉并且首字母小写的字符串,即dog。

  2. 去spring容器中寻找是否有此字符串名称id的对象。

  3. 如果有,就取出注入;如果没有,就报空指针异常。

byType (按类型自动装配)

使用autowire="byType"首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常(NoUniqueBeanDefinitionException)。

测试:

1、将user的bean配置修改一下 : autowire="byType"

2、测试,正常输出

3、再注册一个dog的bean对象!

1
2
3
4
5
6
7
8
9
10
11
···
<bean id="dog" class="com.atomsk.pojo.Dog">
<property name="name" value="小黑"/>
</bean>
<bean id="dog2" class="com.atomsk.pojo.Dog">
<property name="name" value="小黄"/>
</bean>
<bean id="user" class="com.atomsk.pojo.User" autowire="byType">
<property name="str" value="atomsk"/>
</bean>
···

4、测试,报错:NoUniqueBeanDefinitionException

5、删掉dog2,将dog的id改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。

推荐不使用自动装配xml配置 , 而使用注解

使用注解开发

jdk1.5开始支持注解,spring2.5开始全面支持注解。

开启组件扫描

xml方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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-scan package="com.atomsk.pojo"/>
<!--
开启组件扫描后,在需要被Spring管理的类上加上@Component注解
依赖关系和属性注入:1.可以获取到bean之后手动注入
2.使用@Autowired取代 ref,使用@Value取代 value
-->

</beans>

测试:

1
2
3
4
5
6
7
8
@Test//组件扫描,使用注解+手动注入属性
public void test01(){
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
User user = (User) context.getBean("user");
user.getDog().setName("小黑");
user.getDog().shout();
}

java配置类方式

@ComponentScan可以指定要扫描组件的范围,不设置参数默认扫描当前类的包以及子包。如果设置会扫描指定的包。

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@ComponentScan("com.atomsk.pojo")
public class ApplicationConfig {
public ApplicationConfig(){
System.out.println("Spring容器初始化");
}
/*
依赖关系和属性注入:1.和xml的两种方式一样
2.使用上面说到的java显式配置
*/
}

使用java配置类进行测试:

1
2
3
4
5
6
7
8
@Test//java注解配置类方式,并手动注入属性
public void test03(){
ApplicationContext context =
new AnnotationConfigApplicationContext(ApplicationConfig.class);
User user = (User) context.getBean("user");
user.getDog().setName("小黑");
user.getDog().shout();
}

导入其他配置如何做呢?

使用注解@Import(xxxConfig.class)即可

关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!

DI相关注解

@Value

1、可以不用提供set方法,直接在直接名上添加@value(“值”)

1
2
3
4
5
6
7
8
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
@Value("atomsk")
// 相当于配置文件中 <property name="name" value="atomsk"/>
public String name;
···
}

2、如果提供了set方法,在set方法上添加@value(“值”);

1
2
3
4
5
6
7
8
@Component
public class User {
public String name;
@Value("atomsk")
public void setName(String name) {
this.name = name;
}
}

@Autowired

  • @Autowired是默认按类型自动匹配,当有多个相同类型的bean时,匹配id与类名相同的bean
  • 需要导入 spring-aop的包!

@Autowired(required=false) 说明:如果允许对象为null,设置 required=false;默认为true,对象必须存在,不能为null。

@Qualifier

  • @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
  • @Qualifier它在给字段注入时不能独立使用,必须和@Autowire 一起使用,但是给方法参数注入时,可以独立使用。

测试实验步骤:

1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!

1
2
3
4
5
6
<bean id="dog1" class="com.atomsk.pojo.Dog">
<property name="name" value="小黑"/>
</bean>
<bean id="dog2" class="com.atomsk.pojo.Dog">
<property name="name" value="小黄"/>
</bean>

2、没有加Qualifier时,测试不通过,因为当有多个相同类型的bean,又没有一个bean的id与类名同名

3、添加Qualifier注解,则使用value指定id的bean

1
2
3
4
5
···
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
···

4、测试结果:小黄汪汪叫

@Resource

  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
  • 其次再进行默认的byName方式进行装配;
  • 如果以上都不成功,则按byType的方式自动装配。
  • 都不成功,则报异常。

实体类:

1
2
3
4
5
6
7
8
public class User {
//如果允许对象为null,设置required = false,默认为true
@Resource
private Dog dog;
@Resource(name = "cat2")
private Cat cat;
private String str;
}

配置文件1:beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="dog" class="com.atomsk.方式一.Dog">
<property name="name" value="小黑"/>
</bean>
<bean id="dog2" class="com.atomsk.方式一.Dog">
<property name="name" value="小黄"/>
</bean>
<bean id="cat" class="com.atomsk.方式一.Cat">
<property name="name" value="阿毛"/>
</bean>
<bean id="cat2" class="com.atomsk.方式一.Cat">
<property name="name" value="阿喵"/>
</bean>

测试结果:

​ 小黑汪汪叫
​ 阿喵喵喵叫

配置文件2:beans.xml

1
2
3
4
5
6
7
8
9
<bean id="dog" class="com.atomsk.方式一.Dog">
<property name="name" value="小黑"/>
</bean>
<bean id="dog234" class="com.atomsk.方式一.Dog">
<property name="name" value="小黄"/>
</bean>
<bean id="cat2" class="com.atomsk.方式一.Cat">
<property name="name" value="阿喵"/>
</bean>

实体类上只保留注解

1
2
3
4
@Resource
private Dog dog;
@Resource
private Cat cat;

测试结果:

​ 小黑汪汪叫

​ 阿喵喵喵叫

结论:先进行byName查找,失败;再进行byType查找,成功。

@Autowired与@Resource异同

1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。

@Component三个衍生注解

为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:web层
  • @Service:service层
  • @Repository:dao层

@Scope

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收

小结

XML与注解比较

  • XML可以适用任何场景 ,结构清晰,维护方便
  • 注解不是自己提供的类使用不了,开发简单方便

xml与注解整合开发 :推荐最佳实践

  • xml管理Bean
  • 注解完成属性注入
  • 使用过程中, 可以不用扫描,扫描是为了类上的注解
1
<context:annotation-config/>

作用:

  • 进行注解驱动注册,从而使注解生效
  • 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
  • 如果不扫描包,就需要手动配置bean
  • 如果不加注解驱动,则注入的值为null!

AOP

AOP的基础——静态/动态代理模式

什么是AOP

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

为什么需要AOP?

越来越多的非业务需求(日志和验证等)加入后, 原有的业务方法急剧膨胀. 每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点. 以日志需求为例, 只是为了满足这个单一需求, 就不得不在多个模块(方法)里多次重复相同的日志代码. 如果日志需求发生变化, 必须修改所有模块。

上述问题解决的方法就是使用动态代理,代理设计模式的原理是使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理. 代理对象决定是否以及何时将方法调用转到原始对象上。

使用AOP的好处是:

  • 每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
  • 业务模块更简洁, 只包含核心业务代码.

名词解释

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ….
  • 连接点(JointPoint):程序执行的某个特定位置。类比:方法的执行前后,抛出异常后等等
  • 切入点(PointCut):AOP 通过切入点定位连接点。类比:连接点相当于数据库中的记录,切入点相当于查询条件。一个切入点可以匹配多个连接点(execution表达式)。
  • 通知(Advice):切面在连接点采用的行为/增强。类比:代理类中的方法
  • 切面(Aspect):将 横切关注点 抽象后得到的类,由切入点+通知组成
  • 目标(Target):被 通知/代理 的对象,类比:业务代码。
  • 织入(Weaving):将通知应用到目标连接点上的过程
  • 代理(Proxy):将通知织入到目标后产生的对象

Execution表达式

格式:

1
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

括号中各个pattern分别表示:

  • 修饰符匹配(modifier-pattern?)
  • 返回值匹配(ret-type-pattern)可以为*表示任何返回值,全路径的类名等
  • 类路径匹配(declaring-type-pattern?)
  • 方法名匹配(name-pattern)可以指定方法名 或者 代表所有, set 代表以set开头的所有方法
  • 参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(..)表示零个或多个任意参数
  • 异常类型匹配(throws-pattern?)
  • 其中后面跟着“?”的是可选项

Advice类型

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

通知类型 连接点 实现接口
前置通知 方法前 org.springframework.aop.MethodBeforeAdvice
后置通知 方法后 org.springframework.aop.AfterReturnAdvice
环绕通知 方法前后 org.aopalliance.intercept.MethodInterceptor
异常抛出通知 方法抛出异常 org.springframework.aop.ThrowAdvice
引介通知 类中增加新的方法属性 org.springframework.aop.IntroductionInterceptor

即 Aop 在 不改变原有代码的情况下 , 去增加新的功能

SpringAOP基本运行流程

  1. 找到横切关注点,抽象出切面,及切入点和通知
  2. Spring会根据切入点去匹配Target中的连接点
  3. 匹配成功后,根据配置文件寻找对应的Advice
  4. 织入通知

使用Spring实现Aop

【重点】使用AOP织入,需要导入一个依赖包!

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>

环境准备

首先编写业务接口和实现类

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
public interface UserService {

public void add();

public void delete();

public void update();

public void search();

}
public class UserServiceImpl implements UserService{

@Override
public void add() {
System.out.println("增加用户");
}

@Override
public void delete() {
System.out.println("删除用户");
}

@Override
public void update() {
System.out.println("更新用户");
}

@Override
public void search() {
System.out.println("查询用户");
}
}

通过 Advice 实现

第一步:编写Advice增强类 , 一个前置增强 一个后置增强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class BeforeLog implements MethodBeforeAdvice {

//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
}

第二步:在spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!--使用自定义advice实现-->
<bean id="userService" class="com.atomsk.service.UserServiceImp"/>
<bean id="beforeLog" class="com.atomsk.advice.BeforeLog"/>
<bean id="afterLog" class="com.atomsk.advice.AfterLog"/>

<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut"
expression="execution(* com.atomsk.service.UserServiceImp.*(..))"/>
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>

</beans>

第三步:测试

1
2
3
4
5
6
7
@Test
public void test01(){
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-aop-advice.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}

Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .

Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .

通过 Aspect实现

第一步 : 写一个Aspect切入类

1
2
3
4
5
6
7
8
9
10
public class DiyAspect {

public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}

}

第二步:去spring中配置

1
2
3
4
5
6
7
8
9
10
11
<!--使用自定义aspect实现-->
<bean id="userService" class="com.atomsk.service.UserServiceImp"/>
<bean id="logAspect" class="com.atomsk.aspect.LogAspect"/>

<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="logAspect">
<aop:before pointcut-ref="pointcut" method="before"/>
<aop:after pointcut-ref="pointcut" method="after"/>
</aop:aspect>
</aop:config>

第三步:测试:

1
2
3
4
5
6
7
@Test
public void test02(){
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-aop-aspect.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}

使用注解实现

第一步:编写一个注解实现的Aspect切面类

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

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

/**
* @author Atomsk
* @github https://github.com/AtomskGit
* @blog https://atomskgit.github.io/blog/
* @date 2020/7/29 0029
*/
@Aspect //声明当前是个切面类
public class AnnotationPointcut {
//声明切入点表达式
@Pointcut(value = "execution(* com.atomsk.service.UserServiceImpl.*(..))")
private void pc(){};

@Before("pc()")
public void before(){
System.out.println("---------方法执行前---------");
}

@After("pc()")
public void after(){
System.out.println("---------方法执行后---------");
}

@Around("pc()")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}

第二步:在Spring配置文件中,注册bean,并增加支持注解的配置

1
2
3
4
5
<!--使用注解方式实现-->
<bean id="userService" class="com.atomsk.service.UserServiceImpl"/>

<bean id="annotationPointcut" class="com.atomsk.aspect.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>

第三步:测试

1
2
3
4
5
6
7
@Test
public void test03(){
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-aop-aspect-annotation.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}

aop:aspectj-autoproxy

通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@AspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了

<aop:aspectj-autoproxy />有一个``\proxy-target-class`属性,默认为false,表示使用jdk动态代理织入增强,当设置为为true时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

整合Mybatis

导入相关依赖

1、导入相关jar包

mybatis,mybatis-spring,aspectjweaver这三个包可能会出现版本问题导致数据库连接失败,以下是我最后一次使用的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<properties>
<mybatis.version>3.5.5</mybatis.version>
<mybatis-spring.version>2.0.4</mybatis-spring.version>
<aspectjweaver.version>1.9.6</aspectjweaver.version>
</properties>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>

spring相关

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>

mysql-connector-java

1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>

配置Maven静态资源过滤问题!

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/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

2、编写配置文件

3、代码实现

MyBatis-Spring学习

引入Spring之前需要了解mybatis-spring包中的一些重要类;

http://www.mybatis.org/spring/zh/index.html

什么是 MyBatis-Spring?

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。

知识基础

在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要

SqlSessionFactory

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

1
2
3
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>

注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。

在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。

SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。

一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。

需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。

SqlSessionTemplate

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。

模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

1
2
3
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
public class UserDaoImpl implements UserDao {

private SqlSession sqlSession;

public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}

public User getUser(String userId) {
return sqlSession.selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
}
}

按下面这样,注入 SqlSessionTemplate:

1
2
3
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSession" ref="sqlSession" />
</bean>

整合环境搭建

1、引入Spring配置文件beans.xml

1
2
3
4
5
6
7
8
<?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
https://www.springframework.org/schema/context/spring-context.xsd">

2、配置数据源替换mybaits的数据源

1
2
3
4
5
6
7
8
9
10
<context:property-placeholder ignore-unresolvable="true"
location="classpath:db.properties"/>

<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>

3、配置SqlSessionFactory,关联MyBatis

1
2
3
4
5
6
 <!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联Mybatis配置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>

4、mybatis-config.xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<mappers>
<package name="com.atomsk.dao"/>
</mappers>
</configuration>

整合方式一

1、注册sqlSessionTemplate,关联sqlSessionFactory;

1
2
3
4
5
<!--注册sqlSessionTemplate , 关联sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--利用构造器注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

2、实现Mapper;私有化sqlSessionTemplate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Repository
public class UserMapperImpl implements UserMapper{

//sqlSession不用我们自己创建了,Spring来管理
@Autowired
private SqlSessionTemplate sqlSession;

public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}

@Override
public List<User> getAllUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.getAllUser();
}
}

3、测试

1
2
3
4
5
6
7
@Test
public void test02(){
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-mybatis.xml");
UserMapper userMapper = (UserMapper) context.getBean("userMapperImpl");
userMapper.getAllUser();
}

整合方式二

使用SqlSessionDaoSupport, 直接利用 getSqlSession() 获得Mapper, 比起方式一 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好

SqlSessionDaoSupport

1、继承SqlSessionDaoSupport并实现Mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Repository
public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {

@Autowired
public void setSqlSessionTemplate(SqlSessionFactory sqlSessionFactory)
{
super.setSqlSessionFactory(sqlSessionFactory);
}

@Override
public List<User> getAllUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.getAllUser();
}
}

2、测试,结果和整合方式一的一样

声明式事务

Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。

编程式事务管理

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
  • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务管理

  • 一般情况下比编程式事务好用。
  • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

使用Spring管理事务,注意头文件的约束导入 : tx

1
2
3
4
xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。

JDBC事务

1
2
3
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

配置好事务管理器后我们需要去配置事务的通知

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性,默认为REQUIRED,可以不写-->
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="delete*"/>
<tx:method name="update*"/>
<tx:method name="select*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

spring事务传播特性:

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。(默认值)
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。

就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!

配置AOP

导入aop的头文件!

1
2
3
4
5
<!-- 配置事务织入 -->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.atomsk.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

思考问题?

为什么需要配置事务?

  • 如果不配置,就需要我们手动提交控制事务;
  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!