Spring——Spring核心AOP技术详细说明

1. AOP技术概念

1.1 什么是AOP?

    AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理。

     切面:公共的,通用的,重复的功能称为切面,面向切面编程就是将切面提取出单独开发,在需要调用的方法中通过动态代理的方式进行织入.

     AOP技术就是说:在外部运行方法的同时,实际上切入了通用的事务以及日志等

1.2 AOP技术的作用

     AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 Spring 框架中的一个重要内容。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性,同时提高了开发效率。

     面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—转账。

1.3 面向切面编程的好处

1.减少重复;
2.专注业务;
在这里插入图片描述

2. 使用代理模式模拟实现AOP框架

2.1 静态代理方式实现AOP框架(仅能实现一个功能)

实现样例:
    使用静态代理拆分业务和业务接口,切面和实现购买图书业务,在业务实现时实现事务切入或者日志

购买图书业务接口

public interface Service {

    //实现图书购买业务
    void buy();
}

购买图示功能实现类:

public class BookServiceImpl implements Service {

    //此类是目标对象
    public void buy(){
        System.out.println("图书购买业务功能实现......");
    }
}

切面接口


public interface AOP {

    default void before(){}
    default void after(){}
    default void exception(){}
}

日志实现类

public class LogImpl implements AOP{

    @Override
    public void before() {
        System.out.println("日志开启......");
    }
}

事务实现类:

public class TransImpl implements AOP{

    @Override
    public void before() {
        System.out.println("事务开启......");
    }

    @Override
    public void after() {
        System.out.println("事务提交......");
    }

    @Override
    public void exception() {
        System.out.println("事务回滚......");
    }
}

代理类:

public class Agent implements Service{

    //创建目标以(业务)对象,切面对象
    Service target;
    AOP aop;

    public Agent(Service target, AOP aop) {
        this.target = target;
        this.aop = aop;
    }
    @Override
    public void buy() {
        try {
            //切面
            aop.before();
            //业务
            target.buy();
            //切面
            aop.after();
        } catch (Exception e) {
            aop.exception();
        }
    }
}

测试:
在这里插入图片描述

2.2 动态代理方式实现AOP框架

实现样例:
    使用动态代理优化静态代理方式实现的业务
其他地方一样,唯一不一样的就是: obj = method.invoke(target, args);,可以在测试类中传入到代理工厂不同的实现功能,在静态代理中实现的功能已经在代理类中指定:
在这里插入图片描述
代理工厂类:

public class ProxyFactory {

    //创建动态代理对象
    public static Object getProxy(Service target,AOP aop){
        return Proxy.newProxyInstance(
                //类加载器
                target.getClass().getClassLoader(),
                //目标对象实现的所有接口
                target.getClass().getInterfaces(),
                //代理功能的实现
                new InvocationHandler() {
                    @Override
                    public Object invoke(
                            //代理对象
                            Object proxy,
                            //正在被调用的方法
                            Method method,
                            //目标对象的参数
                            Object[] args) throws Throwable {
                        Object obj = null;

                        try {
                            //切面
                            aop.before();
                            //业务功能实现
                            obj = method.invoke(target, args);
                            //切面
                            aop.after();
                            aop.exception();
                        } catch (IllegalAccessException e) {
                            aop.exception();
                        }
                        return obj;
                    }
                }
        );
    }
}

3. AOP的通知类型以及编程术语

  • 通知类型

    • Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;
    • After通知:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice;
    • Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice;
    • Around通知:拦截对目标对象方法调用,涉及接口为 org.aopalliance.intercept.MethodInterceptor。
  • 编程术语:

    • 切面(Aspect)泛指交叉业务逻辑,或是公共的,通用的业务。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。
    • 连接点(JoinPoint)指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
    • 切入点(Pointcut)指声明的一个或多个连接点的集合。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
    • 目标(target)对象指 将要被增强 的对象。 即包含主业 务逻辑的 类的对象。 上例中 的BookServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然, 不被增强,也就无所谓目标不目标了。
    • 通知(Advice)表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。

4. AspectJ对AOP的实现

    对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便, 而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。
    在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。

4.1 AspectJ简介

    AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。
总的来说就是易学易用,在学之后可以使用注解方式轻松实现切面编程

4.2 AspectJ通知类型

AspectJ 中常用的通知有四种类型:

Ⅰ 前置通知@Before
Ⅱ 后置通知@AfterReturning
Ⅲ 环绕通知@Around
Ⅳ 最终通知@After
Ⅴ 定义切入点@Pointcut(了解)

4.3 切入点表达式[比较重要]

AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
在这里插入图片描述
简单点就是:
    execution(访问权限 方法返回值 方法声明(参数) 异常类型)

    切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

*:表示0至任意多个字符
… 如果出现在方法的参数中,则代表任意参数
如果出现在路径中,则代表本路径及其所有的子路径

示例:

execution(public * (…)) :任意的公共方法
execution(
set*(…)):任何一个以“set”开始的方法
execution(* com.xyz.service.impl..(…)):任意的返回值类型,在com.xyz.service.impl包下的任意类的任意方法的任意参数
execution(* com.xyz.service….(…)):任意的返回值类型 ,在com.xyz.service及其子包下的任意类的任意方法的任意参数

    需要牢记在表达式中可以没有访问权限,和抛出异常,*和…代表的含义

4.4 AspectJ基于注解的方式实现AOP技术

4.4.1 AspectJ的前置通知@Before

    在目标方法执行前切入切面功能.在切面方法中不可以获得目标方法的返回值,只能得到目标方法的签名.

    在本次通知中,将所有的类都给全,包括接口类,目标实现类,以及AspectJ实现切面编程,测试类,在后面的几个通知了类中给出的是核心,其余的一样可以使用(接口以及实现类)
接口类:

public interface SomeService {

    String doSome(String name ,int age);
}

接口实现类(目标业务实现这里仅用一句话带过):

@Service
public class SomeServiceImpl implements SomeService {


    @Override
    public String doSome(String name, int age) {
        System.out.println("doSome业务功能实现......");
        return "abcd";
    }
}

AspectJ框架实现:

@Aspect
@Component
public class MyAspect {


    /**
     * 所有的切面功能都是由切面方法实现的
     * 可以将各种切面都在此类中开发
     *
     * 前置通知的切面方法规范
     * 访问权限是public
     * 方法的返回值是void
     * 方法名称自定义
     * 方法没有参数,如果有也只有JointPoint类型
     * 必须使用@Before注解来声明切入的时机是前切工功能和切入点
     * 参数:value 指定切入点表达式
     *
     * 业务方法:
     * public String doSome(String name, int age)
     */

    @Before(value = "execution(public String com.lcl.s01.SomeServiceImpl.doSome(String,int))")
    public void myBefore(JoinPoint joinPoint){
        //使用JoinPoint可以获取目标方法的签名:权限修饰符 返回值类型 方法声明(参数)
        System.out.println("切面方法中的前置功能实现......");
        System.out.println("目标方法的签名是:"+joinPoint.getSignature());
        System.out.println("目标方法的参数是:"+ Arrays.toString(joinPoint.getArgs()));
    }

}

配置文件:

 <!--基于注解的形式创建对象-->
    <context:component-scan base-package="com.lcl.s01"></context:component-scan>
    <!--实现绑定业务-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

测试:

@Test
    public void testSomeService(){

        ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
//        SomeService someService = (SomeService) ac.getBean("someService");
        /*
        * 换成注解的方式获取代理对象就需要命名为someServiceImpl[驼峰命名法]
        * 使用set注入的方式写入的是id
        * */
        SomeService someService = (SomeService) ac.getBean("someServiceImpl");
        System.out.println(someService);
        String s = someService.doSome("李华", 22);
        System.out.println(s);
    }

在这里插入图片描述

注意:
    在实现类和AspectJ类中首先要使用注解的形式实现IoC,将对象交给容器创建,其次在AspectJ框架实现类中加入@Aspect指明此类是切面

4.4.2 @AfterReturning后置通知

    后置通知是在目标方法执行后切入切面功能,可以得到目标方法的返回值.如果目标方法的返回值是简单类型(8种基本类型+String)则不可改变.如果目标方法的返回值是引用类型可以改变.

@Aspect
@Component
public class MyAspect {
    /**
     * 后置通知的方法的规范
     * 1)访问权限是public
     * 2)方法没有返回值void
     * 3)方法名称自定义
     * 4)方法有参数(也可以没有参数,如果目标方法没有返回值,则可以写无参的方法,但一般会写有参,这样可以处理无参可以处理有参),这个切面方法的参数就是目标方法的返回值
     * 5)使用@AfterReturning注解表明是后置通知
     *   参数:
     *      value:指定切入点表达式
     *      returning:指定目标方法的返回值的名称,则名称必须与切面方法的参数名称一致.
     */
    @AfterReturning(value = "execution(* com.lcl.s02.*.*(..))",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知功能实现..............");
        if(obj != null){
            if(obj instanceof String){
                obj = obj.toString().toUpperCase();
                System.out.println("在切面方法中目标方法的返回值:"+obj);
            }
            if(obj instanceof Student){
                Student stu = (Student) obj;
                stu.setName("李四");
                System.out.println("在切面方法中目标方法的返回值:"+stu);
            }
        }
    }
}

4.4.3 AspectJ框架切换JDK动态代理和CGLib动态代理说明

    <aop:aspectj-autoproxy ></aop:aspectj-autoproxy> ===>默认是JDK动态代理,取时必须使用接口类型
    <aop:aspectj-autoproxy proxy-target-class=“true”></aop:aspectj-autoproxy> ==>设置为CGLib子类代理,可以使用接口和实现类接
    使用接口来接,永远不出错.

4.4.4 环绕通知@Around

    它是通过拦截目标方法的方式 ,在目标方法前后增强功能的通知.它是功能最强大的通知,一般事务使用此通知.它可以轻易的改变目标方法的返回值.

@Aspect
@Component
public class MyAspect {
    /**
     * 环绕通知方法的规范
     * 1)访问权限是public
     * 2)切面方法有返回值,此返回值就是目标方法的返回值
     * 3)方法名称自定义
     * 4)方法有参数,此参数就是目标方法
     * 5)回避异常Throwable
     * 6)使用@Around注解声明是环绕通知
     *   参数:
     *      value:指定切入点表达式
     */

    @Around(value = "execution(* com.lcl.s03.*.*(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        //前切功能实现
        System.out.println("环绕通知中的前置功能实现............");
        //目标方法调用
        Object obj = pjp.proceed(pjp.getArgs());
        //后切功能实现
        System.out.println("环绕通知中的后置功能实现............");
        return obj.toString().toUpperCase();  //改变了目标方法的返回值
    }
}

4.4.5 最终通知@After以及AspectJ总结

  无论目标方法是否正常执行,最终通知的代码都会被执行.

 //最终业务功能实现
    @After(value = "myCut()")
    public void myAfter(){
        System.out.println("最终业务功能实现......");
    }
  • 前置业务功能:可以获取目标方法的签名和方法
  • 后置通知功能:在切入点表达式中不仅有Value,还有returning,返回的要和参数一致(目标方法),.如果目标方法的返回值是简单类型(8种基本类型+String)则不可改变.如果目标方法的返回值是引用类型则可以改变.
  • 环绕业务功能:有参数ProceedingJoinPoint,表示目标方法,可以随意修改方法返回值
  • 最终通知功能:没有参数和返回值,一定会被执行
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

皮皮皮皮皮皮皮卡乒

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

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

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

打赏作者

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

抵扣说明:

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

余额充值