Spring = Spring基础讲解Aop

一.基础案例:

起因:使用spring框架整合DbUtils技术,实现用户转账功能

Account:

public class Account {

    private Integer id;
    private  String name;
    private Double money;
    public Account() {
    }
    public Account(Integer id, String name, Double money) {
        this.id = id;
        this.name = name;
        this.money = money;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Double getMoney() {
        return money;
    }
    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

编写AccountDao

import com.wsl.dao.AccountDao;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.sql.SQLException;

@Repository
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private QueryRunner queryRunner;
    @Override
    public void outUser(String outUser, Double money) {

        String sql = "update account set money= money-? where name=?";
        try {
            queryRunner.update(sql,money,outUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void inUser(String inUser, Double money) {
        String sql = "update account set money= money+? where name=?";
        try {
            queryRunner.update(sql,money,inUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

编写AccountService

import com.wsl.dao.AccountDao;
import com.wsl.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;
    @Override
    public void transfer(String outUser, String inUser, Double money) {
        accountDao.outUser(outUser,money);
        accountDao.inUser(inUser,money);
    }
}

 

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.wsl"></context:component-scan>

   <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>
</beans>

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db
jdbc.username=root
jdbc.password=root

测试案例:

import com.wsl.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AServiceTest {

    @Autowired
    AccountService accountService;
    @Test
    public  void test01(){
        accountService.transfer("tom","jerry",200.0);
    }
}

上述代码会出现一个问题:如下:

(1)事务控制:

解决方案:将service方法的多个dao层代码,看做一个事务,要么成功,要么都失败

版本一:

修改AccountService:

// 依赖dataSource
@Autowired
private DataSource dataSource;

// 版本一
@Override
public void transfer(String outUser, String inUser, Double money) {
    Connection connection = null;

    try {
        // 获取conn手动控制事务
        connection = dataSource.getConnection();
        connection.setAutoCommit(false);
        // 业务代码-------- start
        // 转出
        accountDao.outUser(connection,outUser, money);

        // 模拟故障
        int i = 1 / 0;

        // 转入
        accountDao.inUser(connection,inUser, money);
        // 业务代码-------- end

        // 提交事务
        connection.commit();
    } catch (Exception e) {
        e.printStackTrace();
        try {
            // 回滚事务
            connection.rollback();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
    }finally {
        // 释放资源
        try {
            connection.setAutoCommit(true);// 再改为自动提交
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

修改AccountDao

// 方法重载
@Override
public void outUser(Connection connection, String outUser, Double money) {
    try {
        // 1.编写sql
        String sql = "update account set money = money - ?  where name = ?";
        // 2.执行sql(使用service连接对象)
        queryRunner.update(connection,sql,money,outUser);
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
// 方法重载
@Override
public void inUser(Connection connection, String inUser, Double money) {
    try {
        // 1.编写sql
        String sql = "update account set money = money + ?  where name = ?";
        // 2.执行sql(使用service连接对象)
        queryRunner.update(connection,sql,money,inUser);
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

测试:

 问题二:

我们不应该将service的conn对象传递到dao层,这种方式,就产生了dao层与service的耦合性问题

(2)ThreadLocal:解决方案:

解决方案

ThreadLocal是一个线程的局部变量

编写ConnectionUtils

/*
    ThreadLocal操作的工具类
        1. 从当前线程内绑定并获取conn对象
        2. 移除当前线程的conn对象
 */
@Component // 交给ioc容器
public class ConnectionUtils {


    @Autowired
    private DataSource dataSource;

    // 储物柜(线程隔离)
    private static final ThreadLocal<Connection> tl = new ThreadLocal<>();


    //  1.从当前线程内绑定并获取conn对象
    public Connection getThreadConnection(){
        // 第一次执行get,肯定获取不到的...
        Connection connection = tl.get();
        if (connection == null){
            try {
                connection = dataSource.getConnection();
                tl.set(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return connection;
    }

    // 2.移除当前线程的conn对象
    public void removeThreadConnection(){
        tl.remove();
    }

}

修改AccountService

@Autowired
private ConnectionUtils connectionUtils;

// 版本二
@Override
public void transfer(String outUser, String inUser, Double money) {
    Connection connection = null;

    try {
        // 获取conn手动控制事务
        connection = connectionUtils.getThreadConnection();
        connection.setAutoCommit(false);
        // 业务代码-------- start
        // 转出
        accountDao.outUser(outUser, money);

        // 模拟故障
        int i = 1 / 0;

        // 转入
        accountDao.inUser(inUser, money);
        // 业务代码-------- end

        // 提交事务
        connection.commit();
    } catch (Exception e) {
        e.printStackTrace();
        try {
            // 回滚事务
            connection.rollback();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
    }finally {
        // 释放资源
        try {
            connection.setAutoCommit(true);// 再改为自动提交
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

修改AccountDao

@Autowired
private ConnectionUtils connectionUtils;

@Override
public void outUser(String outUser, Double money) {
    try {
        // 1.编写sql
        String sql = "update account set money = money - ?  where name = ?";
        // 获取当前线程内的 conn
        Connection threadConnection = connectionUtils.getThreadConnection();
        // 2.执行sql
        queryRunner.update(threadConnection,sql,money,outUser);
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

@Override
public void inUser(String inUser, Double money) {
    try {
        // 1.编写sql
        String sql = "update account set money = money + ?  where name = ?";
        // 获取当前线程内的 conn
        Connection threadConnection = connectionUtils.getThreadConnection();
        // 2.执行sql
        queryRunner.update(threadConnection,sql,money,inUser);
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

测试:

在企业开发时,我们基础每一个业务层方法都需要进行事务的控制,这部分代码属于公共业务且重复的,出现了大量的代码冗余

(3)事务管理器

解决方案

这时候我们可以把这部分代码抽取到工具类(事务管理器)

编写TransactionManager

/*
    自定义事务管理器工具类
        1.开启事务
        2.提交事务
        3.回滚事务
        4.释放资源
 */
@Component
public class TransactionManager {


    @Autowired
    private ConnectionUtils connectionUtils;

    // 1.开启事务
    public void beginTransaction(){

        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

    //  2.提交事务
    public void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 3.回滚事务
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 4.释放资源
    public void release(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(true);// 恢复 自动提交
            connectionUtils.getThreadConnection().close();// 归还到连接池
            connectionUtils.removeThreadConnection();// 从当前线程删除conn对象
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

修改AccountService

//版本三
@Override
public void transfer(String outUser, String inUser, Double money) {
    try {
        // 开启事务
        transactionManager.beginTransaction();
        // 业务代码-------- start
        // 转出
        accountDao.outUser(outUser, money);

        // 模拟故障
        int i = 1 / 0;

        // 转入
        accountDao.inUser(inUser, money);
        // 业务代码-------- end

        // 提交事务
        transactionManager.commit();
    } catch (Exception e) {
        e.printStackTrace();
        // 回滚事务
        transactionManager.rollback();
    } finally {
        // 释放资源
        transactionManager.release();;
    }

}

测试:

问题:

二.转账案例进阶:(AOP)

我们可以将业务代码和事务代码进行拆分,通过动态代理的方式,对业务方法进行事务的增强。这样就不会对业务层产生影响,解决了耦合性的问题啦!

修改AccountService代码

企业开发时业务层,只有核心业务代码,不会出现事务相关代码

// 版本四(开发者只需要编写核心业务....)
public void transfer(String outUser, String inUser, Double money) {
    // 业务代码-------- start
    // 转出
    accountDao.outUser(outUser, money);

    // 模拟故障
    int i = 1/0;

    // 转入
    accountDao.inUser(inUser, money);
    // 业务代码-------- end
}

(1)JDK动态代理

目标对象:

编写JdkProxyFactory

/*
    基于jdk,实现对目标对象,进行事务的增强
 */
@Component
public class JdkProxyFactory {

    @Autowired
    private TransactionManager transactionManager;


    public Object createJdkProxyTx(Object target) {
        Object proxy = null;

        // 使用sun公司提供的jdk代理工具类
        /*
            1.目标对象类加器
            2.目标对象接口数组
            3.实现增强的业务逻辑(匿名内部类、lambda)
         */
        ClassLoader classLoader = target.getClass().getClassLoader();// 目标对象类加载器
        Class<?>[] interfaces = target.getClass().getInterfaces(); // 目标对象实现的接口数组
        proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {

            /*
                invoke 方法是代理对象的入口
                    proxy:jdk工具类生产的代理对象
                    method:当前用户执行的某个具体方法
                    args:当前用户执行的某个具体方法传递的实际参数列表
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;

                try {
                    // 开启事务
                    transactionManager.beginTransaction();

                    // 执行目标对象原有的功能
                    result = method.invoke(target, args);

                    // 提交事务
                    transactionManager.commit();
                } catch (Exception e) {
                    e.printStackTrace();

                    // 回滚事务
                    transactionManager.rollback();
                }finally {
                    // 释放资源
                    transactionManager.release();
                }

                return result;
            }
        });

        // 返回代理对象(增强后的.....)
        return proxy;
    }
}

测试:

@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountTest {

    @Autowired
    private AccountService accountService; // 目标对象

    @Autowired
    private JdkProxyFactory jdkProxyFactory;// jdk生产代理对象工厂

    // 测试转账案例
    @Test
    public void test02() throws Exception {
        // 目标对象没有事务增强
        // accountService.transfer("tom", "jerry", 100d);
        // 使用jdk对目标对象事务增强
        AccountService jdkProxy = (AccountService) jdkProxyFactory.createJdkProxyTx(accountService);
        jdkProxy.transfer("tom", "jerry", 100d);
    }
    
}

(2)CGLib动态代理

但是有些代码没有接口,但又需要动态代理进行增强,这时候就需要用CGLib了

目标对象

编写CglibProxyFactory

/*
    基于cglib,实现对目标对象,进行事务的增强
 */
@Component
public class CglibProxyFactory {

    @Autowired
    private TransactionManager transactionManager;


    public Object createCglibProxyTx(Object target) {
        Object proxy = null;

        // 使用cglib提供的工具类
        /*
            1. 目标对象class类
            2. 实现增强的业务逻辑(匿名内部类、lambda)
         */
        proxy = Enhancer.create(target.getClass(), new MethodInterceptor() {
            /*
                intercept 代理对象方法入口
                    1.o cglib生产出来的代理对象
                    2.method 执行代理对象(子),被拦截的方法
                    3.objects 执行代理对象,被拦截的方法的参数列表
                    4.methodProxy 目标对象(父),被拦截的方法,功能与method一样的
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object result = null;

                try {
                    // 开启事务
                    transactionManager.beginTransaction();
                    // 调用目标对象原有的共
                    result = method.invoke(target, objects);
                    // 提交事务
                    transactionManager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    // 回滚事务
                    transactionManager.rollback();
                } finally {
                    // 释放资源
                    transactionManager.release();
                }

                return result;
            }
        });

        return proxy;

    }
}

测试:

@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountTest {

    @Autowired
    private AccountService accountService; // 目标对象

    @Autowired
    private JdkProxyFactory jdkProxyFactory;// jdk生产代理对象工厂

    @Autowired
    private CglibProxyFactory cglibProxyFactory;// cglib生产代理对象工厂

    // 测试转账案例
    @Test
    public void test02() throws Exception {
        // 目标对象没有事务增强
        // accountService.transfer("tom", "jerry", 100d);
        // 使用jdk对目标对象事务增强
        // AccountService jdkProxy = (AccountService) jdkProxyFactory.createJdkProxyTx(accountService);
        // jdkProxy.transfer("tom", "jerry", 100d);
        // 使用cglib对目标对象事务增强
        AccountService cglibProxy = (AccountService) cglibProxyFactory.createCglibProxyTx(accountService);
        cglibProxy.transfer("tom", "jerry", 100d);
    }
    
}

总结:

 jdk和cglib两种代理方式的选择?

优先使用jdk,性能高于cglib

1.如果目标对象有接口,一定使用jdk创建代理对象

2.如果目标对象没有接口,没办法只能使用cglib创建代理对象

当核心业务(转账)和通用业务(事务、日志.....)同时出现?

三.SpringAOP简介:

(1)概述

AOP( 面向切面编程 )是一种思想, 它的目的就是在不修改源代码的基础上,对原有功能进行增强。

Spring AOP是对AOP思想的一种实现, Spring底层同时支持jdk和cglib动态代理。

这样做的好处是

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

  2. 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码

  3. 减少重复代码,提高开发效率,便于后期维护

Spring会根据被代理的类是否有接口自动选择代理方式:

  • 如果有接口,就采用jdk动态代理( 当然,也可以强制使用cglib )

  • 没有接口就采用cglib的方式

(2)相关术语

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

* Target:目标对象
	service层的核心业务(AccountServiceImpl)
	
* JoinPoint:连接点
	目标对象中的所有方法
		transfer()
		findAll()

* Pointcut:切点
	目标对象需要增强的方法
		transfer()

* Advice:通知(增强)
	实现增强的功能的(TransactionManager)
		beginTransaction()
		commit()
		rollback()
		release()

* Weaving:织入
	将通知和切点进行织入(动作)

* Aspect:切面(spring术语)
	通知 + 切点 = 切面

* Proxy:代理对象(底层实现)
	通知 + 切点 = 代理对象

 

(4)基于xml的AOP开发:

创建maven工程

依赖管理:

    <!--依赖管理-->
    <dependencies>
        <!--spring的核心-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <!--aspectj-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--spring整合junit-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
    </dependencies>

编写目标类AccountService

public interface AccountService {
    
    void transfer();
    
    void save();
    
}

public class AccountServiceImpl implements AccountService {

    @Override
    public void transfer() {
        System.out.println("转账...");
    }

    @Override
    public void save() {
        System.out.println("保存...");
    }
}

编写通知类 增强的

/*
    通知类(增强)

 */
public class MyAdvice {

    // 在目标对象方法,执行之前增强....
    public void before() {
        System.out.println("前置通知....");
    }
}

配置spring的aop(切点+通知)

如果spring要使用aop,我们需要再引入schema约束+命名空间

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:p="http://www.springframework.org/schema/p"
       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
	    http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--目标对象,交给ioc-->
    <bean id="accountService" class="cn.wsl.service.impl.AccountServiceImpl"></bean>

    <!--通知对象,交给ioc-->
    <bean id="myAdvice" class="cn.wsl.advice.MyAdvice"></bean>


    <!--aop配置-->
    <aop:config>
        <!--切面 -->
        <aop:aspect ref="myAdvice">
            <!--织入(通知+方法)
                <aop:before> 前置通知,特点在目标方法执行之前,进行增强
                   具体执行的增强方法 method="通知的具体方法"
                   切点:pointcut="切点表达式"

            -->
            <aop:before method="before" pointcut="execution(public void cn.wsl.service.impl.AccountServiceImpl.transfer())" ></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

测试:

@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountTest {

    @Autowired
    private AccountService accountService; // 配置了aop之后,这就是代理对象了....

    @Test
    public void test01()throws Exception{
        // accountService.save();
        accountService.transfer();
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值