1、Spring概述
1.1、Spring是什么?
- Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题
- Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。
- 这些功能的底层都依赖于它的两个核心特性,也就是依赖注入(dependency injection,DI)和面向切面编程(aspect-oriented programming,AOP)。
1.2 Spring的优缺点
优点:
- 方便解耦,简化开发 :Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。
- AOP编程的支持: Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
- 声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程。
- 方便程序的测试: Spring对Junit4支持,可以通过注解方便的测试Spring程序。
- 方便集成各种优秀框架: Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
- 降低JavaEE API的使用难度:Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低
缺点
- Spring明明一个很轻量级的框架,却给人感觉大而全
- Spring依赖反射,反射影响性能
- 使用门槛升高,入门Spring需要较长时间
1.3 Spring的组成模块
Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、 Web、 消息(Messaging) 、 Test等 6 个模块中。 以下是 Spring 5 的模块结构图:
- **spring core:**提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
- **spring beans:**提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。
- **spring context:**构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。
- **spring jdbc:**提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。
- **spring aop:**提供了面向切面的编程实现,让你可以自定义拦截器、切点等。
- **spring Web:**提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。
- **spring test:**主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。
1.4 Spring中应用的设计模式
- 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
- 单例模式:Bean默认为单例模式。
- 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
- 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
- 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
2、Spring IOC
2.1、什么是IOC
IOC,Inversion of Control,控制反转,指将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。
最直观的表达就是,以前创建对象的时机和主动权都是由自己把控的,如果在一个对象中使用另外的对象,就必须主动通过new指令去创建依赖对象,使用完后还需要销毁(比如Connection等),对象始终会和其他接口或类耦合起来。而 IOC 则是由专门的容器来帮忙创建对象,将所有的类都在 Spring 容器中登记,当需要某个对象时,不再需要自己主动去 new 了,只需告诉 Spring 容器,然后 Spring 就会在系统运行到适当的时机,把你想要的对象主动给你。也就是说,对于某个具体的对象而言,以前是由自己控制它所引用对象的生命周期,而在IOC中,所有的对象都被 Spring 控制,控制对象生命周期的不再是引用它的对象,而是Spring容器,由 Spring 容器帮我们创建、查找及注入依赖对象,而引用对象只是被动的接受依赖对象,所以这叫控制反转。
看不懂,没有关系,帅哥公主请看代码:
一般情况:
public class Test {
public static void main(String[] args) {
A a = new A();
//a.b = new B(); !!! 如果我们没有这句代码,运行是会报错的。
a.b.shout();
}
}
class A{
B b; //这相当于是一个指针,但是没有指向任何对象
}
class B{
void shout(){
System.out.println("我是B");
}
}
Spring情况
@Component
public class A {
@Autowired
public B b;
}
@Component
public class B {
public void shout(){
System.out.println("我是B");
}
}
public class MyTest {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
A a = context.getBean("a", A.class);
//这个地方,我们 new B() 了吗,没有吧,但是可以直接使用
//因为 spring 容器帮我们创建了,也处理了二者之间的依赖关系。我们直接用就行了
a.b.shout();
}
}
2.2、什么是DI
IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性
能注入的数据类型:
- 基本类型和String。
- 其他bean类型(在配置文件中或者注解配置过的bean)。
- 复杂类型/集合类型。
注入的方式:
- 使用构造函数提供。
- 使用set方法提供。
- 使用注解提供。
- 接口注入(不推荐)
2.2.1 构造函数注入
顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。
标签中的属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型。
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始。
name:用于指定给构造函数中指定名称的参数赋值。
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
<!--默认构造器方式-->
<bean id="user" class="com.kuang.pojo.User">
<property name="name" value="张三"/>
</bean>
<!--通过有参构造创建对象。方式一:下标赋值-->
<bean id="user" class="com.kuang.pojo.User">
<constructor-arg index="0" value="jerry"/>
</bean>
<!--通过有参构造创建对象。方式二:类型创建,不建议使用-->
<bean id="user" class="com.kuang.pojo.User">
<constructor-arg type="java.lang.String" value="jarry"/>
</bean>
<!--通过有参构造创建对象。方式三:通过参数名,推荐使用-->
<bean id="user" class="com.kuang.pojo.User">
<constructor-arg name="name" value="jarry"/>
<constructor-arg name="birthday" ref="now"/>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
2.2.2 set方法注入
顾名思义,就是在类中提供需要注入成员的 set 方法。
set方法注入【常用】
标签的属性
name:用于指定注入时所调用的set方法名称
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象。
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数。
弊端:
如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
<property name="name" value="tom" ></property>
<property name="age" value="23"></property>
<property name="birthday" ref="now"></property>
</bean>
2.2.3 注解注入
//等价于 <bean id="user" class="com.x.pojo.User"/>
@Component //组件
@Scope("prototype")
public class User {
//等价于 <property name="name" value="嘿嘿"/>
// @Value("嘿嘿")
public String name;
//放set方法也行
@Value("嘿嘿")
public void setName(String name) {
this.name = name;
}
}
@Component
注解用来将类声明为容器的组件,当容器启动后,通过扫描,会生成对应的对象交给容器托管。
@Value
注解用来给属性赋值,可以直接放在属性上,也可以放在对应的set
方法上
@Scope
注解声明 类的作用域
2.3 自动装配
自动装配针对的是引入型对象
自动装配和依赖注入有什么联系与区别???
让我们来看看 Gpt 的回答
自动装配(Autowired)和依赖注入(Dependency Injection)在Spring框架中是相关概念,它们实现了相同的目标,即将一个对象的依赖关系注入到另一个对象中。然而,它们之间存在一些联系和区别:
联系:
- 实现方式: 自动装配是一种依赖注入的方式,它可以通过
@Autowired
注解(基于注解的自动装配)或者在XML配置文件中使用<property>
元素(基于XML的自动装配)来实现。- 目标: 自动装配和依赖注入的共同目标是减少硬编码,提高代码的灵活性和可维护性,同时降低了对象之间的耦合度。
- 由Spring容器管理: 在两种情况下,Spring容器负责管理Bean之间的关系,确保依赖的Bean在需要的时候能够被正确注入到目标Bean中。
区别:
- 显式配置 vs 隐式配置: 依赖注入可以通过显式配置(在XML配置文件或Java配置类中明确指定依赖关系)或者隐式配置(使用
@Autowired
注解进行自动装配)来实现。自动装配是一种依赖注入的方式,它是一种自动化的、隐式的依赖注入。- 灵活性: 自动装配提供了更大的灵活性,因为它不需要在配置文件中显式地指定每个Bean的依赖关系。相反,Spring容器会根据自动装配规则自动地完成依赖注入,这样就减少了配置的工作量,同时也更容易适应变化。
- 粒度: 依赖注入的粒度更细,可以对一个类的特定属性进行注入,也可以对构造方法进行注入,而自动装配通常是针对整个类进行的,它会自动装配类中所有符合条件的属性。
综上所述,自动装配是依赖注入的一种实现方式,它通过隐式的方式完成依赖注入,提高了配置的便利性和灵活性。依赖注入是一种更广泛的概念,它包括了显式配置和隐式配置两种方式,而自动装配则是依赖注入的一种特殊形式。
public class People {
private Dog dog; //用来测试 xml 文件
private Cat cat; // 用来测试 注解
}
public class Cat {
String name;
}
public class Dog {
private String name;
}
放在此处,下面要用到
2.3.1 基于XML配置文件
自动装配是spring满足bean依赖的一种方式。spring会通过在上下文寻找匹配,实现bean的自动装配。
测试文件
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
People people = context.getBean("people", People.class);
System.out.println(people.getDog().getName());
}
ByName:
使用 byName
它会根据 people
对应的属性名去匹配 id
名与之相同的 bean
;
(注意:它匹配id之后会进行一个类型的检查,总不能给 cat属性装配一个 dog 对象吧)
<bean id="dog" class="com.x.pojo.Dog">
<property name="name" value="dog"/>
</bean>
<bean id="dog2" class="com.x.pojo.Dog">
<property name="name" value="dong2"/>
</bean>
<bean id="people" class="com.x.pojo.People" autowire="byName"/>
输出结果为 : dog ;
总结: byName
根据 name 去匹配 ,如果 没有匹配到 就会报错
ByType :
使用 byType
它会根据 people
对应的属性的类型名去匹配 class属性对应与之对应的 bean
<bean id="dog1" class="com.x.pojo.Dog">
<property name="name" value="dog"/>
</bean>
<bean id="people" class="com.x.pojo.People" autowire="byType"/>
输出结果: dog
总结:byType
的根据属性去匹配,如果有多个 匹配 的 bean 则会报错
2.3.2 基于注解
xml文件的形式 是在 标签使用 autowired
属性
注解则有两种常用的: @Autowired
和@Resource
使用方法很简单:我们直接再对应的属性上或者 set方法上添加注解就行
public class People {
@Autowired //注意 Resource 不能用在 构造方法上
People(){
}
@Autowired
//@Resource
private Cat cat;
private String name;
public Cat getCat() {
return cat;
}
//@Resource
@Autowired
public void setCat(Cat cat) {
this.cat = cat;
}
二者还是有区别的
1、来源不同:
- @Autowired 是 Spring 定义的注解
- @Resource 是 Java 定义的注解(记得导入相应的依赖)
2、依赖查找顺序不同
@Autowired
是先根据类型(byType)查找,如果存在多个 Bean 再根据名称(byName)进行查找@Resource
是先根据名称查找,如果(根据名称)查找不到,再根据类型进行查找
3、参数不同
@Autowired
只支持设置一个 required 的参数- 可以搭配
@Qualifier
注解,但是一旦指定名字且没有对应的id,也不会进行type匹配了
- 可以搭配
@Resource
支持很多个
4、用法不同
@Autowired
可以用在 属性,set方法,构造器方法@Resource
不能用在构造方法
2.4 使用注解实现bean的托管
我们发现前面我们每声明一个类都要在 xml 文件中添加一个对应的标签,当类比较多时,这个工作量就会很大。有没有别的方法呢?
这就要提到我们的@Component
注解了。使用这个注解,我们要在配置文件中进行声明
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.x"/>
<!--开启注解配置-->
<context:annotation-config/>
<context:component-scan base-package="com.x"/>
当spring容器启动后,他就会自动去扫描 com.x
包下所有添加了
@Component、@controller、@service、@repository
注解的类,将其看成bean交给容器托管。
我们在类上添加一个 @Component 注解,就可以将类标记为Spring中的bean了
@Component //组件
public class User {
//等价于 <property name="name" value="嘿嘿"/>
public String name;
@Value("嘿嘿")
public void setName(String name) {
this.name = name;
}
}
基于 @Component 扩展的注解还有三个: @controller、@service、@repository
1、@controller: controller控制器层(注入服务)
2、@service : service服务层(注入dao)
3、@repository : dao持久层(实现dao访问)
4、@component: 标注一个类为Spring容器的Bean,(把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>)
这里不具体讲四者有什么区别,只是希望大家简单的知道 @service 标记的类,说说这个类是service层的实现就够了
2.5 配置类实现
JavaConfig是Spring的一个子项目 ,在Spring4之后,它变成了核心功能(SpringBoot中推荐使用这种方法)
配置类
myConfig2.class
@Configuration
@ComponentScan(basePackages = "com.x.pojo")
public class myConfig2 {}
myConfig.class
package com.x.config;
@Configuration //它也会被spring托管,它的本质也是一个组件
// @Configuration 代表他是一个配置类,就和beans.xml一样
@Import(myConfig2.class)
public class myConfig {
//注册一个bean,就相当于之前写的一个bean标签
//这个方法的名字,就相当于id
//返回值,就相当于 class属性
@Bean
User getUser(){
return new User();//返回要注入bean的对象
}
}
@Configration
注解:声明当前类是一个配置类,相当于 Spring 中的一个 XML 文件
@Bean
注解:作用在方法上,声明当前方法的返回值是一个 Bean
配置类的加载
我们发现此处用到了一个新标签 @Bean
,我们对比最开始用到的
@Component
2.4.1 @Bean 和 @Component 的区别
@Component(和@Service和@Repository)用于自动检测和使用类路径扫描自动配置bean。注释类和bean之间存在隐式的一对一映射(即每个类一个bean)。
@Bean用于显式声明单个bean,而不是让Spring像上面那样自动执行它。它将bean的声明与类定义分离,并允许您精确地创建和配置bean该怎么理解这段话呢?
首先 我们知道 @Component 是放在类上的,这样就可以将这个类标记为bean,并交给spring托管。我们完完全全就是在 这个类的 “源码”上进行的配置。那如果我们引入的是第三方的类,不知道源码,那我们怎么用@Component注释呢?这个时候就要用 @Bean了。也就是所谓的声明与类定义分离。我们不需要知道类是怎么定义的。我们只需要声明一个这个类的对象?
那可能又有疑问了,既然是声明对象为什么不这样写呢?
@Configuration
public class AppConfig {
@Bean
User user;
}
现在我们去看@Bean的源码:这是源码的第一句话
Indicates that a method produces a bean to be managed by the Spring container.
中文意思是:指示方法生成要由 Spring 容器管理的 Bean。这样就应该能明白为啥放在方法上了吧。
3、Spring Bean
3.1、Spring Bean是什么?
Bean作为Spring框架面试中不可或缺的概念,其本质上是指代任何被Spring加载生成出来的对象。(本质上区别于Java Bean,Java Bean是对于Java类的一种规范定义。)Spring Bean代表着Spring中最小的执行单位,其加载、作用域、生命周期的管理都由Spring操作。可见Spring Bean在整个Spring框架中的重要地位。
什么是Java Bean
并非所有的类都是 Java Bean,其是一种特殊的类,具有以下特征:
- 提供一个默认的无参构造函数。
- 需要被序列化并且实现了 Serializable 接口。
- 可能有一系列可读写属性,并且一般是 private 的。
- 可能有一系列的 getter 或 setter 方法。
3.2、Spring Bean的作用域
1、singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。
2、prototype:为每一个bean请求创建一个实例。
3、request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
4、session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。
5、global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。
3.3、Spring 的容器启动流程
详细见此文章 Spring Ioc 容器启动流程-CSDN博客
3.4、Spring Bean的生命周期
正在创作中。。。
3.5、Spring Bean的线程安全问题
Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。
(1)对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。
(2)对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。
有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。
无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。
对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。
也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
3.6、Spring Bean的循环依赖问题
正在创作中
3.7、BeanFactory 和 ApplicationContext 区别
BeanFactory
是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理。
ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
- 继承MessageSource,因此支持国际化。
- 资源文件访问,如URL和文件(ResourceLoader)。
- 载入多个(有继承关系)上下文(即同时加载多个配置文件) ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
- 提供在监听器中注册bean的事件。
区别:
1、
BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能提前发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。
ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。相对于BeanFactory,ApplicationContext 唯一的不足是占用内存空间,当应用程序配置Bean较多时,程序启动较慢。
2、
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
3、
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
4、Spring Aop
4.1、什么是 Aop
OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。
4.2、Spring AOP里面的几个名词的概念:
(1)连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。
(2)切面(Aspect):被抽取出来的公共模块,可以用来会横切多个对象。Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。
在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。
(3)切点(Pointcut):切点用于定义 要对哪些Join point进行拦截。
切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。annotation方式可以指定被哪些注解修饰的代码进行拦截。
(4)通知(Advice):指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。
(5)目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。
(6)织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。
(7)引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
几个概念的关系图可以参考下图:
(1)连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。
(2)切面(Aspect):被抽取出来的公共模块,可以用来会横切多个对象。Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。
在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。
(3)切点(Pointcut):切点用于定义 要对哪些Join point进行拦截。
切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。annotation方式可以指定被哪些注解修饰的代码进行拦截。
(4)通知(Advice):指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。
(5)目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。
(6)织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。
(7)引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
几个概念的关系图可以参考下图:
4.3 通知
Advice的类型:
(1)前置通知(Before Advice):在连接点(Join point)之前执行的通知。
(2)后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
(3)环绕通知(Around Advice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
(4)返回后通知(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)
(5)抛出异常后通知(AfterThrowing advice):在方法抛出异常退出时执行的通知
Advice的执行顺序:
没有异常情况下的执行顺序:
around before advice
before advice
target method 执行
after advice
around after advice
after Returning advice
出现异常情况下的执行顺序:
around before advice
before advice
target method 执行
after advice
around after advice
afterThrowing advice
java.lang.RuntimeException:异常发生
4.4、使用方法
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
用来测试的代码
第一种:原生的AOP
<bean id="userService" class="com.x.service.UserServiceImpl"/>
<bean id="log" class="com.x.Log.Log"/>
<bean id="afterLog" class="com.x.Log.AfterLog"/>
<!--配置AOP:需要导入AOP的约束-->
<aop:config>
<!--切入点,我们需要在哪执行我们的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.x.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
自定义的log类,要实现相应的接口
public class AfterLog implements AfterReturningAdvice {
//返回后的
//returnValue : 返回值
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
}
}
public class Log implements MethodBeforeAdvice {
//method:要执行对象的目标方法
//objects:参数
//target:目标对象
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
输出结果为:
第二种:自定义方法
<bean id="diy" class="com.x.diy.DiyPointCut"/>
<aop:config>
<!--自定义切面,ref要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.x.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
public class DiyPointCut {
public void before(){
System.out.println("==========方法执行前==========");
}
public void after(){
System.out.println("==========方法执行后==========");
}
}
执行结果:
第三种:使用注解
<!--方式三-->
<bean id="apc" class="com.x.diy.AnnotationPointCut"/>
<!--开启注解支持 JDK(默认 proxy-target-class="false") cglib(proxy-target-class="true")-->
<aop:aspectj-autoproxy />
@Aspect//标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.x.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("==========方法执行前==========");
}
@After("execution(* com.x.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("==========方法执行后==========");
}
//在就环绕增强中,我们可以给定一个参数,待定我们要获取的处理切入的点
@Around("execution(* com.x.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Signature signature = jp.getSignature();//获得签名
System.out.println("signature:"+signature);
//执行方法
jp.proceed();//方法执行
System.out.println("环绕后");
}
}
执行结果:
5、Spring 事务
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过 redo log 和 undo log实现的。Spring会在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。
5.1、Spring事务的种类:
spring支持编程式事务管理和声明式事务管理两种方式:
①编程式事务管理使用TransactionTemplate。
②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
5.2、spring的事务传播机制:
pring事务的传播机制说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。事务传播机制实际上是使用简单的ThreadLocal实现的,所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。
PROPAGATION_REQUIRED:(默认传播行为)如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。
② PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务进行执行。
③ PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行。‘
④ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑤ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行。
⑥ PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;如果当前不存在事务,就抛出异常。
⑦ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
5.3、Spring中的隔离级别:
① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
② ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据。
③ ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据。
④ ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的。
⑤ ISOLATION_SERIALIZABLE:所有事务逐个依次执行。