spring中AOP以及事务

1.什么是AOP

AOP(Aspect Orient Programming)面向切面编程,在传统OOP中,纵向抽成一个个对象,在AOP中可以将一个个对象的某些类似的方法横向抽成一个切面,降低代码的重复性,方便管理。

切面(Aspect):切面为那些横跨多个对象的功能从主业务逻辑中分离出来,形成切面。

切点(Pointcut):主要定义在哪些地方应用,可以通过路径表达式或者注解匹配到目标方法或类

处理(Advice):定义了处理内容以及处理时机比如:前置通知Before(在目标方法执行之前调用)、后置通知After(在目标方法执行之后调用,不考录是否发生异常)、返回通知AfterReturning(在目标方法成功执行后调用)和异常通知AfterThrowing(在目标方法抛出异常后调用)

2.为什么使用AOP

(1)将共同代码抽成切面,集中管理,避免代码重复

(2)业务逻辑和非业务逻辑(如事务、权限、日志)得以分离,提高代码维护

(3)可以在不改变源代码的情况下,添加、修改和删除功能

案例:在加减乘除方法中添加记录日志操作

public class MathServiceImpl implements MathService {
    /**
     * 加法
     *
     * @param a
     * @param b
     * @return
     */
    @Override
    public double add(double a, double b) {
        double result = a + b;
        System.out.println("测试记录日志");
        return result;
    }

    /**
     * 减法
     *
     * @param a
     * @param b
     * @return
     */
    @Override
    public double subtract(double a, double b) {
        double result = a - b;
        System.out.println("测试记录日志");
        return result;
    }

    /**
     * 乘法
     *
     * @param a
     * @param b
     * @return
     */
    @Override
    public double multiply(double a, double b) {
        double result = a * b;
        System.out.println("测试记录日志");
        return result;
    }

    /**
     * 除法
     *
     * @param a
     * @param b
     * @return
     */
    @Override
    public double div(double a, double b) {
        double result = a / b;
        System.out.println("测试记录日志");
        return result;
    }
}

发现需要每个都需要添加相同的代码,如果日后进行修改的话比较麻烦,不利于维护。下面我们使用AOP将这个记录日志抽取成切面,切点为这个类的所有方法,处理为后置返回通知。

3.如何使用AOP

3.1引入相关依赖
<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
3.2配置xml:
<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.gjc.demo01"/>
    <!--开启AOP切面注解驱动-->
    <aop:aspectj-autoproxy/>

</beans>
3.3创建切面类

切面

@Aspect 标识为注解类

切点

@Pointcut 表示为切点  可以设置路径或者注解

通知

@Before前置通知:在目标方法执行之前

@After后置通知:在目标方法执行之后调用,不考录是否发生异常 ,相当与异常中的finally

@AfterReturning后置返回通知:在目标方法执行成功后调用

@AfterThrowing异常通知:在目标方法抛出异常后调用

@Around环绕通知:结合以上通知

完成以上案例:

将记录日志抽成一个切面

@Aspect  //标志为切面类
@Component  //标志为交给spring容器管理
public class logAspect {
    //定义切点  访问修饰符为public 返回类型double 在com.gjc.demo01.service.impl.MathServiceImpl下的所有方法
    @Pointcut(value = "execution(public double com.gjc.demo01.service.impl.MathServiceImpl.*(..))")
    public void point(){

    };
    //定义通知
    @AfterReturning(value = "point()")//后置返回通知 方法执行成功后通知
    public void a(){
        System.out.println("测试记录日志");
    }
}
public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
        MathService mathService = (MathService) app.getBean("mathServiceImpl");
        mathService.multiply(10, 20);//乘法
        mathService.add(10,20);//加法
    }
}

运行结果:每个方法后都执行了记录日志

乘法200.0
测试记录日志
加法30.0
测试记录日志

3.4可以使用通配符来配置路径

//第一个*表示可以为任意修饰符和任意返回类型
    //第二个*表示可以为任意类
    //第三个*表示可以为任意方法
    //..表示为任意参数
    @Pointcut(value = "execution(* com.gjc.demo01.service.impl.*.*(..))")
    public void point(){
    };

3.5也可以使用注解模式来定义切点

创建注解:

@Target(ElementType.METHOD) //只能用在方法上
@Retention(RetentionPolicy.RUNTIME) //运行时生效
public @interface MyAnnotation {
}

定义切点:

@Pointcut(value = "@annotation(com.gjc.demo01.annotation.MyAnnotation)") //定义为切点
    public void point(){
    }
    //定义通知
    @AfterReturning(value = "point()")//后置返回通知 方法执行成功后通知
    public void a(){
        System.out.println("测试记录日志");
    }

添加注解:

package com.gjc.demo01.service.impl;

import com.gjc.demo01.annotation.MyAnnotation;
import com.gjc.demo01.service.MathService;
import org.springframework.stereotype.Service;

@Service
public class MathServiceImpl implements MathService {
    /**
     * 加法
     *
     * @param a
     * @param b
     * @return
     */
    @MyAnnotation
    @Override
    public double add(double a, double b) {
        double result = a + b;
        System.out.println("加法"+result);
        return result;
    }

    /**
     * 减法
     *
     * @param a
     * @param b
     * @return
     */
    @MyAnnotation
    @Override
    public double subtract(double a, double b) {
        double result = a - b;
        System.out.println("减法"+result);
        return result;
    }

    /**
     * 乘法
     *
     * @param a
     * @param b
     * @return
     */
    @Override
    public double multiply(double a, double b) {
        double result = a * b;
        System.out.println("乘法"+result);
        return result;
    }

    /**
     * 除法
     *
     * @param a
     * @param b
     * @return
     */
    @Override
    public double div(double a, double b) {
        double result = a / b;
        System.out.println("除法"+result);
        return result;
    }
}

测试:只有加法和减法用了注解的才是切入点

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
        MathService mathService = (MathService) app.getBean("mathServiceImpl");
        mathService.add(10,20);//加法
        mathService.subtract(10,20);//减法
        mathService.multiply(10, 20);//乘法

    }
}
加法30.0
测试记录日志
减法-10.0
测试记录日志
乘法200.0
3.4通知类型
@Before(value = "point()") //前置通知  在方法执行之前通知
    public void b(){
        System.out.println("----方法执行之前----");
    }
    @After(value = "point()") //后置通知  在方法执行之后通知 不考录是否发生异常
    public void c(){
        System.out.println("----方法执行之后----");
    }
    @AfterReturning(value = "point()") //后置返回通知 方法执行成功后通知
    public void a(){
        System.out.println("测试记录日志");
    }
    @AfterThrowing(value ="point()")//异常通知 方法抛出异常通知
    public void d(){
        System.out.println("----异常执行----");
    }

测试:

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
        MathService mathService = (MathService) app.getBean("mathServiceImpl");
        mathService.add(10,20);//加法
    }
}
----方法执行之前----
加法30.0
----方法执行之后----
测试记录日志

模拟异常测试:

----方法执行之前----
----方法执行之后----
----异常执行----

环绕通知(重点)

@Around(value = "point()")
    public Object e(ProceedingJoinPoint joinPoint){ //joinPoint连接点 理解为被执行的方法对象
        System.out.println("前置通知");
        try {
            Object proceed = joinPoint.proceed();//执行链接点
            System.out.println("后置返回通知");
            return proceed;
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("异常通知");
        }finally {
            System.out.println("后置通知");
        }
        return null;
    }
public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
        MathService mathService = (MathService) app.getBean("mathServiceImpl");
        mathService.add(10,20);//加法
    }
}
前置通知
加法30.0
后置返回通知
后置通知

4事务

数据库操作的一种机制,要么全部成功要么全部不成功(回滚)

有四个特性:原子性Atomicity、一致性Consistency、隔离性Isolation、持久性Durability称为ACID

spring如何实现事务:

1.前置通知开启手动事务

2.后置返回通知对事物进行提交

3.异常通知事务回滚

使用spring提供的事务

1.添加依赖

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.12.RELEASE</version>
        </dependency>
        <!--spring事务依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.2.12.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.15.RELEASE</version>
        </dependency>

        <!--mybatis的依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>
        <!--mybatis和spring整合的依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.7</version>
        </dependency>
        <!--druid的连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
        </dependency>
    </dependencies>

2.配置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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
       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 http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--springmvc的配置-->
    <!--包扫描 扫描com.gjc以及该包下的子包-->
    <context:component-scan base-package="com.gjc"/>



    <!--spring整合mybatis的配置-->

    <!--数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <!--mysql驱动为8.0以后必须使用时区-->
        <property name="url" value="jdbc:mysql://localhost:3306/qy168?serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!--spring封装了一个类SqlSessionFactoryBean类,可以把mybatis中的配置-->
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:/mapper/*.xml"/>
    </bean>

    <!--为指定dao包下的接口生产代理实现类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
        <!--它会为com.gjc.dao包下的所有接口生产代理实现类-->
        <property name="basePackage" value="com.gjc.dao"/>
    </bean>

    <!---================以下内容是关于事务的配置===================-->

    <!--事务切面管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--开启事务管理注解的驱动-->
    <tx:annotation-driven/>
</beans>

service:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public void zhuanzhang(int id, int uid, double money) {
        userDao.update(id,-money);
        int i=10/0; //模拟异常
        userDao.update(uid,money);
    }


}

dao:

@Mapper
public interface UserDao {
    /**
     * 根据id修改余额
     * @param id
     * @param money
     */
    public void update(@Param("id") int id,@Param("money") double money);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.gjc.dao.UserDao">

    <update id="update">
        update user set money=money+#{money} where id=#{id}
    </update>
</mapper>

测试:id7给id8的用户转了200,id7钱减少了,但id8没增加

public static void main(String[] args) {
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = (UserService) app.getBean("userServiceImpl");
        userService.zhuanzhang(7,8,200);
    }

添加事务注解@Transactional

    @Override
    @Transactional //事务注解
    public void zhuanzhang(int id, int uid, double money) {
        userDao.update(id,-money);
        int i=10/0; //模拟异常
        userDao.update(uid,money);
    }

 转账中出现异常进行回滚,有一个失败都失败

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值