Spring AOP
一、概念
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。
OOP(Object Oriented Programming)面向对象编程
我们都知道OOP是一种编程思想,那么AOP也是一种编程思想,编程思想主要的内容就是指导程序员该
如何编写程序,所以它们两个是不同的编程范式
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。AOP是在不改原有代码的前提下对其进行增强。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。(面试答)
不过 OOP 允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在 OOP 设计中,它导致了大量代码的重复,而不利于各个模块的重用。
OOP 面向对象,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。
AOP 技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名 为" Aspect ",即切面。
所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
二、AOP的作用
作用:在不惊动原始设计的基础上为其进行功能增强,前面咱们有技术就可以实现这样的功能即代理模式。
面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足。
利用AOP对业务逻辑的各个部分进行隔离,降低业务逻辑的耦合性,提高程序的可重用型和开发效率。
主要用于对同一对象层次的公用行为建模。
速记:提高代码的可重用性、业务代码编码更简洁、业务代码维护更高效、 业务功能拓展更便捷。
三、AOP 的特性
切面(Aspect)
被抽取的公共模块,可能会横切多个对象。 在Spring AOP中,切面可以使用通用类或者在普通类中以@AspectJ注解来实现。
连接点(Join point)
指方法在Spring AOP中,一个连接点总是代表一个方法的执行。
通知(Advice)
在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
切入点(Pointcut)
切入点是指我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法,比如指定拦截add*、search*。
引入(Introduction)
声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现IsModified接口,以便简化缓存机制。在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。
目标对象(Target Object)
被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做被通知(adviced)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxy)对象。
织入(Weaving)
指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。
概念的关系图
切入点(pointcut)和连接点(join point)匹配的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。切入点使得定位通知(advice)可独立于OO层次。例如,一个提供声明式事务管理的around通知可以被应用到一组横跨多个对象中的方法上。
四、AOP 通知的分类
- 前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
- 返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
- 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
- 后置(最终)通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- 环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。环绕通知是最常用的一种通知类型。
五、AOP 实现方式
xml 方式
1. 引入依赖
<dependencies>
<!-- 用于支持面向切面编程(AOP)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.22</version>
</dependency>
<!-- AspectJ 是一个面向切面编程(AOP)的框架,aspectjrt 是 AspectJ 的运行时库。-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
<!-- aspectjweaver 是 AspectJ 的一个 weaver(织入器),用于在运行时实现切面的织入。-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
2. 书写目标对象接口和实现类
public interface GuanWeiTarget {
void add(String name, int age, char sex);
void update(int id, String name, int age, char sex);
User findById(int id);
List<User> findAll();
}
public class GuanWeiTargetImpl implements GuanWeiTarget {
public void add(String name, int age, char sex) {
System.out.println("我是添加的实现类");
}
public void update(int id, String name, int age, char sex) {
System.out.println("我是修改的实现类");
}
public User findById(int id) {
System.out.println("我是按照ID查询的实现类");
return null;
}
public List<User> findAll() {
System.out.println("我是全查询的实现类");
return null;
}
}
3. 书写通知,这里写了前置和后置通知
@Component
public class FirstAdvice implements MethodBeforeAdvice, AfterReturningAdvice{
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("在GuanweiTarget目标对象之后执行");
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("在GuanweiTarget目标对象之前执行");
}
}
4. 书写 xml 的配置,设置切入点和切面等的关系
<?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/>
<context:component-scan base-package="com.dailyblue.java.advice,com.dailyblue.java.target"/>
<!--定义切入点的位置,这里的位置是find方法-->
<bean id="pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*find.*" />
</bean>
<!--使切入点和通知相关联,完成切面配置-->
<bean id="pointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="firstAdvice" />
<property name="pointcut" ref="pointcut" />
</bean>
<!--设置代理 proxy代理了target指向的对象-->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--被代理的对象-->
<property name="target" ref="guanweiTargetImpl" />
<!--使用切面-->
<property name="interceptorNames" value="pointcutAdvisor"/>
<!--代理接口-->
<property name="proxyInterfaces" value="com.dailyblue.java.target.GuanweiTarget" />
</bean>
</beans>
5. 测试
ApplicationContext beans = new ClassPathXmlApplicationContext("applicationContext.xml");
GuanweiTarget gt = beans.getBean("proxy",GuanweiTarget.class);
gt.findAll();
注意:这里使用的是 proxy 获取的对象。
注解方式
1. 书写目标对象
@Component
public class GuanweiTarget {
public void print(){
System.out.println("关为执行了这个方法");
}
}
2. 书写通知
//使得 Spring 能够自动扫描并将其纳入应用上下文中管理。
@Component
//表明该类是一个切面类,其中定义了一组切面逻辑。
@Aspect
public class SecondAdvice {
// 用于定义切入点,即切面在哪个目标对象的哪个方法上织入。
@Pointcut("execution(* com.zgh.annotation.target.impl.SecondTargetImpl.*(..))")
private void execute() {
}
// 定义了一个前置通知,表示在目标对象的方法执行之前执行。
@Before("execution(* com.zgh.annotation.target.impl.SecondTargetImpl.find(..))")
// 表示在目标对象的方法执行之前执行。
public void before() {
System.out.println("我会在你的代码执行之前执行");
}
// 定义了一个最终通知,表示在目标对象的方法执行之后执行,无论方法是否抛出异常。
@AfterReturning("execute()")
public void after() {
System.out.println("我会在你的代码执行之后执行");
}
@SneakyThrows
// 定义了一个环绕通知,表示在目标对象的方法执行前后执行。
@Around("execute()")
public void around(ProceedingJoinPoint pjp) {
System.out.println("环绕之前");
pjp.proceed();
System.out.println("环绕之后");
}
// 定义了一个环绕通知,表示在目标对象的方法执行前后执行。
@After("execute()")
public void fin() {
System.out.println("我是最终执行的");
}
// 异常通知的注解。它表示在目标对象的方法抛出异常时执行的通知。
// @AfterThrowing 注解会在切入点(即 execute() 方法指定的位置)处的目标对象方法抛出异常时触发。
// 它对应的通知方法会在异常抛出后立即执行。
@AfterThrowing("execute()")
public void exception() {
System.out.println("代码出现了异常情况");
}
}
3. 自动扫描
//表示该类是一个配置类
@Configuration
//用于启用 AspectJ 的自动代理功能。它会扫描应用程序中的切面类,并将它们织入到相应的目标对象中。
@EnableAspectJAutoProxy
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext beans = new AnnotationConfigApplicationContext("com.zgh.annotation");
// 从应用程序上下文中获取一个名为 SecondTarget 的 bean。SecondTarget 是一个目标对象,我们将在后面调用它的 save() 方法。
SecondTarget secondTarget = beans.getBean(SecondTarget.class);
//在启用了 AspectJ 自动代理的情况下,该方法可能会被织入一个或多个切面的逻辑
secondTarget.save();
}
}
spring_aop
项目结构
1、xml 方式
导入依赖
<dependencies>
<!-- 用于支持面向切面编程(AOP)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.22</version>
</dependency>
<!-- AspectJ 是一个面向切面编程(AOP)的框架,aspectjrt 是 AspectJ 的运行时库。-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
<!-- aspectjweaver 是 AspectJ 的一个 weaver(织入器),用于在运行时实现切面的织入。-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
traget包
public interface FirstTarget {
void save();
void update();
void delete();
void find();
void findAll();
void findById();
}
public class FirstTargetImpl implements FirstTarget {
@Override
public void save() {
System.out.println("save");
}
@Override
public void update() {
System.out.println("update");
}
@Override
public void delete() {
System.out.println("delete");
}
@Override
public void find() {
System.out.println("find");
}
@Override
public void findAll() {
System.out.println("findAll");
}
@Override
public void findById() {
System.out.println("findById");
}
}
App
public class App {
public static void main(String[] args) {
ApplicationContext beans = new ClassPathXmlApplicationContext("spring.xml");
FirstTarget target = beans.getBean("proxy", FirstTarget.class);
//target.findAll();
target.delete();
}
}
spring.xml
<?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="firstTarget" class="com.zgh.xml.target.impl.FirstTargetImpl"/>
<bean id="firstAdvice" class="com.zgh.xml.advice.FirstAdvice"/>
<!-- 定义切入点的位置,这里的位置是find方法-->
<bean id="pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*find.*"/>
</bean>
<!-- 使切入点和通知相关联,完成切面配置-->
<bean id="pointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="pointcut"/>
<property name="advice" ref="firstAdvice"/>
</bean>
<!--设置代理 proxy代理了target指向的对象-->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--被代理的对象-->
<property name="target" ref="firstTarget"/>
<!--使用切面-->
<property name="interfaces" value="com.zgh.xml.target.FirstTarget"/>
<!--代理接口-->
<property name="interceptorNames" value="pointcutAdvisor"/>
</bean>
</beans>
测试效果
1、findAll方法
2、delete
注:相交的连接点里面没有delete,只有find.
2、注解方式
target包
public interface SecondTarget {
void find();
void save();
}
@Component
public class SecondTargetImpl implements SecondTarget {
@Override
public void find() {
System.out.println("find");
}
@Override
public void save() {
System.out.println("save");
int a = 9 / 0;
}
}
advice包
@Component
@Aspect
public class SecondAdvice {
@Pointcut("execution(* com.zgh.annotation.target.impl.SecondTargetImpl.*(..))")
private void execute() {
}
@Before("execution(* com.zgh.annotation.target.impl.SecondTargetImpl.find(..))")
public void before() {
System.out.println("我会在你的代码执行之前执行");
}
@AfterReturning("execute()")
public void after() {
System.out.println("我会在你的代码执行之后执行");
}
@SneakyThrows
@Around("execute()")
public void around(ProceedingJoinPoint pjp) {
System.out.println("环绕之前");
pjp.proceed();
System.out.println("环绕之后");
}
@After("execute()")
public void fin() {
System.out.println("我是最终执行的");
}
@AfterThrowing("execute()")
public void exception() {
System.out.println("代码出现了异常情况");
}
}
App
//该类是一个配置类
@Configuration
@EnableAspectJAutoProxy
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext beans = new AnnotationConfigApplicationContext("com.zgh.annotation");
SecondTarget secondTarget = beans.getBean(SecondTarget.class);
secondTarget.save();
}
}
六、Spring 的事务管理
隔离级别
什么是事务的隔离级别?我们知道,隔离性是事务的四大特性之一,表示多个并发事务之间的数据要相互隔离,隔离级别就是用来描述并发事务之间隔离程度的大小,在并发事务之间如果不考虑隔离性,会引发如下安全性问题:
- 脏读 :一个事务读到了另一个事务的未提交的数据。
- 不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致。
- 幻读 :一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致。
在 Spring 事务管理中,为我们定义了如下的隔离级别:
-
ISOLATION_DEFAULT:使用数据库默认的隔离级别。
-
ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取已改变而没有提交的数据,可能会导致脏读、幻读或不可重复读。
-
ISOLATION_READ_COMMITTED:允许读取事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
-
ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据事务本身改变,可以阻止脏读和不可重复读,但幻读仍有可能发生。
-
ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保不发生脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的。
不可重复读:事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。也就是说,当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配,也就照应了不可重复读的语义。
幻读:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。也就是说,当前事务读第一次取到的数据比后来读取到数据条目少。
传播行为
Spring事务传播机制规定了事务方法和事务方法发生嵌套调用时事务如何进行传播,即协调已经有事务标识的方法之间的发生调用时的事务上下文的规则。
Spring定义了七种传播行为,这里以方法A和方法B发生嵌套调用时如何传播事务为例说明:
- PROPAGATION_REQUIRED:A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务。
- PROPAGATION_SUPPORTS:A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行。
- PROPAGATION_MANDATORY:A如果有事务,B将使用该事务;如果A没有事务,B将抛异常。
- PROPAGATION_REQUIRES_NEW:A如果有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务。
- PROPAGATION_NOT_SUPPORTED:A如果有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行。
- PROPAGATION_NEVER:A如果有事务,B将抛异常;A如果没有事务,B将以非事务执行。
- PROPAGATION_NESTED:A和B底层采用保存点机制,形成嵌套事务。
是否只读
如果将事务设置为只读,表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
事务超时
事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。在 TransactionDefinition 中以 int 的值来表示超时时间,默认值是-1,其单位是秒。
回滚规则
回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚。
七、在框架中使用事务
在 xml 中注册事务管理
在 service 上引入注解
@Transactional 注解介绍
属性名 | 说明 |
---|---|
name | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 |
propagation | 事务的传播行为,默认值为 REQUIRED。 |
isolation | 事务的隔离度,默认值采用 DEFAULT。 |
timeout | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
no-rollback- for | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |
在启动类中开始事务
/ 开启事务管理器
@EnableTransactionManagement
@Configuration
@MapperScan("org.dailyblue.spring.ioc.mybatis.mapper")
public class MybatisConfig {
}
面试题
1.什么是SpringAop?
一般称为面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
简单的说:就是将程序中重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
(1)切面(Aspect): 在Spring Aop指定就是“切面类” ,切面类会管理着切点、通知。
(2)连接点(Join point): 连接点是在应用执行过程中能够插入切面(Aspect)的一个点。这些点可以是调用方法时,抛出异常时。它是一个虚拟的概念,例如坐地铁的时候,每一个站都可以下车,那么这每一个站都是一个接入点。假如一个对象中有多个方法,那么这个每一个方法就是一个连接点。
(3)通知(Advice): 就是需要增加到业务方法中的公共代码, 通知有很多种类型分别可以在需要增加的业务方法不同位置进行执行(前置通知、后置通知、异常通知、返回通知、环绕通知)
(4)切点(Pointcut):核心方法, 结合切点表达式进行实现
(5)目标对象(Target Object):指定是增强的对象
(6)织入(Weaving) :spring aop用的织入方式:动态代理。 就是为目标对象创建动态代理的过程就叫织入。
核心原理:观察所调用的方法是否符合切入点表达式,如果符合,则使用代理执行增强方法
2.Spring通知有哪些类型
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知(After-returning ):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
3.JDK动态代理和CGLIB动态代理的区别
1.jdk动态代理只提供实现接口的目标类代理,不支持没有实现接口的目标类的代理。如果目标类没有实现接口,只能用cglib代理。
2.jdk动态代理会在运行时为目标类生成一个动态代理类$proxy*.class。cglib的底层是通过ASM在运行时动态生成目标类的子类,还会有其它类。
3.jdk动态代理的代理类实现了目标类实现的接口,并且会实现接口所有方法来代码增强。cglib动态代理会重写父类所有的方法来代码增强。
4.jdk动态代理调用时先去调用处理类进行增强,再通过反射的方式调用目标类的方法。cglib动态代理调用时先通过代理类进行增强,再直接调用父类对应的方法进行调用目标方法。
5.jdk动态代理如果目标类未实现接口则无法代理,cglib是通过继承的方式来动态代理,若目标类被final关键字修饰,则无法使用cglib做动态代理。
6.性能上:在老版的jdk,jdk代理生成的类速度快,通过反射调用慢,cglib是jdk代理速度的10倍左右,jdk在版本每次升级都会有很大的性能提升,cglib停滞不前,jdk7 8的动态代理性能在1万次实验中比cglib要快20%左右。
4.SpringAop的工作过程
1.Spring 创建IOC容器
先扫扫描包中的所有由@Service 和@Component修饰的类,并为它们创建对象,放在Spring IOC容器中。
2.寻找切面类
Spring在创建完对象后,开始寻找由 @Aspect 修饰的切面类并获取切面类中的所有方法。
3.寻找切面类的方法中带有表达式的部分
接下来,Spring找到所有由合法表达式修饰的方法
4.查找有相应方法的类
随后,Spring检查它所扫描到的所有类,并将上一步中找到的方法与所有类进行对照,找出有这个(些)方法的类(这个类就是被代理类)。
5.创建动态对象
最后,Spring根据上一步找到的被代理类以及切面类创建动态类的动态对象并放入Spring IOC容器中。
5.Spring的事务管理机制
使用AOP实现
编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。业务层的每个方法都是一个事务(业务逻辑层不要捕获异常)
6.Spring的事务传播行为
7.Spring事务的隔离级别
8.Spring AOP用的是哪种设计模式?
代理模式。
通过代理,可以详细控制访问某个或者某类对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。
谈谈你对代理模式的理解?
通过代理,控制对对象的访问,它的设计思路是:定义一个抽象角色,让代理角色和真实角色分别去实现它
真实角色就是实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用,它只关注真正的业务逻辑。
代理角色就是实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并在前后可以附加自己的操作。
9.静态代理和动态代理有什么区别?
代理模式分为静态代理和动态代理,静态代理是我们自己创建一个代理类,而动态代理是程序自动帮我们生成一个代理。
- 静态代理:
静态代理需要创建真实角色和代理角色,分别实现抽象角色,真实角色可以直接实现,代理类需要将真是的对象传进来。
(1).接受真实对象
(2).通过构造方法传进来真实的对象
-
动态代理:
动态代理比静态代理使用的更广泛,在本质上,动态代理的代理类不用我们来管,交给工具去生成代理类即可。
- JDK 动态代理:
我们不需要去创建代理类,那我们只需要编写一个动态处理器就可以了,真正的代理对象由JDK在运行时为我们动态的创建。
动态代理处理类:
(1).接收真实对象
(2).通过构造方法传进来真实的对象
(3).给真实对象生成一个代理对象实例
相对于静态代理,JDK 动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前第啊用InvokeHandler来处理。
JDK 动态代理的特点:JDK 实现动态代理需要实现类通过接口定义业务方法。
- CGLIB 动态代理:
对于没有接口的类,就需要CGLIB。
原理:通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。采用继承,不能对final修饰的类进行代理。
使用CGLIB需要实现MethodInterceptor接口,并重写intercept方法,在该方法中对原始要执行的方法前后做增强处理。该类的代理对象可以使用代码中的字节码增强器来获取。
CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK要合适一些。
CGLIB采用动态创建子类的方法,对于final修饰的方法无法进行代理。
要代理的类中可能不止一种方法,有时候需要对特定的方法进行增强处理,可以对传入的method参数进行方法名的判断,再做相应的处理。
10.Spring AOP采用哪种代理?
Spring 中代理的选择:Spring中会通过是否是实现了接口来进行那种动态代理方式的选择.
JDK 动态代理和CGLIB 动态代理均是实现Spring AOP的基础。
在Spring 5源码中:
(1).判断是否是最优化的,判断是否是代理类,判断代理的对象是否又实现接口。前两个判断默认返回false
(2).判断目标类是否为null,若为null,则抛出异常 (AopConfigException)
(3).判断目标类是否是接口或者目标类是否是Proxy类型,若是则使用JDK动态代理
(4).若配置使用CGLIB进行动态代理或者目标类没有接口,那么使用CGLIB的方式创建代理对象
(5).若(2)、(3)、(4)方法中没有一个为true,那使用JDK的提供的代理方式生成代理对象。
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP;如果目标对象没有实现接口,则采用CGLIB库,Spring会自动在JDK动态代理和CGLIB动态代理之间转换。
速记:
(1) SpringAOP是动态代理来实现的。有两种代理方式:JDK动态代理与CGLIB动态代理
(2) JDK动态代理:是通过反射来接收被代理类,要求必须实现一个接口
(3) CGLIB动态代理:当被代理类没有实现一个接口的时候,就会使用CGLIB进行动态代理。CGLIB动态代理通过运行时动态生成被代理类的子类,运用继承的方式来实现动态代理。如果被代理类被final修饰了,那么就不能使用CGLIB进行动态代理了。
11.AOP的作用和优势
- 作用:从定义中来看,就是为了在程序运行期间,不修改源码对已有方法进行增强。
- 优势:减少重复代码 提交了开发效率 维护方便
- 实现方式: 就是动态代理的技术
- 具体的作用:实现事务的控制 日志 和 安全模块
12.AOP相关术语
Joinpoint(连接点): 被拦截到的点,在Spring中指的是方法,且Spring中只支持方法类型的连接点.
Pointcut(切入点): 我们对其进行增强的方法.
Advice(通知/增强): 对切入点进行的增强操作包括前置通知,后置通知,异常通知,最终通知,环绕通知 五种
Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 属性
Target(目标对象):代理的目标对象
Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程
目标对象的生命周期里有多个点可以进行织入:
编译期:切面在目标类编译时被织入。这种方式需要特殊的编译 器。AspectJ的织入编译器就是以这种方式织入切面的
类加载期:切面在目标类加载到JVM时被织入。这种方式需要特 殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织 入切面时,AOP容器会为目标对象动态地创建一个代理对象。 Spring AOP就是以这种方式织入切面的
Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面): 是切入点和通知的结合