65-Spring实战以及AOP介绍

Spring实战以及AOP介绍

转账案例:
需求:
使用spring框架整合DBUtils技术,实现用户转账功能
基础功能:
步骤分析:
/*
1. 创建java项目,导入坐标
2. 编写Account实体类
3. 编写AccountDao接口和实现类
4. 编写AccountService接口和实现类
5. 编写spring核心配置文件
6. 编写测试代码
*/
创建java项目,导入坐标:
<dependencies>
    <dependency>
        
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>5.1.47version>
    dependency>
    <dependency>
        
        <groupId>com.alibabagroupId>
        <artifactId>druidartifactId>
        <version>1.1.15version>
    dependency>
    <dependency>
        
        <groupId>commons-dbutilsgroupId>
        <artifactId>commons-dbutilsartifactId>
        <version>1.6version>
    dependency>
    <dependency>
        
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.1.5.RELEASEversion>
    dependency>
    <dependency>
        
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-testartifactId>
        <version>5.1.5.RELEASEversion>
    dependency>
    <dependency>
        
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.12version>
    dependency>
dependencies>

编写Account实体类:
package com.lagou.domain;

/**
 *
 */
public class Account {
    private Integer id;
    private String name;
    private Double money;

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", 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;
    }
}

编写AccountDao接口和实现类 :
package com.lagou.dao;

/**
 *
 */
public interface AccountDao {

   //转出操作

    public void out(String outUser,double money);

   //转入操作
    public void in(String inUser,double money);

}

package com.lagou.dao.impl;

import com.lagou.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("accountDao")  //生成该类实例到IOC容器中
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner queryRunner;

    @Override
    public void out(String outUser, double money) {

        String sql = "update account set money = money - ? where name = ?";
        try {
            queryRunner.update(sql, money, outUser);
            System.out.println(queryRunner);
            //输出对象,会提示一个小错误,但是不影响程序的运行
            //可以在url后面加上&useSSL=false来解决
            //是因为输出这个,触发了安全机制,需要加上&useSSL=false,一般默认为true
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void in(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接口和实现类:
package com.lagou.service;

/**
 *
 */
public interface AccountService {

    //转账方法
    public void transfer(String out, String in, Double money);


}

package com.lagou.service.impl;

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

/**
 *
 */
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    //转账方法
    //可以知道,service层不只是在dao层的方法的基础上进行单独接口操作(调用)和对应判断(接口外的代码)
    //也可以进行多个接口操作和对应判断
    //实际上本来就可以,只是我们通常这样做
    //即dao层只是提供接口,而service层是对接口的操作和对应判断
    @Override
    public void transfer(String outUser, String inUser, Double money) {

        //调用了减钱方法
        accountDao.out(outUser, money);
        //调用了加钱方法
        accountDao.in(inUser, money);


    }
}

编写spring核心配置文件:

<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:component-scan base-package="com.lagou">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.driverClassName}">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.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_db?characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
编写测试代码:
package com.lagou.test;

import com.lagou.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.SpringJUnit4ClassRunner;

/**
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test //测试方法,基本只能写void返回值,否则报错
    public void textTransfer() {
        accountService.transfer("tom", "jerry", 100d);
        //100d就是这个100是double类型,就如3.14f就是float类型
    }
}

问题分析:
上面的代码事务在dao层,转出转入操作都是一个独立的事务
因为dao层的方法,基本用来编写单个操作的,如转出操作和转入操作
使得service层也只能依次调用,而dao层之所以不合并方法
是为了需要合并中的单个方法时,可以使用(如只需要转入或者转出操作,但这样的业务通常都是一起,虽然也可以多建立方法)
但是方法越少越好(因为实现一次,就要重写一次,为了好维护)
所以实际开发,当有些业务逻辑需要合起来调用时,应该把业务逻辑放在service层,即控制在一个事务中,使得不能分开执行
代码演示:
  @Override
    public void transfer(String outUser, String inUser, Double money) {

        //调用了减钱方法
        accountDao.out(outUser, money);
        
        //int i = 1/0; 
        //若这里报错,后面加钱方法就不会运行
        //由于这两个是分开的,那么在数据库里,可以看到,只有减钱,没有加钱
        //在实际开放中是非常严重的
        
        //调用了加钱方法
        accountDao.in(inUser, money);


    }
从上面的问题出发,看如下解决方式
传统事务:
步骤分析:
/*
1. 编写线程绑定工具类   ThreadLocal
一般的连接若没有关闭,那么基本就是同一个连接,而不是新的连接(单线程情况下,是好的)
那么对于连接中,连接池和ThreadLocal的区别,为什么使用ThreadLocal,后面会有所解释
2. 编写事务管理器
3. 修改service层代码
4. 修改dao层代码
*/
编写线程绑定工具类:
package com.lagou.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 *连接工具类:从数据源中获取一个连接,并且将获取到的连接与线程进行绑定
 * ThreadLocal:线程内部的存储类,可以在指定的线程内来存储数据
 * 使用类似于map集合的操作进行存储,key为线程,就是当前线程,也就是Thread.currentThread()
 * value为连接(这里是连接)
 */
@Component
public class ConnectionUtils {

    @Autowired
    private DataSource dataSource;

    private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

    /*
    获取当前线程上绑定的连接:如果获取到的连接为空
    那么就要从数据源中获取连接,并且放到ThreadLocal中(绑定到当前线程)
    key不变,也就是当前线程Thread.currentThread()
    所以得到时,就是get()方法,没有指定key,因为底层帮你指定了
    设置时,就是set(对应连接),这里是连接,因为泛型,也不用指定key,底层也指定了
    代码如下:
    底层get方法如下
     public T get() {
        Thread t = Thread.currentThread(); 这就是key
        ThreadLocalMap map = getMap(t); 得到类似于map集合的key和value
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this); 得到对应key的value值
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;  返回value的值
            }
        }
        return setInitialValue();
    }
    底层set方法如下
     public void set(T value) {
        Thread t = Thread.currentThread(); 这就是key
        ThreadLocalMap map = getMap(t); 得到类似于map集合的key和value
        if (map != null) {
            map.set(this, value); 设置key和value
        } else {
            createMap(t, value);
        }
    }

    也就是说,在同一个线程,那么连接一定是相同的,即一个线程一个连接
    这样就基本导致了,一个用户的连接基本不会变
     */
    public Connection getThreadConnection(){

        //先从ThreadLocal上获取连接
        Connection conn = threadLocal.get();

        //判断是否为空,即当前线程中是否有连接
        if(conn == null){

            //从数据源中获取一个连接,并且放到ThreadLocal中
            try {
                //不为null
                conn= dataSource.getConnection();

                threadLocal.set(conn);

            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        return conn;
    }

    //解除当前线程的连接绑定
    public void removeThreadConnection(){
        threadLocal.remove();
        
        /*
        底层remove方法如下
         public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread()); 得到类似于map集合的key和value
         if (m != null) {
             m.remove(this); 删除对应key
         }
     }
         */
    }
}

连接池和ThreadLocal的区别:
//接下来,我们解释为什么不用数据库连接池,而是使用ThreadLocal线程绑定
/*
在这之前,我们需要明白,在数据库里,不同的会话,就是不同的事务,而会话,就是一个连接
对于不理想的连接池(下面有括号里的说明):
我们主要是使用他帮我们创建好的连接,那么这个连接也就不会关闭,那么会有如下情况:
在使用事务时:
同一时间和不同时间(可以出现不同连接)和不同地方(可以出现不同连接),单个线程,那么事务操作,没有什么问题
因为一个线程无论是否是一个连接,再怎么操作,基本也不会出现事务的不合理
同一时间,多个线程,基本一定不是同一个连接,也就基本也不会出现事务的不合理
不同时间和地方(可以出现不同连接),多个线程,假如
可能出现的连接池:不是并发情况下的获取连接
第一个线程正在进行操作(这个操作要很久,插入很多数据)时,归还了连接(并没有关闭连接,且没有设置自动提交)
第二个线程正好得到了这个连接(也就是从连接池中拿),再次使用事务,直接提交(第一个线程还没插入完)
由于是同一个连接,那么可以发现,第一个线程插入不了了(提交了,被中断,插入数据不完善)
超级不理想的连接池:在并发下的获取连接
同时进入,类似于懒汉式的线程不安全的操作,可能会没有对应锁,但一般都会有锁
那么就算设置了自动提交
也会使得其中一个连接提交或者关闭归还自动提交后
另外一个线程还没有操作完(用户有不同的行为的,即可能有些用户慢一点,就会出现这种情况)
很明显,有些数据库连接池,在多个线程中,有可能会是同一个连接的情况,会出现事务的不合理
主要是会再去连接池中拿,导致同一个连接
虽然是可能,但还是有机会
而ThreadLocal线程绑定,那么我们将线程与连接当作联系,那么就出现了,一个线程,必定只有对应的一个连接
那么就基本不会出现事务的不合理
但对于上面出现的连接池,没有设置自动提交,或者并发获取的连接,也是会出现事务的不合理,但通常都会自动提交

所以现在基本不会出现上述不理想的连接池
如果出现了,就算是使用ThreadLocal线程绑定
也是会出现事务的不合理,没有设置自动提交或并发获取连接(并发基本不会出现,即加了锁)
即多个线程获取同一个连接没有关系,主要是对应的没有自动提交(设置了获得同一连接时,一定的相对干净的)
也就是说现在基本都是正常的连接池(加锁,自动提交)
所以ThreadLocal线程绑定的主要功能并不是上述功能,主要是下面的功能,请看如下
我们知道一个连接对应一个事务,在单线程的情况下,是会出现获得不同的连接的
一般的dao层的代码我们都是一个连接来使用全部的方法
但是若一个dao层方法,只对应一个连接(这是可能的情况,大多数情况下,可能会出现)
比如前面使用QueryRunner的对应方法里,没有指定连接,那么就是去连接池里获得连接的
那么就会出现,两个不同的事务,于是,借助上面的转出转入操作,那么转出操作的事务和转入操作的事务,不是同一个
也就是说,又变成了两个独立的事务,很明显,又回到了上面的情况,只是方式不同而已
可能转出提交了,而中间正好报错,导致没有转入,或者转入回滚了,这些情况
那么为了防止不同的dao层方法,获得连接,就出现了新的操作
即就有了ThreadLocal线程绑定,这样会先判断
在使用方法时,是否为同一个线程后,在考虑去连接池里拿连接,一般都是正常连接池


所以现在的数据库连接池优化上,底层一般也是使用ThreadLocal这个方式的
注意:对于大多数连接池,若没有指定连接的话,基本都是获取连接池的连接,来进行操作,即基本会出现不同连接
所以事务最好要指定连接


对于数据库连接池的归还问题:是一个双向链表存放对应连接,即头部获取,尾部归还(归还连接池)
这就是不在并发情况下的获取相同连接的原理,正好得到了这一个位置的连接



简单一句话,在方法分开的地方,无论你怎么去连接池里拿连接,得到的都是一个连接
*/
ThreadLocal使用图解:

65-Spring实战以及AOP介绍_第1张图片

最后得到的都是同一个对象,也就是同一个连接,与引用的不同没有关系(不同引用可以指向同一个对象)
因为执行方法的,从来不是引用,而是对象,只是用引用来代替而已
编写事务管理器:
package com.lagou.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.Connection;

/**
 *事务管理工具类:包含:开启事务,提交事务,回滚事务,释放资源
 */
@Component
public class TransactionManager {

    @Autowired
    private ConnectionUtils connectionUtils;

    /*
     * 开启事务
     */
    public void beginTransaction(){

            Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            //开启手动事务,也就是关闭自动提交
            threadConnection.setAutoCommit(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /*
     * 提交事务
     */
    public void commit(){

            Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            //提交事务
            threadConnection.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
    回滚事务
     */
    public void rollback(){

            Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            //回滚事务
            threadConnection.rollback();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
     /*
     释放资源
     */
    public void release(){

        //将手动事务改成自动提交事务
        Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            threadConnection.setAutoCommit(true);

            //将连接归还到连接池中
            connectionUtils.getThreadConnection().close();

            //解除线程绑定,就是删除对应key
            connectionUtils.removeThreadConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



    }



修改service层代码:
package com.lagou.service.impl;

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

/**
 *
 */
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private TransactionManager transactionManager;

    @Override
    public void transfer(String outUser, String inUser, Double money) {

        //手动开启事务:调用事务管理器类中的开启事务方法
        transactionManager.beginTransaction();
        try {
            //调用了减钱方法
            accountDao.out(outUser, money);
            
            //int i = 1/0;  这时你就可以通过这个来实验加对应参数连接和不加的区别了

            //调用了加钱方法
            accountDao.in(inUser, money);

            //手动提交事务
            transactionManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //手动回滚事务
            transactionManager.rollback();
        }finally{
            //手动释放资源
            transactionManager.release();
        }
        //可以看出,sql的事务操作在实际应用上的重大作用
    }
}

修改dao层代码 :
package com.lagou.dao.impl;

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

import java.sql.SQLException;

/**
 *
 */
@Repository("accountDao")  //生成该类实例到IOC容器中
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner queryRunner;

    @Autowired
    private ConnectionUtils connectionUtils;

    @Override
    public void out(String outUser, double money) {

        String sql = "update account set money = money - ? where name = ?";
        try {
            queryRunner.update(connectionUtils.getThreadConnection(), sql, money, outUser);
            //指定了连接,那么就不会去连接池里获取连接,使得基本出现不同的事务了(很小几率获得同一连接)
            //因为归还,但就算获得了,基本也是自动提交的,所以还是不要去碰这个运气
            System.out.println(queryRunner);
            //输出对象,会提示一个小错误,但是不影响程序的运行
            //可以在url后面加上&useSSL=false来解决
            //是因为输出这个,触发了安全机制,需要加上&useSSL=false
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void in(String inUser, double money) {
        String sql = "update account set money = money + ? where name = ?";
        try {
            queryRunner.update(connectionUtils.getThreadConnection(), sql, money, inUser);

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

问题分析 :
上面代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制
也产生了一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码
并且业务层方法和事务控制方法耦合了,违背了面向对象的开发思想
Proxy优化转账案例:
我们可以将业务代码和事务代码进行拆分,通过动态代理的方式,对业务方法进行事务的增强
这样就不会对业务层产生影响,解决了耦合性的问题啦!
常用的动态代理技术:
JDK 代理:
基于接口的动态代理技术·:利用拦截器(必须实现invocationHandler)加上反射机制生成一个代理接口的匿名类
在调用具体方法前调用InvokeHandler来处理,从而实现方法增强
CGLIB代理:
基于父类的动态代理技术:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法
不重写也没关系,只是使用的是父类方法的操作然后增强
在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行增强

65-Spring实战以及AOP介绍_第2张图片

相当于其他类帮你调用方法(也有自己的一些调用,这些调用,就可以说是增强)
JDK动态代理方式:
Jdk工厂类:
package com.lagou.proxy;

import com.lagou.service.AccountService;
import com.lagou.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 *JDK动态代理工厂类
 */
@Component
public class JDKProxyFactory {

    @Autowired
    private AccountService accountService;

    @Autowired
    private TransactionManager transactionManager;

    /*
    采用JDk动态代理技术来生成目标类的代理对象,即使用代理类Proxy类来生成代理对象
     */
    public AccountService createAccountServiceJdkProxy(){
        /*
        Proxy.newProxyInstance()方法的参数介绍:
        ClassLoader loader,
        类加载器,反射基本也是可以是类加载器来操作的(他可以操作Class,一般也有其他功能,所以这里是类加载器),这里可以借助被代理对象获取到类加载器,由于大多数的类加载器是一样的,所以这个参数基本可以随便使用某个类的加载器
        Class[] interfaces,
        被代理类所需要实现的全部接口,所以是数组,注意是接口的Class类型哦,一般这里代表返回值类型(因为代理类与他相关,实现他)
        InvocationHandler h
        当代理对象调用接口中的任意方法时,那么都会执行InvocationHandler的invoke方法

        反射中,只要有方法名,就可以通过invoke方法来调用对应方法
        如Object invoke(Object obj,Object... args)
        使用对象obj来调用此Method对象所表示的成员方法,实参传递args
        具体操作是根据反射出来的类实例,获得Method类,然后根据方法名称,调用反射出来的类的invoke方法
        那么这个反射出来的对应类也就执行了对应方法名称的方法

        那么根据参数,可以知道,通过类加载器来反射出对象(代理),然后根据接口方法名称的数组得到对应名称
        之后调用对应的invoke方法进行执行对应名称的方法
        */

        //getClass()得到的是对应对象的Class对象,因为是对象来调用方法的
        //这点必须理解,后面是以这点为中心
        //而newInstance方法,是创建参数的对象实例,所以不能是接口或者抽象类
        //Class[] getInterfaces(),获取实现的所有接口,Class调用
        //那么就只知道对应对象的实现的接口信息了,一般没去用
        //一般返回格式是,如interface java.lang.Comparable interface java.io.Serializable
        //一般来说该位置代表返回值类型,因为是接口,所以可以是多种,都操作对应的类的(因为他们都是被实现)

        //之所以可以指定对应类型,参数已经指定好了,因为都是对应的对象
        //这就是相当于我们创建了一个一样的对象,帮我们操作原对象的方法
        AccountService o = (AccountService) 
            Proxy.newProxyInstance(accountService.getClass().getClassLoader(), 
                                   accountService.getClass().getInterfaces(), new 
                                   InvocationHandler() {

            //这里参数名称解释
            //proxy:当前的代理对象引用,一般没有去用,反射好的对象,基本是全局的
            //method:被调用的目标方法引用,自己调用时,出现的对应Method对象
            //args:被调用的目标方法所用到的参数,自己调用时,出现的对应参数
            //可以发现,所有的一切操作都在这里执行
            //也就是说proxy是反射出来的实例
            //method就是这个实例的对应Method对象,已经弄好了对应名称,每次调用都是其中一个
            //而args就是对应对象实现的接口格式信息,即包括接口方法的对应参数,每次调用都是其中一组参数
            //之所以没有指定,是进行拦截了自己手动对象调用的信息,而放入的,也就是自己进行调用的方法
            //所以method的名称就是拦截的原信息
            //而args也是拦截的对应参数信息
            //那么对象就可以通过invoke方法,在有methed的情况下
            //通过方法名称,使用invoke来调用对应名称的接口方法了,就使得出现动态代理,即可以增强方法
            //在下面方法的地方可以再次增加其他操作,即增强的意思
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				
                //手动开启事务:调用事务管理器类中的开启事务方法
                transactionManager.beginTransaction();
                try {
                //System.out.println("目标方法执行之前动态进行了方法增强");
             //被代理的对象的方法执行,我们使用代理对象进行操作时拦截对应信息,然后根据这些信息进行反射执行
                method.invoke(accountService, args);
                //增强的一部分内容
                //System.out.println(1);

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

                return null; //我们主要用到上面的invoke方法,这个返回值是什么,基本可以不用管
            }
        });

        return o;
    }

    //所以说,静态代理,是在类里进行放入其他类,而形成增强,而这里是通过反射进行方法确定,而进行增强
    //静态放入其他类,这里我们自己创建一个类,当作拦截对象(也是对应的类型)
    //再根据拦截信息,来操作反射的真正对象的方法(对应方法中,可以加上其他增强)


    //最后注意:JDK代理只能操作接口,若操作类就会报错,那是因为对应的对象已经继承了Proxy了
    //那么只能实现自己这个接口(在文件里先写好,然后进行运行)
    //因为基本不可能在运行时修改代码
    //所以使得我们强转时可以变成对应接口类型

}

package com.lagou.service.impl;

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

/**
 *
 */
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;


    @Override
    public void transfer(String outUser, String inUser, Double money) {



            //调用了减钱方法
            accountDao.out(outUser, money);

            //int i = 1/0;

            //调用了加钱方法
            accountDao.in(inUser, money);


    }
}

测试代码:
 /*
    测试JDK动态代理优化转账案例
     */
    @Test
    public void testTransferProxyJDK(){

        System.out.println("被代理类的transfer执行了");

        //当前返回的实际上是AccountService的一个代理对象proxy
        AccountService accountServiceJdkProxy = jdkProxyFactory.createAccountServiceJdkProxy();

        //代理对象Proxy调用接口中的任意方法时,都会执行底层的invoke方法
        //因为调用方法的实际操作不是引用,而是对象,所以这里说Proxy对象,因为对应对象继承Proxy类
        accountServiceJdkProxy.transfer("tom", "jerry", 100d);

    }
/*
可以发现,我们原来执行accountServiceJdkProxy.transfer("tom", "jerry", 100d);代码时
很多的事务代码不用写了,若对应原来代码的话来说,若这样的transfer方法一多,那么对应事务代码一定会多
所以我们使得在执行transfer方法时,通过反射在某个方法里执行(拦截)
而这个方法里进行了增强,那么每个这样的方法,就不需要对应的事务代码了,且是动态的,也就是比静态的扩展性高
静态的需要对应类,虽然直接执行也是可以,但是必须手动写对应的方法名称,且一般需要一致
而动态的通过拦截,就不需要手动写对应方法了,所以扩展性高

但是在这里要注意:但凡扩展性高的,基本底层操作也会多点,所以运行效率也会慢一些,但开发效率快

代理对原来的方法进行改变,使得我们使用的是代理的,即代理增强
*/
CGLIB动态代理方式:
Cglib工厂类:
package com.lagou.proxy;

import com.lagou.service.AccountService;
import com.lagou.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/*
 该类就是采用cglib动态代理来对目标类(AccountServiceImpl)进行方法(transfer)的动态增强(添加上事务控制)
 */
@Component
public class CglibProxyFactory {

    @Autowired
    private AccountService accountService;

    @Autowired
    private TransactionManager transactionManager;

    public AccountService createAccountServiceCglibProxy(){
        // 编写cglib对应的API来生成代理对象进行返回
        // 参数1 : 目标类的字节码对象
        // 参数2:  动作类,当代理对象调用目标对象中原方法时,那么会执行intercept方法
        AccountService accountServiceproxy = (AccountService) 
            Enhancer.create(accountService.getClass(), new MethodInterceptor() {

            // o : 代表生成的代理对象,一般没去用,反射到的对象一般是全局的
            // method:调用目标方法的引用,拦截到的
            // objects:方法入参,拦截到的
            // methodProxy:代理方法,一般有对应实现接口的信息,一般没去用
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy 
                                    methodProxy) throws Throwable {

                try {
                    // 手动开启事务:调用事务管理器类中的开启事务方法
                    transactionManager.beginTransaction();

                    method.invoke(accountService, objects);

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


                return null;
            }
        });
        return accountServiceproxy;


    }



}

测试代码:
    /*
   测试Cglib动态代理优化转账案例
    */
    @Test
    public void testTransferProxyCglib(){

        //accountServiceCglibProxy:proxy
        AccountService accountServiceCglibProxy = cglibProxyFactory.createAccountServiceCglibProxy();

        accountServiceCglibProxy.transfer("tom", "jerry", 100d);


    }
与JDK动态代理不同,Cglib动态代理的返回值可以是接口
也可以是对应类(需要符号父类指向子类或者本身的规则),即指向规则
而JDK动态代理只可以返回接口,因为JDK动态代理继承了一个类,所以不可再次继承
而Cglib动态代理则没有继承,所以可以继承类
通过分析进行文件添加,但他们都是通过拦截来进行操作的
实际上在文件添加时,由于实现或继承了对应接口或者类,在调用对应方法时
那么就会有对应方法的信息(添加时封装好的),即这些信息就可以说是拦截的信息,然后赋值使用
JDK动态代理由于代理继承了类,那么就要对应需要代理的类实现一个接口
而Cglib动态代理虽然可以返回接口和对应类,但他们对应的final的重写不了
JDK动态代理和Cglib动态代理他们都有父子类关系
初识AOP :
什么是AOP:
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程
AOP 是 OOP(面向对象编程) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容
利用AOP可以对业务逻辑的各个部分进行隔离
从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
这样做的好处是:
在程序运行期间,在不修改源码的情况下对方法进行功能增强
逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
减少重复代码,提高开发效率,便于后期维护
AOP底层实现:
实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的
在运行期间Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入
在去调用目标对象的方法,从而完成功能的增强
AOP相关术语:
Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写
并通过配置的方式完成指定目标的方法增强
在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语
常用的术语如下:
/*
Target(目标对象):代理的目标对象  也就是前面说的AccountServiceImpl,在这里也可以叫做AccountService
 
Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类
 
Joinpoint(连接点):所谓连接点是指那些可以被拦截到的点。在spring中,这些点指的是方法,因为
spring只支持方法类型的连接点
 
Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
 
Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知
分类:前置通知、后置通知、异常通知、最终通知、环绕通知(可以说是前面四个的整体,即整体的通知)
这个整体也包括执行方法,也就是说,使用环绕通知的话,执行方法需要自己手动执行了(相当于替代执行方法的位置)
 
Aspect(切面):是切入点和通知(引介)的结合
 
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织
入,而AspectJ采用编译期织入和类装载期织入
*/
图解:

65-Spring实战以及AOP介绍_第3张图片

65-Spring实战以及AOP介绍_第4张图片

对应理解的代码如下:
package com.lagou.proxy;

import com.lagou.service.AccountService;
import com.lagou.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 *JDK动态代理工厂类
 */
@Component
public class JDKProxyFactory {

    @Autowired
    private AccountService accountService;

    @Autowired
    private TransactionManager transactionManager;

    public AccountService createAccountServiceJdkProxy(){

        AccountService o = (AccountService) 
            Proxy.newProxyInstance(accountService.getClass().getClassLoader(), 
                                   accountService.getClass().getInterfaces(), new InvocationHandler() 
                                   {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {


                try {
                    //可以通过Method的getName()方法得到对应的方法名称
                    if(method.getName().equals("transfer")) {
                        System.out.println("进行了前置增强");
                        //手动开启事务:调用事务管理器类中的开启事务方法
                        transactionManager.beginTransaction();

                        method.invoke(accountService, args);
                        //手动提交事务
                        transactionManager.commit();
                        System.out.println("进行了后置增强");
                    }else{
                        method.invoke(accountService, args);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    //手动回滚事务
                    transactionManager.rollback();
                }finally{
                    //手动释放资源
                    transactionManager.release();
                }

                return null;
            }
        });

        return o;
    }


}

package com.lagou.service;

/**
 *
 */
public interface AccountService {

    //转账方法
    public void transfer(String out, String in, Double money);

    public void save();

    public void update();

    public void delete();



}

package com.lagou.service.impl;

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

/**
 *
 */
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;


    @Override
    public void transfer(String outUser, String inUser, Double money) {



            //调用了减钱方法
            accountDao.out(outUser, money);

            //int i = 1/0;

            //调用了加钱方法
            accountDao.in(inUser, money);


    }

    @Override
    public void save() {
        System.out.println("save方法");
    }

    @Override
    public void update() {

        System.out.println("update方法");
    }

    @Override
    public void delete() {
        System.out.println("delete方法");

    }
}

  /*
    测试JDK动态代理优化转账案例
     */
    @Test
    public void testTransferProxyJDK(){

        System.out.println("被代理类的transfer执行了");

        //当前返回的实际上是AccountService的一个代理对象proxy
        AccountService accountServiceJdkProxy = jdkProxyFactory.createAccountServiceJdkProxy();

      
        //accountServiceJdkProxy.transfer("tom", "jerry", 100d);

        accountServiceJdkProxy.save();
    }
AOP开发明确事项:
开发阶段(我们做的)
编写核心业务代码(目标类的目标方法) 切入点
把公用代码抽取出来,制作成通知(增强功能方法) 通知
在配置文件中,声明切入点与通知间的关系,即切面
运行阶段(Spring框架完成的):
Spring 框架监控切入点方法的执行
一旦监控到切入点方法被运行,拦截,并使用代理机制,动态创建目标对象的代理对象来进行执行方法
根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行
底层代理实现:
在 Spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式
当bean实现接口时,会用JDK代理模式
当bean没有实现接口,用cglib实现
也可以强制使用cglib,在spring配置中加入如下配置(后面会讲到)
<aop:aspectj-autoproxy proxy-target-class="true"/>
知识小结:
/*
aop:面向切面编程
aop底层实现:基于JDK的动态代理 和 基于Cglib的动态代理
aop的重点概念:
 Pointcut(切入点):真正被增强的方法
 Advice(通知/ 增强):封装增强业务逻辑的方法
 Aspect(切面):切点+通知
 Weaving(织入):将切点与通知结合,产生代理对象的过程
*/
基于XML的AOP开发:
快速入门:
步骤分析:
/*
1. 创建java项目,导入AOP相关坐标
2. 创建目标接口和目标实现类(定义切入点)
3. 创建通知类及方法(定义通知)
4. 将目标类和通知类对象创建权交给spring
5. 在核心配置文件中配置织入关系,及切面
6. 编写测试代码

*/
创建java项目,导入AOP相关坐标:
<dependencies>
    
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.1.5.RELEASEversion>
    dependency>
    
    <dependency>
        <groupId>org.aspectjgroupId>
        <artifactId>aspectjweaverartifactId>
        <version>1.8.13version>
    dependency>
    
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-testartifactId> 
        <version>5.1.5.RELEASEversion>
    dependency>
    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.12version>
    dependency>
dependencies>

创建目标接口和目标实现类:
package com.lagou.service;

/**
 *
 */
public interface AccountService {

    /*
    目标方法:(切入点:要进行拦截增强的方法)
     */
    public void transfer();
}

package com.lagou.service.impl;

import com.lagou.service.AccountService;

/**
 *
 */
public class AccountServiceImpl implements AccountService {

    /*
    目标方法:(切入点:要进行拦截增强的方法)
     */
    @Override
    public void transfer() {
        System.out.println("转账方法执行了");
    }
}

创建通知类:
package com.lagou.advice;

/**
 *通知类
 */
public class MyAdvice {

    public void before() {
        System.out.println("前置通知执行了");
    }
}

将目标类和通知类对象创建权交给spring以及在核心配置文件中配置织入关系,及切面:

<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"
       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">

    

    
<bean id="accountService" class="com.lagou.service.impl.AccountServiceImpl">bean>

    
<bean id="myAdvice" class="com.lagou.advice.MyAdvice">bean>
    
    
    


    
    <aop:config>
        
        <aop:aspect ref="myAdvice"> 
            <aop:before method="before" 
                        pointcut="execution(void 
                                  com.lagou.service.impl.AccountServiceImpl.transfer())"/>
            
        aop:aspect>
    aop:config>

beans>
编写测试代码:
package com.lagou.test;

import com.lagou.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.SpringJUnit4ClassRunner;

/**
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")

public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() {
        accountService.transfer();

    }
}

XML配置AOP详解:
切点表达式:
表达式语法:
//execution([修饰符] 返回值类型 包名.类名.方法名(参数))  []包括的是可以省略的
访问修饰符可以省略
/*
execution(void com.lagou.service.impl.AccountServiceImpl.transfer(java.lang.String))
*/
返回值类型、包名、类名、方法名可以使用星号 * 代替,代表任意
/*
execution(void com.lagou.service.impl.AccountServiceImpl.transfer(java.lang.String))
execution(* *.*.*.*.*.*())
代表任意返回值类型,任意包,任意类,的所有方法,且参数是无参的都会被监听
访问修饰没有*,因为默认为public的,所以就没有设置*这个东西
*/
包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
/*
execution(* *..*.*())
代表任意返回值类型,任意包及其子包下的所有类的所有方法,且无参的都会被监听
也就相当于execution(* *.*.*.*.*.*())
即*.*.*.*省略成了*..
最后都到达对应的类
*/
参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表
/*
execution(* *..*.*(..))
代表任意返回值类型,任意包及其子包下的所有类的所有方法,且参数是所有参数类型及其任意个数的都会被监听
即代表任意返回值类型,任意包及其子包下的所有类的所有方法(不用指定参数了)都会被监听
而execution(* *..*.*(*)) 代表一个参数无视参数类型,当然也是可以与其他参数一起的,用,隔开
如execution(* *..*.*(java.lang.String,*)) 两个参数,第二个参数无视参数类型
*/
还有如下例子:
/*
execution(public void com.lagou.service.impl.AccountServiceImpl.transfer())
execution(void com.lagou.service.impl.AccountServiceImpl.*(..))
execution(* com.lagou.service.impl.*.*(..))
execution(* com.lagou.service..*.*(..))
*/
最后注意一点:使用@Test的测试方法,public void 方法名称(){}是固定格式,也就是说
访问权限必须是public,返回值必须是void,方法参数必须是无参,有一个不满足就会报错
使用后置通知,修改配置文件和加上对应方法:
   
<aop:config>
        
        <aop:aspect ref="myAdvice"> 
            <aop:before method="before" 
                        pointcut="execution(void 
                                  com.lagou.service.impl.AccountServiceImpl.transfer())"/>
       

            <aop:after-returning method="after" 
                                 pointcut="execution(void 
                                           com.lagou.service.impl.AccountServiceImpl.transfer())"/>

        aop:aspect>
    aop:config>
package com.lagou.advice;

/**
 *通知类
 */
public class MyAdvice {

    public void before() {
        System.out.println("前置通知执行了");
    }
    public void after() {
        System.out.println("后置通知执行了");
    }
}

切点表达式抽取:
当多个增强的切点表达式相同时,可以将切点表达式进行抽取
在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式
  
    <aop:config>
        <aop:pointcut id="myPointcut" 
                      expression="execution(void 
                                  com.lagou.service.impl.AccountServiceImpl.transfer())"/>
        
        <aop:aspect ref="myAdvice"> 
            <aop:before method="before" pointcut-ref="myPointcut"/>
    
            -->
            <aop:after-returning method="after" pointcut-ref="myPointcut"/>

        aop:aspect>
    aop:config>
通知类型:
通知的配置语法:
aop:通知类型>

65-Spring实战以及AOP介绍_第5张图片

注意:通常情况下,环绕通知都是独立使用的
测试代码:
package com.lagou.advice;

/**
 *通知类
 */
public class MyAdvice {

    public void before() {
        System.out.println("前置通知执行了");
    }
    public void after() {
        System.out.println("后置通知执行了");
    }

    public void afterThrowing() {
        System.out.println("异常通知执行了");
    }
    public void afterFinally() {
        System.out.println("最终通知执行了");
    }
}

xml配置文件:
 
    <aop:config>
        <aop:pointcut id="myPointcut" 
                      expression="execution(void 
                                  com.lagou.service.impl.AccountServiceImpl.transfer())"/>
        
        <aop:aspect ref="myAdvice">
            <aop:before method="before" pointcut-ref="myPointcut"/>
      
            <aop:after-returning method="after" pointcut-ref="myPointcut"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
            <aop:after method="afterFinally" pointcut-ref="myPointcut"/>
        aop:aspect>
    aop:config>

运行结果:
/*
前置通知执行了     aop:before
转账方法执行了    
后置通知执行了(或异常通知执行了)		aop:after-returning   aop:after-throwing
只能有一个,因为出现异常就不会执行后置通知了
最终通知执行了		aop:after

即可以看成这样
 try{
    前置通知
    转账方法
    后置通知
}catch (Exception e){
    异常通知
    e.printStackTrace();
}finally{
    最终通知
}
大致位置,可能Spring版本的不同,会造成位置的不同,其中后置通知和最终通知可能会变
*/
使用环绕通知:
<aop:around method="around" pointcut-ref="myPointcut">aop:around>
//Proceeding JoinPoint :正在执行的连接点  切点
    //术语中JoinPoint(连接点):所谓连接点是指那些可以被拦截到的点
    //也就是说,我们能够拦截哪些方法的总和,也可以说,对应代理可以执行的方法的总和
    //当我们使用他时,可以知道哪个方法被拦截,得到对应拦截信息
    //那么我们可以通过这个连接点,执行对应方法,那么就相当于执行切入方法
    //一般的在通过监听器监听到切入点之后,然后拦截,自动通过拦截信息进行调用对应方法
    //而由于环绕,使得我们需要手动执行,那么就需要通过自己去使用拦截信息了
    public Object around(ProceedingJoinPoint pjp){
        //切点方法执行
        Object proceed = null;
        try {
            System.out.println("前置通知执行了");
            proceed = pjp.proceed();
            System.out.println("后置通知执行了");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("异常通知执行了");
        }finally {
            System.out.println("最终通知执行了");
        }
        return proceed;


        //可以看出,我们手动操作了通知,但实际上还是放在对应地方的
    }
其实相当于替代执行方法的位置
通过实验
 
    <aop:config>
        <aop:pointcut id="myPointcut" 
                      expression="execution(void 
                                  com.lagou.service.impl.AccountServiceImpl.transfer())"/>
        
        <aop:aspect ref="myAdvice"> 
            <aop:before method="before" pointcut-ref="myPointcut"/>
            <aop:after-returning method="after" pointcut-ref="myPointcut"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
            <aop:after method="afterFinally" pointcut-ref="myPointcut"/>
            <aop:around method="around" pointcut-ref="myPointcut">aop:around>
        aop:aspect>
    aop:config>
当他们一起时,对应方法
package com.lagou.advice;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 *通知类
 */
public class MyAdvice {

    public void before() {
        System.out.println("前置通知执行了");
    }
    public void after() {
        System.out.println("后置通知执行了");
    }

    public void afterThrowing() {
        System.out.println("异常通知执行了");
    }
    public void afterFinally() {
        System.out.println("最终通知执行了");
    }

    public Object around(ProceedingJoinPoint pjp){
        //切点方法执行
        Object proceed = null;
        try {
            System.out.println("前置通知执行了11");
            proceed = pjp.proceed();
            System.out.println("后置通知执行了11");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("异常通知执行了11");
        }finally {
            System.out.println("最终通知执行了11");
        }
        return proceed;

    }
}

执行结果:
/*
前置通知执行了
前置通知执行了11
转账方法执行了
后置通知执行了11
最终通知执行了11
最终通知执行了    到前面了
后置通知执行了

发现,的确相当于是替换了原来执行方法的位置,所以需要手动执行,但前面的最终方法,会与后置方法交换位置
*/
知识小结:
/*
aop织入的配置
 
        
            
        
    
                                                        
通知的类型
 前置通知、后置通知、异常通知、最终通知
 环绕通知
 
切点表达式
 execution([修饰符] 返回值类型 包名.类名.方法名(参数))

*/
基于注解的AOP开发 :
快速入门:
步骤分析:
/*
1. 创建java项目,导入AOP相关坐标
2. 创建目标接口和目标实现类(定义切入点)
3. 创建通知类(定义通知)
4. 将目标类和通知类对象创建权交给spring
5. 在通知类中使用注解配置织入关系,升级为切面类
6. 在配置文件中开启组件扫描和 AOP 的自动代理
7. 编写测试代码
*/
创建java项目,导入AOP相关坐标:
<dependencies>
    
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.1.5.RELEASEversion>
    dependency>
    
    <dependency>
        <groupId>org.aspectjgroupId>
        <artifactId>aspectjweaverartifactId>
        <version>1.8.13version>
    dependency>
    
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-testartifactId>
        <version>5.1.5.RELEASEversion>
    dependency>
    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.12version>
    dependency>
dependencies>
创建目标接口和目标实现类:
package com.lagou.service;

/**
 *
 */
public interface AccountService {


    public void transfer();
}

package com.lagou.service.impl;

import com.lagou.service.AccountService;
import org.springframework.stereotype.Service;

/**
 *
 */
@Service
public class AccountServiceImpl implements AccountService {


    @Override
    public void transfer() {
        System.out.println("转账方法执行了");


    }
}

创建通知类:
package com.lagou.advice;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 *通知类
 */
@Component
@Aspect //升级为切面类:配置切入点和通知的关系,也就是存放通知方法和对应指定的切入方法路径的类
//因为Aspect是是切入点和通知(引介)的结合
public class MyAdvice {

    //很明显,被扫描到时,顺便也有了对应类实例(读取到@Aspect的作用,使得有对应实例)
    //即也通过注解信息,通过了方法添加
    @Before("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
    public void before() {
        System.out.println("前置通知执行了");
    }

}

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

<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.lagou">context:component-scan>

    
    <aop:aspectj-autoproxy proxy-target-class="true">aop:aspectj-autoproxy>
    

beans>
编写测试代码 :
package com.lagou.test;

import com.lagou.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.SpringJUnit4ClassRunner;

/**
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")

public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() {
        accountService.transfer();

    }

}

注解配置AOP详解 :
切点表达式:
切点表达式的抽取
package com.lagou.advice;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 *通知类
 */
@Component
@Aspect
public class MyAdvice {

    //会先识别这个注解,所以位置不做要求,并讲对应的设置用类名加方法组成
    //后面的注解中,就可以使用这个信息了,当然有的话就会识别
    @Pointcut("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
    public void myPoint(){}


    @Before("MyAdvice.myPoint()")
    public void before() {
        System.out.println("前置通知执行了");
    }

    @AfterReturning("MyAdvice.myPoint()")
    public void afterReturning() {
        System.out.println("后置通知执行了");
    }

}

通知类型:
通知的配置语法:@通知注解(“切点表达式")

65-Spring实战以及AOP介绍_第6张图片

注意:
/*
当前四个通知组合在一起时,执行顺序如下:
@Before -> @After -> @AfterReturning(如果有异常:@AfterThrowing)
即最终通知与后置通知交换了位置
*/
package com.lagou.advice;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 *通知类
 */
@Component
@Aspect
public class MyAdvice {

    //会先识别这个注解,所以位置不做要求,并讲对应的设置用类名加方法组成
    //后面的注解中,就可以使用这个信息了,当然有的话就会识别
    @Pointcut("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
    public void myPoint(){}


    @Before("MyAdvice.myPoint()")
    public void before() {
        System.out.println("前置通知执行了");
    }

    @AfterReturning("MyAdvice.myPoint()")
    public void afterReturning() {
        System.out.println("后置通知执行了");
    }

    @AfterThrowing("MyAdvice.myPoint()")
    public void afterThrowing() {
        System.out.println("异常通知执行了");
    }

    @After("MyAdvice.myPoint()")
    public void after() {
        System.out.println("最终通知执行了");
    }

    @Around("MyAdvice.myPoint()")
    public Object around(ProceedingJoinPoint pjp){
        //切点方法执行
        Object proceed = null;
        try {
            System.out.println("前置通知执行了11");
            proceed = pjp.proceed();
            System.out.println("后置通知执行了11");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("异常通知执行了11");
        }finally {
            System.out.println("最终通知执行了11");
        }
        return proceed;

    }

}

运行结果:
/*
前置通知执行了11   在前面了
前置通知执行了
转账方法执行了
后置通知执行了11
最终通知执行了11
最终通知执行了
后置通知执行了
*/
他们的添加方法,有不同的效果,主要是注解或者环绕通知造成的
一般的,我们都会单独使用环绕通知,而不会用其他通知和环绕通知一起,若其他通知出现顺序不对,就可以使用环绕通知来操作
一般开发中,基本都会使用环绕通知来进行,其他通知基本不会使用,因为环绕通知比较方便
纯注解配置 :
package com.lagou.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 *
 */
@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy //开启AOP自动代理 替代了aop:aspectj-autoproxy
public class SpringConfig {
}


package com.lagou.test;

import com.lagou.config.SpringConfig;
import com.lagou.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.SpringJUnit4ClassRunner;

/**
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
//使用classes指定类,不使用基本指定xml,他们不能互换,否则报错

public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() {
        accountService.transfer();

    }

}

知识小结 :
/*
使用@Aspect注解,标注切面类
使用@Before等注解,标注通知方法
使用@Pointcut注解,抽取切点表达式
配置aop自动代理  或 @EnableAspectJAutoProxy

*/

//实际上可以看成两次代理(环绕),或者两个方法

AOP优化转账案例 :
依然使用前面的转账案例,将两个代理工厂对象直接删除!改为spring的aop思想来实现
xml配置实现:
在转账案例的配置文件中加入以下配置文件(注意命名空间和约束路径):
 
    <aop:config>

        
        <aop:pointcut id="myPoint" 
                      expression="execution(* com.lagou.service.impl.AccountServiceImpl.*(..))"/>

        
        <aop:aspect ref="transactionManager"> 
            
            <aop:before method="beginTransaction" pointcut-ref="myPoint"/>
            <aop:after-returning method="commit" pointcut-ref="myPoint"/>
            <aop:after-throwing method="rollback" pointcut-ref="myPoint"/>
            <aop:after method="release" pointcut-ref="myPoint"/>
        aop:aspect>

    aop:config>
注意加上依赖:
  
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>1.8.13version>
        dependency>
执行测试方法:
 @Test
    public void textTransfer() {
        accountService.transfer("tom", "jerry", 100d);
        //100d就是这个100是double类型,就如3.14f就是float类型
    }
注解配置实现 :
在转账案例的配置文件中加入以下配置文件:
 
    <aop:aspectj-autoproxy>aop:aspectj-autoproxy>
根据下面代码,在转账案例的对应地方加上对应代码:
@Component
@Aspect //先声明为切面类,即通知类
public class TransactionManager {
    @Around("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
    //返回值类型也可以是void,或者返回null也可以,会根据返回值类型加上对应的变量的,或者不加
    //所有返回值类型是什么基本无影响
    public Object around(ProceedingJoinPoint pjp){

        Object proceed = null;
        try {
            //开启事务
            connectionUtils.getThreadConnection().setAutoCommit(false);
            proceed = pjp.proceed();
            //提交事务
            connectionUtils.getThreadConnection().commit();
        } catch (Throwable e) {
            e.printStackTrace();
            //回滚事务
            try {
                connectionUtils.getThreadConnection().rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            try {
                //释放资源
                connectionUtils.getThreadConnection().setAutoCommit(true);
                //将连接归还到连接池中
                connectionUtils.getThreadConnection().close();

                //解除线程绑定,就是删除对应key
                connectionUtils.removeThreadConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
            return proceed;

    }


}
运行:
 @Test
    public void textTransfer() {
        accountService.transfer("tom", "jerry", 100d);
        //100d就是这个100是double类型,就如3.14f就是float类型
    }
使用注解拦截会先拦截我们写的Proxy类,即在使用注解时,若使用自己的代理类,那么基本会报错
而注解和xml则共生的添加,因为操作是一样的,除非有程序的报错,就如上面的事务,当注解和xml一起使用时
相当于大肠包小肠,即如下
/*
try {
前置
    try {
        前置
        方法
         后置
    }catch (Exception e){
       异常
    }finally {
        最终
    }
    这什么相当于再次替换了方法,具体谁后替换,看注解和xml谁后操作
    使得当上面最终关闭资源后,下面的后置可能就会报错
    
    后置
}catch (Exception e){
      异常
}finally {
    最终
}

即输出
前置
前置
方法
后置(异常)
最终
后置(异常)
最终
*/
 

你可能感兴趣的:(笔记,java,spring)