Spring Aop简介

1.1什么是AOP

AOP为Aspect Oriented Programming的缩写,意思是面向切面编程,是通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

Aop是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分主键的耦合度降低,提高程序的可重用行,同时提高了开发的效率。

1.2AOP的作用及其优势

  • 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强

1.3AOP的底层实现

实际上,AOP的底层是通过Spring提高的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的接入,在去调用目标对象的方法,从而完成功能的增强。

1.4AOP的动态代理技术

常用的动态代理技术

  • JDK代理:基于接口的动态代理技术
  • cglib代理:基于父类的动态代理技术

![image-20230315154720653](C:\Users\Lenovo\Desktop\Md文件\spring+mybatis+springmvc\Spring\Spring Aop和事务控制\image-20230315154720653.png)

1.5JDK的动态代理

![image-20230315160349011](C:\Users\Lenovo\Desktop\Md文件\spring+mybatis+springmvc\Spring\Spring Aop和事务控制\image-20230315160349011.png)

public class ProxyTest {
    public static void main(String[] args) {
        //创建目标对象
        final Target target = new Target();
        //增强对象
        final Advice advice = new Advice();

        TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        advice.before();
                        Object invoke = method.invoke(target, args);
                        advice.afterRetuning();
                        return invoke;
                    }
                }
        );
        proxy.save();
    }
}

1.6cglib的动态代理

public class ProxyTest {
    public static void main(String[] args) {
        //创建目标对象
        final Target target = new Target();
        //增强对象
        final Advice advice = new Advice();
        //1.创建增强器
        Enhancer enhancer = new Enhancer();
        //2.设置父类(目标)
        enhancer.setSuperclass(Target.class);
        //3.设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //执行前置
                advice.before();
                //执行目标
                Object invoke = method.invoke(target, args);
                //执行后置
                advice.afterRetuning();
                return invoke;
            }
        });
        //4.创建代理对象
        Target proxy =(Target) enhancer.create();
        proxy.save();
    }
}

1.7AOP相关概念

Spring的AOP实现底层就是对上面的动摇代理的代码进行了封装,封装后我们只需要对关注的部分进行代码编写,并通过配置的方法完成指定目标的增强。

在正式讲解AOP的操作之前,我们必须理解AOP的相关术语,常用的术语如下:

  • Target(目标对象):代理的目标对象
  • Proxy(代理):一个类被AOP增强后,就产生一个结果代理类
  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
  • Pointcut(切入点):所谓切入点是指我们要对哪些JoinPoint进行拦截的定义
  • Advice(通知/增强):所谓通知是指拦截到Joinpoint之后要做的事情就是通知
  • Aspect(切面):是切入点和通知的结合
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程,spring采用动态代理织入,而Aspectj采用编译期织入和类装载期织入

1.8AOP开发明确的实现

1.需要编写的内容

  • 编写核心业务代码(目标类的目标方法)
  • 编写切面类,切面类中有通知(增强功能方法)
  • 在配置文件中,配置植入关系,即将那些通知与哪些连接点进行结合

2.AOP技术实现的内容

Spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

3.AOP底层使用哪种代理方式

在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

1.9知识要点

aop:面向切面编程

aop底层实现:基于JDK或者Cglib的动态代理

aop重点概念:

  • Pointcut切入点:被增强的方法
  • Advice通知\增强:封装增强业务逻辑的方法
  • Aspect切面:切点+通知
  • Weaving织入:将切点与通知结合的过程

开发明确事项

  • 谁是切点
  • 谁是通知
  • 将切点和通知进行织入配置

Spring 基于XML的AOP开发

2.1快速入门

①导入AOP相关坐标

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.3.25</version>
</dependency>

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.4</version>
</dependency>

②创建目标接口和目标类(内部有切点)

在java包下创建com.名字.aop 包、然后创建MyAspcet类

③创建切面类(内部有增强方法)

public class MyAspect {
    public void before(){
        System.out.println("前置增强.......");
    }
}

④将目标类和切面类的对象创建权交给spring

<!--    目标对象-->
    <bean id="target" class="com.zy.aop.Target"></bean>
<!--    切面对象-->
    <bean id="myAspect" class="com.zy.aop.MyAspect"></bean>

⑤在applicationContext.xml中配置织入关系

<!--    配置织入,告诉spring框架 哪些方法(切点)需要进行哪些增强(前置、后置。。。)-->
    <aop:config>
<!--        声明切面-->
        <aop:aspect ref="myAspect">
<!--            切面,切点+通知-->
            <aop:before method="before" pointcut="execution(public void com.zy.aop.Target.save())">
                <!--或者-->
            <aop:before method="before" pointcut="execution(* com.zy.aop.*.*(..))"/>
            </aop:before>
        </aop:aspect>
    </aop:config>

⑥测试代码

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>5.3.25</version>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    @Autowired
    private TargetInterface target;

    @Test
    public void test1(){
        target.save();
    }
}

2.2XML配置AOP详解

1.切面表达式的写法

表达式语法:

execution([修饰符] 返回值类型 包名.类名.方法名(参数))
  • 访问修饰符可以省略
  • 返回值类型、包名、类名、方法名可以使用*号代替任意
  • 包名与类名之间一个点.代表当前报下的类,两个点..表示当前包及其子包下的类
  • 参数列表可以使用两个点..表示任意个数,任意类型的参数列表
excution(public void com.zy.aop.Target.method())

excution(void com.zy.aop.Target.*(..))

excution(* com.zy.aop.※.※(..))

excution(* com.zy.aop..※.※(..))

excution(* *..*.*(..))

2.通知的类型

通知的配置语法

<aop:通知类型 method="切面类中的方法名" pointcut="切点表达式"></aop:通知类型>
名称 标签 说明
前置通知 aop:before 用于配置前置通知,指定增强的方法在切入点方法之前执行
后置通知 aop:after-returning 用于配置后置通知,指定增强的方法在切入点方法之后执行
环绕通知 aop:around 用于配置环绕通知,指定增强的方法在切入点方法之前和之后都执行
异常抛出通知 aop:throwing 用于配置异常抛出通知,指定增强的方法在出现异常时执行
最终通知 aop:after 用于配置最终通知,无论增强方法执行是否有异常都会执行
public class MyAspect {
    public void before(){
        System.out.println("前置增强.......");
    }

    public void afterReturning(){
        System.out.println("后置增强.......");
    }

    //ProceedingJoinPoint 连接点
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前增强.......");
        //切点方法(目标方法)
        Object proceed = pjp.proceed();
        System.out.println("环绕后增强.......");
        return proceed;
    }

    public void afterThrowing(){
        System.out.println("异常抛出增强.......");
    }

    public void after(){
        System.out.println("最终增强.......");
    }
}
    <aop:config>
<!--        声明切面-->
        <aop:aspect ref="myAspect">
<!--            切面,切点+通知-->
<!--            <aop:before method="before" pointcut="execution(public void com.zy.aop.Target.save())"></aop:before>-->
<!--                <aop:before method="before" pointcut="execution(* com.zy.aop.*.*(..))"/>-->
<!--                <aop:after-returning method="afterReturning" pointcut="execution(* com.zy.aop.*.*(..))"/>-->
                <aop:around method="around" pointcut="execution(* com.zy.aop.*.*(..))"/>
                <aop:after-throwing method="afterThrowing" pointcut="execution(* com.zy.aop.*.*(..))"/>
                <aop:after method="after" pointcut="execution(* com.zy.aop.*.*(..))"/>
        </aop:aspect>
    </aop:config>

3.切点表达式的抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替pointcut属性来引用抽取后的切点表达式

  <aop:config>
<!--        声明切面-->
        <aop:aspect ref="myAspect">
<!--            抽取切点表达式-->
        <aop:pointcut id="myPointcut" expression="execution(* com.zy.aop.*.*(..))"></aop:pointcut>
            <aop:around method="around" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>

2.3知识要点

  • aop织入的配置

      <aop:config>
            <aop:aspect ref="切面类">
            <aop:after method="通知方法名称" pointcut="切面表达式"/>
            </aop:aspect>
        </aop:config>
    
  • 通知的类型:前置通知、后置通知、环绕通知、异常抛出通知、最终通知

  • 切点表达式的写法:

    execution([修饰符] 返回值类型 包名.类名.方法名(参数))
    

Spring基于注解的AOP开发

3.1快速入门

基于注解的aop开发步骤:

①创建目标接口和目标类(内部有切点)

在java包下创建com.名字.anno包,在该包下创建目标类Target

②创建切面类(内部有增强方法)

创建MyAspect切面类

③将目标类和切面类的对象创建权交给spring

@Component("target")
public class Target implements TargetInterface {
    @Override
    public void save() {
//        int i = 1/0;
        System.out.println("save running...");
    }
}
@Component("MyAspect")
public class MyAspect

④在切面类中使用注解配置织入关系

在切面类上加@Aspect标注当前类是一个切面类

@Component("MyAspect")
@Aspect //标注当前类是一个切面类
public class MyAspect {
    //配置前置通知
    @Before("execution(* com.zy.anno.*.*(..))")
    public void before(){
        System.out.println("前置增强.......");
    }
}

⑤在配置文件中开启扫描和AOP的自动代理

创建applicationContext-anno.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:aop="http://www.springframework.org/schema/aop"
       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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
>
<!--    开启组件扫描-->
<context:component-scan base-package="com.zy.anno"/>
    <!--    aop:自动代理-->
    <aop:aspectj-autoproxy/>
</beans>

⑥测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-anno.xml")
public class AnnoTest {
    @Autowired
    private TargetInterface target;

    @Test
    public void test1(){
        target.save();
    }
}

3.2注解配置AOP详解

1.注解通知的类型

通知的配置语法:@通知注解(“切面表达式”)

名称 标签 说明
前置通知 @Before 用于配置前置通知,指定增强的方法在切入点方法之前执行
后置通知 @AfterReturning 用于配置后置通知,指定增强的方法在切入点方法之后执行
环绕通知 @Around 用于配置环绕通知,指定增强的方法在切入点方法之前和之后都执行
异常抛出通知 @AfterThrowing 用于配置异常抛出通知,指定增强的方法在出现异常时执行
最终通知 @After 用于配置最终通知,无论增强方法执行是否有异常都会执行

2.切点表达式的抽取

同xml配置aop一样,我们可以将切点表达式抽取,抽取方法是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在增强注解中进行引用,具体如下:

//定义切点表达式
@Pointcut("execution(* com.zy.anno.*.*(..))")
public void pointcut(){

}
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
     System.out.println("环绕前增强.......");
     //切点方法(目标方法)
     Object proceed = pjp.proceed();
     System.out.println("环绕后增强.......");
     return proceed;
}
@After("MyAspect.pointcut()")
public void after(){
     System.out.println("最终增强.......");
 }

3.3知识要点

  • 注解aop开发步骤

    • 使用@Aspect标注切面类
    • 使用@通知注解标注通知方法
    • 在配置文件中配置aop自动代理aop:aspectj-autoproxy/
  • 通知注解类型

    • 前置通知 @Before 用于配置前置通知,指定增强的方法在切入点方法之前执行
      后置通知 @AfterReturning 用于配置后置通知,指定增强的方法在切入点方法之后执行
      环绕通知 @Around 用于配置环绕通知,指定增强的方法在切入点方法之前和之后都执行
      异常抛出通知 @AfterThrowing 用于配置异常抛出通知,指定增强的方法在出现异常时执行
      最终通知 @After 用于配置最终通知,无论增强方法执行是否有异常都会执行

Spring事务控制

◆编程式事务控制相关对象

1.1PlatformTransactionManager

PlatformTransactionManager接口是Spring事务管理器,它里面提供了我们常用的操作事务的方法

方法 说明
TransactionStauts getTransaction(TransactionDefination defination) 获取事务的状态信息
void commit(TransactionStauts status) 提交事务
void rollback(TransactionStatus status) 回滚事务

注意

PlatformTransactionManager是接口类型,不同的Dao层技术则有不同的实现类,例如Dao层技术是jdbc或mybatis时:org.springframework.jdbc.datasource.DataSourceTransactionManager。Dao层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager

1.2TransactionDefinition

TransactionDefinition是事务的定义信息对象,里面有如下方法:

方法 说明
int getIsolationLever() 获得事务的隔离级别
int getPropogationBehavior() 获取事务的传播行为
int getTimeout() 获得超时时间
boolean isReadOnly() 是否只读

1.事务隔离级别

设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读

ISOLATION_DEFAULT

ISOLATION_READ_UNCOMMITTED

ISOLATION_READ_COMMITTED

ISOLATION_REPEATABLE_READ

ISOLATION_SERIALIZABLE

2.事务的传播行为

  • REQUIRED:如果当前没有事务,就新建一个事务;如果已经存在一个事务中,加入到这个事务中,一般的选择(默认值)
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
  • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
  • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起
  • NOT_SUPPORTED:以非事务方法执行操作,如果当前存在事务,就把当前事务挂起
  • NEVER:以非事务方式运行,如果当前存在事务,抛出异常
  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作
  • 超时时间:默认值是-1,没有超时限制,如果有,以秒为单位进行设置
  • 是否只读:建议查询时设置为只读

1.3TransactionStatus

TransactionStatus接口提供的是事务具体的运行状态

方法 说明
boolean hasSavepoint() 是否存储回滚点
boolean isCompleted() 事务是否完成
boolean isNewTransaction() 是否是新事务
boolean isRollbackOnly() 事务是否回滚

1.4知识要点

编程式事务控制三大对象

  • PlaformTransactionManager
  • TransacationDefinition
  • TransactionSatus

◆基于XML的声明式事务控制

2.1什么是声明式事务控制

Spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里说的声明就是指在配置文件中声明,用在Spring配置文件中声明式的处理事务来代替代码式的处理事务。

声明式事务处理的作用

  • 事务管理不侵入开发的组件,具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要定义文件中重新配置即可。
  • 在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便。

注意:Spring声明式事务控制底层就是AOP

2.2声明式事务控制的实现

声明式事务控制明确事项:

  • 谁是切点? service层就是目标对象,也就是切点

  • 谁是通知?

  • <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    
  • 配置切面?

  • <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.zy.service.impl.*.*(..))"/>
    </aop:config>
    

2.3切点方法的事务参数的配置

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

其中,<tx:method>代表切点方法的事务参数的配置:例如

<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>

  • name:切点方法名称
  • isolation:事务的隔离级别
  • propogation:事务的传播行为
  • timeout:超时时间
  • read-only:是否只读

2.4知识要点

声明式事务控制的配置要点

  • 平台事务管理器配置
  • 事务通知的配置
  • 事务AOP织入的配置

◆基于注解的声明式事务控制

在需要使用事务的方法上加上@Transactional注解

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;
    @Autowired
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    public void transfer(String outMan, String inMan, double money) {
        accountDao.out(outMan,money);
        accountDao.in(inMan,money);
    }
}

然后在配置文件中配置组件扫描和注解驱动,需要导入context和tx

xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
<context:component-scan base-package="com.zy"/>

<tx:annotation-driven transaction-manager="transactionManager"/>

3.1注解配置声明式事务控制解析

①使用@Transactional在需要进行事务控制的类或是方法上修饰,注解可用的属性同xml配置方式,例如隔离级别、传播行为等

②注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置

③使用在方法上,不同的方法可以采用不同的事务参数配置

④Xml配置文件中要开启事务的注解驱动

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
</bean>
<!--事务的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>

3.2知识要点

注解声明式事务控制的配置要点

  • 平台事务管理器配置(xml方式)
  • 事务通知的配置(@Transactional注解配置)
  • 事务注解驱动的配置<tx:annotation-driven/>