Spring——AOP前言(写一个小demo为了好学习AOP)

1.AOP的概念

1.1 AOP简单样例

我们来先通过一个例子来对AOP进行理解,这个例子就是有关Spring的事务的一个样例,有关Spring是怎么实现事务的,这个事务其实本质上就是对于我们代码的一个增强。废话不多说,上程序,请各位同学自行感悟。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <!--有单元测试的环境,Spring5版本,Junit4.12版本-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    <scope>test</scope>
    </dependency>
    <!--连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <!--mysql驱动包-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>
    <!-- Spring整合Junit测试的jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.2.RELEASE</version>
        <scope>test</scope>
    </dependency>
</dependencies>

然后我们去看一眼我们的数据库,有没有一个Spring_db的表,(如果没有请参照上一篇博客第四小节应该有,创建数据库的sql语句)

然后我们创建一个model的包,包中有创建一个Account的实体类

/**
 * JavaBean 实体类
 * 封装数据
 */
public class Account {

    private int id;

    private String name;

    private double money;

    public int getId() {
        return id;
    }

    public void setId(int 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;
    }

    public Account() {
    }

    public Account(int id, String name, double money) {
        this.id = id;
        this.name = name;
        this.money = money;
    }

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

然后创建Dao的包,写一个AccountDao的接口

/**
 * 持久层接口
 * 实现保存转账金额的方法
 */
public interface AccountDao {

    public void saveAll (Account account);
}

再写一个AccountDaoImpl的实现类

/**
 * 持久层的实现类
 */
public class AccountDaoImpl implements AccountDao {
    @Override
    public void saveAll(Account account) {
        //jdbc的程序
        System.out.println("持久层保存成功");
    }
}

创建service包,再service包下写AccountService的接口

/**
 * 业务层的接口
 */
public interface AccountService {

    public void saveAll(Account account, Account account1);
}

然后写一个AccountServiceImpl的实现类

public class AccountServiceImpl implements AccountService{

    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public void saveAll(Account account, Account account1) {
        System.out.println("业务层保存方法执行");
        accountDao.saveAll(account);
        accountDao.saveAll(account1);
    }
}

写一个applicationcontext.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">

    <bean id="accountService" class="com.qcby.service.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

    <bean id="accountDao" class="com.qcby.dao.AccountDaoImpl"/>
</beans>

然后在test下创建com.qcby的包结构,写一个Test1的测试类

public class Test1 {

    @Test
    public void run1(){
        ApplicationContext ac=new ClassPathXmlApplicationContext("applicationcontext.xml");
        AccountService as=(AccountService) ac.getBean("accountService");
        Account account=new Account(1,"熊大",10000.00);
        Account account1=new Account(2, "熊二",9999.99);
        as.saveAll(account,account1);
    }
}

然后我们去写持久层,在写持久层的时候,我们首先要有一个工具类

import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
public class TxUtils {
    //静态的类属性 连接池对象
    public static DruidDataSource ds=null;
    //使用ThreadLocal存储当前线程中的connection对象
    private static ThreadLocal<Connection> threadLocal=new ThreadLocal<>();

    //在静态代码中创建数据库连接池
    static {
        try {
            //通过代码创建druid数据库连接池
            ds=new DruidDataSource();
            ds.setDriverClassName("com.mysql.jdbc.Driver");
            ds.setUrl("jdbc:mysql///spring_db");
            ds.setUsername("root");
            ds.setPassword("2020");

        }catch (Exception e){
            throw new ExceptionInInitializerError(e);
        }
    }

    public static Connection getConnection() throws Exception{
        Connection conn=threadLocal.get();
        if (conn==null){
            //从数据源获取数据连接conn
            conn= getDataSource().getConnection();
            //把conn对象存储到当前线程
            threadLocal.set(conn);
        }
        return conn;
    }

    /**
     * 开启事务
     */
    public static void startTransaction() {
        try{
            Connection conn=threadLocal.get();
            if (conn==null){
                conn=getConnection();
                threadLocal.set(conn);
            }
            //开启事务
            conn.setAutoCommit(false);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 回滚事务
     */
    public static void rollback(){
        try{
            Connection conn=threadLocal.get();
            if (conn!=null){
                conn.rollback();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 提交事务
     */
    public static void commit(){
        try{
            Connection conn=threadLocal.get();
            if (conn!=null){
                conn.commit();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 关闭数据库连接,这里的关闭指的是把数据库连接还给数据库连接池
     */
    public static void close(){
        try{
            Connection conn=threadLocal.get();
            if (conn!=null){
                conn.close();
                threadLocal.remove();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 获取数据
     */
    public static DruidDataSource getDataSource(){
        return ds;
    }
}

这个工具类有几个值得注意的点我们来看一下,第一个,就是我们在类中设置了一个静态的类的属性 ,可以用threadLocal存储这对象,达到只用一个连接的目的,还有就是我们把close的方法写到这个工具类里了,因为我们不能在持久层关闭连接,我们可以从业务层进行关闭。

然后我们再回到持久层,写连接

public class AccountDaoImpl implements AccountDao {
    @Override
    public void saveAll(Account account) throws Exception {
        //jdbc的连接数据库进行保存数据
        //获取连接加载驱动 保证事务

        System.out.println("持久层保存操作!!!");
        //把数据存到数据库之中
        //获取连接
        Connection conn = TxUtils.getConnection();
        //编写sql
        String sql="insert into account values(null,?,?)";
        //预编译SQL
        PreparedStatement statement= conn.prepareStatement(sql);
        //设置值
        statement.setString(1,account.getName());
        statement.setDouble(1,account.getMoney());
        //执行操作
        statement.executeUpdate();
        //关闭资源,(conn的关闭不能写在持久层)
        statement.close();
    }
}

这时候我们的业务实现层肯定会有一个异常,跑出去就行了。

然后运行一下test测试

那么我们接下来模拟这么一个场景,就是插入第一条数据后,在插入第二条数据第二条数据时模拟一个异常。

try {
    accountDao.saveAll(account);
    int i=10/0;
    accountDao.saveAll(account1);
} catch (SQLException e) {
    e.printStackTrace();
}

这时候就会有一个除零的异常

按照测试代码,我们应该加入小明和小红两条数据,但是加入小明之后我们就出现异常就终止执行了,就没法插入小红的这条数据了。

@Test
public void run(){
    ApplicationContext ac=new ClassPathXmlApplicationContext("ApplicationContext.xml");

    AccountService accountService =(AccountService) ac.getBean("accountService");
    Account account=new Account(null,"小明",1000.0);
    Account account1=new Account(null,"小红",1000.0);
    accountService.saveAll(account,account1);
}

然后我们去业务层中,加上事务的管理

public void saveAll(Account account, Account account1) {

    System.out.println("业务层方法执行了");
    //开启事务 conn是一个连接
    TxUtils.startTransaction();
    try {
        accountDao.saveAll(account);
        int i=10/0;
        accountDao.saveAll(account1);
        //提交事务
        TxUtils.commit();
    } catch (SQLException e) {

        e.printStackTrace();
        //回滚事务
        TxUtils.rollback();
    }

}

然后我们把那个除零的异常语句给注释掉,我们执行以下看一下效果

能加进来

然后我们去把那个异常的注释给解掉,然后再来运行看一下效果。

数据库中就没有添加进去小明。说明进行了事务的回滚。

然后如果我们要在业务写一个转账的方法,也需要些开启事务,提交事务,还有回滚事务,就比较麻烦了。

就是加事务这个代码,我们会发现与业务本身的逻辑没有关系,就是需要在业务逻辑执行前开启事务,执行完提交事务,要是出现问题就要回滚事务。这个事务就式对我们代码的一个增强。

我们如果要做到不在改源代码的情况下实现事务,我们实现的思路有哪些?

jdk动态代理

静态代理

父子类(父类做业务逻辑,子类做增强)

装饰者模式

AOP的底层就是动态代理

1.2 jdk动态代理类

上面说了,我们用jdk的动态代理的目的就是实现代码的增强。

我们首先就要有一个代理的类

代理类中有一个获取代理对象的方法,这个方法有三个参数,类的加载器,代理对象所实现的接口,回调函数,然后就在invoke方法中写我们对原代码的增强 method让目标对象执行方法(反射调用)。

public class JdkProxy {
    //获取代理对象的方法
    //增强目标对象的方法
    public static Object getProxy(AccountService accountService){
        /**
         * 使用jdk的动态代理生成代理对象
         * Proxy一个有关反射的一个类 三个参数 类的加载器 代理对象所实现的接口 回调函数 并把对象给返回
         * 通过Java反射的一个类,里面有一个生成代理对象的方法
         * 静态代理和动态代理的区别 一个是继承,一个是基于接口实现的让目标对象执行方法(反射调用),然后
         */

        Object proxy=Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
            //

            /**
             * 调用代理对象的方法,invoke方法就会执行
             * method就是对目标对象方法
             * @param proxy
             * @param method
             * @param args
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result=null;
                try{
                    //事务处理,开启事务
                    TxUtils.startTransaction();

                    //让目标对象执行方法(反射调用)
                    method.invoke(accountService,args);

                    //提交事务
                    TxUtils.commit();
                }catch (Exception e){
                    e.printStackTrace();
                    //回滚事务
                    TxUtils.rollback();
                }finally {
                    TxUtils.close();
                }

                return proxy;
            }
        });

        return null;
    }

然后在测试方法中这么使用就可以了

@Test
    public void run(){
        ApplicationContext ac=new ClassPathXmlApplicationContext("ApplicationContext.xml");

        AccountService accountService =(AccountService) ac.getBean("accountService");
        Account account=new Account(null,"小明",1000.0);
        Account account1=new Account(null,"小红",1000.0);
//        accountService.saveAll(account,account1);
        //增强的代理对象来执行
        Object proxyobj= JdkProxy.getProxy(accountService);
        //转型
        AccountService proxy=(AccountService) proxyobj;
        //调用逻辑
        proxy.saveAll(account,account1);
    }

然后我们要是想看到这个方法的相互的调用的过程的话就打个断点debug走一下就明白了

相关推荐
ProtonBase4 分钟前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构
乐之者v11 分钟前
leetCode43.字符串相乘
java·数据结构·算法
suweijie7683 小时前
SpringCloudAlibaba | Sentinel从基础到进阶
java·大数据·sentinel
公贵买其鹿4 小时前
List深拷贝后,数据还是被串改
java
云和数据.ChenGuang5 小时前
Django 应用安装脚本 – 如何将应用添加到 INSTALLED_APPS 设置中 原创
数据库·django·sqlite
woshilys6 小时前
sql server 查询对象的修改时间
运维·数据库·sqlserver
Hacker_LaoYi6 小时前
SQL注入的那些面试题总结
数据库·sql
建投数据7 小时前
建投数据与腾讯云数据库TDSQL完成产品兼容性互认证
数据库·腾讯云
xlsw_7 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
Hacker_LaoYi8 小时前
【渗透技术总结】SQL手工注入总结
数据库·sql