Spring-AOP

AOP:Aspect Oriented Programming 即:面向切面编程。

简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强

静态代理

//租房接口
public interface Rent {
    void rent();
}
//房东
public class Host implements Rent {
    //房东只管出租房子
    @Override
    public void rent() {
        System.out.println("房东出租房子");
    }
}
//代理
public class MyProxy implements Rent {

    private Host host;

    public MyProxy(Host host){
        this.host = host;
    }
    @Override
    public void rent() {
        seeHouse();
        host.rent();
        agreement();
        charge();
    }

    //在原来租房的基础上加强的方法
    public void seeHouse(){
        System.out.println("中介带你看房");
    }

    public void agreement(){
        System.out.println("中介和你签合同");
    }

    public void charge(){
        System.out.println("中介收费");
    }
}
//客户
public class Client {
    public static void main(String[] args) {
        //房东
        Host host = new Host();
        //代理(中介)
        MyProxy proxy = new MyProxy(host);
        proxy.rent();
    }
}

动态代理

动态代理常用的有两种方式

  • 基于接口的动态代理
    • 提供者:JDK 官方的 Proxy 类。
    • 要求:被代理类最少实现一个接口。
  • 基于子类的动态代理
    • 提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar
    • 要求:被代理类不能用 final 修饰的类(最终类)

JDK的动态代理

//租房接口
public interface Rent {
    void rent();
}
//房东
public class Host implements Rent{
    //房东只管出租房子
    @Override
    public void rent() {
        System.out.println("房东出租房子");
    }
}
public class ProxyFactory implements InvocationHandler {

    //被代理对象,将类型设为Object,可代理任何类型
    private Object proxied;

    public void setProxied(Object proxied){
        this.proxied = proxied;
    }

    //生成代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                proxied.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object res = method.invoke(proxied, args);
        //在原来租房的基础上加强的方法
        log(method.getName());
        return res;
    }

    public void log(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}
public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        ProxyFactory proxyFactory = new ProxyFactory();
        //设置被代理对象
        proxyFactory.setProxied(host);
        Rent proxy = (Rent) proxyFactory.getProxy();
        proxy.rent();
    }
}

AOP

作用:
在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便

AOP 的实现方式

动态代理

相关术语

  • Aspect

    表示切面。切入业务流程的一个独立模块。例如,前面案例的VerifyUser类,一个应用程序可以拥有任意数量的切面。

  • JoinPoint

    表示连接点。也就是业务流程在运行过程中需要插入切面的具体位置。例如,前面案例的AopEmailNotice类的setTeacher方法就是一个连接点。

  • Advice

    表示通知。是切面的具体实现方法。可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)和环绕通知(Around)五种。实现方法具体属于哪类通知,是在配置文件和注解中指定的。例如,VerifyUser类的beforeAdvice方法就是前置通知。

  • Pointcut

    表示切入点。用于定义通知应该切入到哪些连接点上,不同的通知通常需要切入到不同的连接点上。例如,前面案例配置文件的aop:pointcut标签。

  • Target

    表示目标对象。被一个或者多个切面所通知的对象。例如,前面案例的AopEmailNotice类。

  • Proxy

    表示代理对象。将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象为目标对象的业务逻辑功能加上被切入的切面所形成的对象。

  • Weaving

    表示切入,也称为织入。将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期。

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

基于XML的AOP

  1. 准备必要的代码,导入jar包

  2. 创建 spring 的配置文件并导入约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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">
</beans>
  1. 抽取公共代码制作成通知
public class TransactionManager {
    //定义一个 DBAssit
    private DBAssit dbAssit ;
    public void setDbAssit(DBAssit dbAssit){
        this.dbAssit = dbAssit;
    }
    //开启事务
    public void beginTransaction() {
        try {
            dbAssit.getCurrentConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //提交事务
    public void commit() {
        try {
            dbAssit.getCurrentConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //回滚事务
    public void rollback() {
        try {
            dbAssit.getCurrentConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //释放资源
    public void release() {
        try {
            dbAssit.releaseConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.配置

(1)配置通知类

<bean id="txManager" class="com.itheima.utils.TransactionManager">
<property name="dbAssit" ref="dbAssit"></property>
</bean>

(2)使用 aop:config 声明 aop 配置

aop:config 作用:
用于声明开始 aop 的配置

<aop:config>
<!-- 配置的代码都写在此处 -->
</aop:config>

(3)使用 aop:aspect 配置切面

aop:aspect:
作用:
用于配置切面。
属性:
id:给切面提供一个唯一标识。
ref:引用配置好的通知类 bean 的 id。

<aop:aspect id="txAdvice" ref="txManager">
<!--配置通知的类型要写在此处-->
</aop:aspect>

(4)使用 aop:pointcut 配置切入点表达式

aop:pointcut :
作用:
用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强
属性:
expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识

<aop:pointcut expression="execution(
    public void com.itheima.service.impl.AccountServiceImpl.transfer(
java.lang.String, java.lang.String, java.lang.Float)
)" id="pt1"/>

(5)使用 aop:xxx 配置

aop:before

作用:
用于配置前置通知。指定增强的方法在切入点方法之前执行
属性:
method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
poinitcut:用于指定切入点表达式

执行时间点:切入点方法执行之前执行

<aop:before method="beginTransaction" pointcut-ref="pt1"/>

aop:after-returning

作用:
用于配置后置通知
属性:
method :指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref :指定切入点表达式的引用

执行时间点:切入点方法正常执行之后。它和异常通知只能有一个执行

<aop:after-returning method="commit" pointcut-ref="pt1"/>

aop:after-throwing

作用:
用于配置异常通知
属性:
method :指定通知中方法的名称。
pointct :定义切入点表达式
pointcut-ref :指定切入点表达式的引用

执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个

<aop:after-throwing method="rollback" pointcut-ref="pt1"/>

aop:after

作用:
用于配置最终通知
属性:
method :指定通知中方法的名称。
pointct :定义切入点表达式
pointcut-ref :指定切入点表达式的引用

执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行。

<aop:after method="release" pointcut-ref="pt1"/>

aop:around

作用:
用于配置环绕通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用

说明:
它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式

注意: 通常情况下,环绕通知都是独立使用的

public Object transactionAround(ProceedingJoinPoint pjp) {
    //定义返回值
    Object rtValue = null;
    try {
        //获取方法执行所需的参数
        Object[] args = pjp.getArgs();
        //前置通知:开启事务
        beginTransaction();
        //执行方法
        rtValue = pjp.proceed(args);
        //后置通知:提交事务
        commit();
    }catch(Throwable e) {
        //异常通知:回滚事务
        rollback();
        e.printStackTrace();
    }finally {
        //最终通知:释放资源
        release();
    }
    return rtValue;
}

基于注解的AOP

1.使用注解配置

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;
}

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private DBAssit dbAssit ;
}

2.在配置文件中指定 spring 要扫描的包

<!-- 告知 spring,在创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>

3.把通知类也使用注解配置

@Component("txManager")
public class TransactionManager {
    @Autowired
    private DBAssit dbAssit ;
}

4.在通知类上使用@Aspect 注解声明为切面

@Component("txManager")
@Aspect//表明当前类是一个切面类
public class TransactionManager {
//定义一个 DBAssit
    @Autowired
    private DBAssit dbAssit ;
}

5.在增强的方法上使用注解配置通知

@Before

@Before("execution(* com.itheima.service.impl.*.*(..))")
    public void beginTransaction() {
    try {
        dbAssit.getCurrentConnection().setAutoCommit(false);
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

@AfterThrowing

@AfterThrowing("execution(* com.itheima.service.impl.*.*(..))")
public void rollback() {
    try {
        dbAssit.getCurrentConnection().rollback();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

@After

@After("execution(* com.itheima.service.impl.*.*(..))")
public void release() {
    try {
        dbAssit.releaseConnection();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Around

@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object transactionAround(ProceedingJoinPoint pjp) {
    //定义返回值
    Object rtValue = null;
    try {
        //获取方法执行所需的参数
        Object[] args = pjp.getArgs();
        //前置通知:开启事务
        beginTransaction();
        //执行方法
        rtValue = pjp.proceed(args);
        //后置通知:提交事务
        commit();
    }catch(Throwable e) {
        //异常通知:回滚事务
        rollback();
        e.printStackTrace();
        }finally {
        //最终通知:释放资源
        release();
    }
    return rtValue;
}

切入点表达式注解:@Pointcut

@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1() {}
引用方式:
/**
* 环绕通知
* @param pjp
* @return
*/
@Around("pt1()")// 注意:千万别忘了写括号
public Object transactionAround(ProceedingJoinPoint pjp) {
    //定义返回值
    Object rtValue = null;
    try {
        //获取方法执行所需的参数
        Object[] args = pjp.getArgs();
        //前置通知:开启事务
        beginTransaction();
        //执行方法
        rtValue = pjp.proceed(args);
        //后置通知:提交事务
        commit();
    }catch(Throwable e) {
        //异常通知:回滚事务
        rollback();
        e.printStackTrace();
    }finally {
        //最终通知:释放资源
        release();
    }
    return rtValue;
}

不使用 XML 的配置方式

@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

6.在 spring 配置文件中开启 spring 对注解 AOP 的支持

<!-- 开启 spring 对注解 AOP 的支持 -->
<aop:aspectj-autoproxy/>
posted @ 2020-04-09 13:04  tianqibucuo  阅读(174)  评论(0)    收藏  举报