动态代理-AOP

1 什么是AOP?

  • Aspect Oriented Programming的缩写,面向切面编程,切面指定就是动态代理的方法,作用是在不改变业务层方法源代码的基础上对方法进行增强,底层使用的是动态代理技术,面向切面编程也可以理解成面向动态代理编程。

2 AOP相关概念

在这里插入图片描述

  • Target(目标对象):被代理的对象就是目标对象
  • Proxy(代理对象):被增强后的对象就是代理对象
  • Joinpoint(连接点):就是目标对象中所有被拦截到的方法
  • Pointcut(切入点):就是目标对象中被增强的方法
  • Advice(通知):执行目标方法之前或者之后调用的方法就是通知
  • Aspect(切面):通知方法和切入点方法结合所在的位置叫做切面
  • Weaving(织入):通知方法和切入点方法结合的过程,织入之后的结果就是切面

总结一下:
连接点是所有被拦截到的方法,切入点是所有被增强的方法,连接点不一定是切入点,但是切入点一定是连接点。在执行目标对象方法之前或者之后要做的事叫做通知,通知中有增强的业务。将切入点和通知组织到一起叫织入,织入形成的结果就是切面。

3 AOP配置实现步骤

<1>【第一步】导入相关依赖:spring-context、aspectjweaver

<!--spring核心依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.9.RELEASE</version>
</dependency>
<!--切入点表达式依赖,作用:通过表达式找到哪些方法需要增强,也就是找到切入点-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>

<2>【第二步】定义通知类和目标对象

  • AOP目标接口:
public interface StudentService {
    //查询全部
    public abstract List<Student> findAll() throws IOException;

    public void transfer(Integer outId, Integer inId, double money);
}

  • AOP目标实现类:
@Service("studentService")
public class StudentServiceImpl implements StudentService {
    @Override
    public List<Student> findAll() throws IOException {
        System.out.println("查询所有学生信息findAll...");
        return null;
    }
   @Override
    public void transfer(Integer outId,Integer inId,double money){
        //1 张三的账户-1000元
        System.out.println("调用dao:张三("+outId+")的账户"+(-money)+"元");
        //2 李四的账户+1000元
        System.out.println("调用dao:李四("+inId+")的账户"+money+"元");
    }
    //接口中没有该方法,不会被拦截
    public void show(){
        System.out.println("----------------");
    }
}

注:代理的为接口对象,接口中没有的方法,实现类自己的方法不会被增强

  • 通知类:
import org.aspectj.lang.ProceedingJoinPoint;

//通知类,告诉spring在增强的前后需要做什么事
public class Advice {
    public void before(){
        //前置通知:开启事务
        System.out.println("前置通知:开启事务");
    }
    public void afterReturn(){
        //后置通知:提交事务
        System.out.println("后置通知:提交事务");
    }
    public void afterThrowable(){
        //异常通知:回滚事务
        System.out.println("异常通知:回滚事务");
    }
    public void after(){
        //最终通知:释放资源
        System.out.println("最终通知:释放资源");
    }

    // 环绕通知:是Spring给我们提供的一种手动调用目标对象方法或者其他通知方法的方式
    // spring在调用环绕通知方法时会传递一个封装了目标方法的对象,叫做ProceedingJoinPoint
    public Object around(ProceedingJoinPoint pjp){
        Object result =null;
        try {
            //前置通知
            before();
            //执行目标方法,相当于动态代理中的 result=method.invoke(...)
            result = pjp.proceed();
            //后置通知
            afterReturn();
        } catch (Throwable throwable) {
            //异常通知
            afterThrowable();
            throwable.printStackTrace();
        } finally {
            //最终通知
            after();
        }
        return result;
    }
}

<3>xml文件配置AOP

    1 配置目标对象,添加到spring容器中
	2 配置通知对象,添加到spring容器中
	3 配置切入点方法和通知方法织入过程,也就配置切面
  • 纯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: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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--1.配置service-->
    <bean id="studentService" class="com.itheima.service.impl.StudentServiceImpl"/>

    <!--2.配置通知对象-->
    <bean id="myAdvice" class="com.itheima.aop.Advice"/>

    <!--3.配置AOP-->
    <aop:config>
        <!--3.1配置AOP切入点表达式[可放在任意位置]-->
        <!--*空格代表void 方法名
            .*:.StudentServiceImpl
            .*:.方法名
            (..)方法参数, ..代表参数任意
        -->
        <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        <!--<aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.StudentServiceImpl.*(..))"/>-->

        <!--3.2配置切面-->
        <aop:aspect ref="myAdvice">
            <!--前置通知-->
            <aop:before method="before" pointcut-ref="pt"/>
            <!--后置通知-->
            <aop:after-returning method="afterReturn" pointcut-ref="pt"/>
            <!--异常通知-->
            <aop:after-throwing method="afterThrowable" pointcut-ref="pt"/>
            <!--最终通知-->
            <aop:after method="after" pointcut-ref="pt"/>

            <!--使用环绕通知-->
            <!--<aop:around method="around" pointcut-ref="pt"/>-->
        </aop:aspect>
    </aop:config>
</beans>
  • 注解配置:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

//通知类,告诉spring在增强的前后需要做什么事
@Component("advice")
@Aspect//告知是一个切面类,扫描时会扫描它的注解//代替:<aop:aspect ref="advice">
public class Advice {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    //id为方法名[首字母小写]
    public void pt() {
    }

    //==注意:使用注解配置AOP,后置通知和异常通知会在最终通知之后调用,
// 在spring-context的5.1.9版本中是这样的,在更高的版本中可能得到了解决,
// (5.2.6及以上版本解决了)。
// 但是我们可以使用环绕通知解决这个问题,推荐使用环绕通知。==**
  /*  @Before("pt()")
    public void before(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        //前置通知:开启事务
        System.out.println("前置通知:开启事务"+args[0]);
    }*/
//两种得到传递参数的方法 如果目标方法没有传参,则不执行
    @Before("execution(* com.itheima.service.impl.*.*(..))&&args(x)")
    public void before(int x) {
        //前置通知:开启事务
        System.out.println("前置通知:开启事务" + x);
    }

    @AfterReturning("pt()")
    public void afterReturn() {
        //后置通知:提交事务
        System.out.println("后置通知:提交事务");
    }

    @AfterThrowing("pt()")
    public void afterThrowable() {
        //异常通知:回滚事务
        System.out.println("异常通知:回滚事务");
    }

    @After("pt()")
    public void after() {
        //最终通知:释放资源
        System.out.println("最终通知:释放资源");
    }

    // 环绕通知:是Spring给我们提供的一种手动调用目标对象方法或者其他通知方法的方式
    // spring在调用环绕通知方法时会传递一个封装了目标方法的对象,叫做ProceedingJoinPoint
    //@Around("pt()")
    public Object around(ProceedingJoinPoint pjp) {
        Object result = null;
        try {
            //前置通知
            //Object[] args = pjp.getArgs();
            //before();
            //执行目标方法,相当于动态代理中的 result=method.invoke(...)
            result = pjp.proceed();
            //后置通知
            afterReturn();
        } catch (Throwable throwable) {
            //异常通知
            afterThrowable();
            throwable.printStackTrace();
        } finally {
            //最终通知
            after();
        }
        return result;
    }
}

注:
使用注解配置AOP,后置通知和异常通知会在最终通知之后调用,
在spring-context的5.1.9版本中是这样的,在更高的版本中可能得到了解决,
(5.2.6及以上版本解决了)。
但是我们可以使用环绕通知解决这个问题,推荐使用环绕通知

  • 核心配置类代替XML
import org.springframework.context.annotation.*;

@Configuration//表示代表替换applicationContext.xml的标识[可以不写]
@ComponentScan("com.itheima")//开启Spring注解扫描
@EnableAspectJAutoProxy//开启Spring的AOP注解支持
public class SpringConfig {
}


4.底层动态代理类似原理[studentService动态代理工厂]

package com.itheima.proxy;
import com.itheima.aop.Advice;
import com.itheima.service.StudentService;
import com.itheima.service.impl.StudentServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class StudentServiceProxyFactory {
    public static StudentService createStudentServiceProxy() {
        Advice advice = new Advice();
        //1.创建真实对象
        StudentService studentService = new StudentServiceImpl();//可采用set注入
        //2.创建代理对象
        /**
            ClassLoader loader,创建代理对象的class对象
            Class<?>[] interfaces,告诉代理对象要和目标对象实现相同的接口,就具有相同的功能。
            InvocationHandler h,处理增强的逻辑
         */
        ClassLoader classLoader = studentService.getClass().getClassLoader();
        Class<?>[] interfaces = studentService.getClass().getInterfaces();//获取所有的直接实现的接口
        StudentService service = (StudentService) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            /**
             * @param proxy 代理对象
             * @param method 调用代理对象的方法,findAll、findById、transfer、update。。。
             * @param args 调用代理对象方法传递进来的参数们
             * @return 此处的返回值将返回给调用处
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;

                if (method.getName().equals("transfer") || method.getName().equals("delete")) {
                    try {
                        //1.开启事务
                       advice.before();
                        //2.执行操作,调用目标方法
                        result = method.invoke(studentService, args);
                        //3.提交事务
                        advice.afterReturn();
                    } catch (Exception e) {
                        e.printStackTrace();
                        //4.如果有异常则回滚事务
                       advice.afterThrowable();
                    } finally {
                        //5.释放资源
                        advice.after();
                    }
                } else {
                     //执行操作,调用目标方法
                    result = method.invoke(studentService, args);
                }
                return result;
            }
        });
        return service;
    }
}

注:只可对单一实现类对象进行增强

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陪雨岁岁年年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值