AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过 OOP 允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在 OOP 设计中,它导致了大量代码的重复,而不利于各个模块的重用。
OOP 面向对象,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。
AOP 技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名 为" Aspect ",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。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. 引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
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. 书写通知
@Component
@Aspect
public class FirstAdvice {
@Pointcut("execution(* com.dailyblue.java.target.*.*(..))")
private void execute() {
}
// 前置通知
@Before("execution(* com.dailyblue.java.target.*.*(..))")
public void before() {
System.out.println("我会在目标对象之前执行");
}
//后置通知
@After("execute()")
public void after() {
System.out.println("我会在目标对象之后执行");
}
// 最终通知
@AfterReturning("execute()")
public void afterReturning() {
System.out.println("我是最终会执行的方法");
}
@Around("execute()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前");
proceedingJoinPoint.proceed();
System.out.println("环绕之后");
}
}
3. 自动扫描
@Configuration
// 自动产生引入过程
@EnableAspectJAutoProxy
public class App {
public static void main(String[] args) {
ApplicationContext beans = new AnnotationConfigApplicationContext("com.dailyblue.java");
GuanweiTarget gdao = beans.getBean("guanweiTarget ", GuanweiTarget.class);
gdao.print();
}
}
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条数据了,就产生了幻读。也就是说,当前事务读第一次取到的数据比后来读取到数据条目少。
是否只读
如果将事务设置为只读,表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
事务超时
事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。在 TransactionDefinition 中以 int 的值来表示超时时间,默认值是-1,其单位是秒。
回滚规则
回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚。
在框架中使用事务
在 xml 中注册事务管理
在 service 上引入注解
@Transactional 注解介绍
在启动类中开始事务
/ 开启事务管理器
@EnableTransactionManagement
@Configuration
@MapperScan("org.dailyblue.spring.ioc.mybatis.mapper")
public class MybatisConfig {
}