【Spring】AOP(面向切面编程),动态代理详解,AspectJ注解

大家好,我是被白菜拱的猪。

一个热爱学习废寝忘食头悬梁锥刺股,痴迷于girl的潇洒从容淡然coding handsome boy。

一、写在前言

二、AOP

(一)概念介绍

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

通俗的讲就是不改变源代码的方式,在主干功能中添加新的功能,比如我们在实现登录功能时想来一个权限判断,是管理员还是普通用户。

(二)底层原理

1、动态代理简单介绍

AOP底层采用的是动态代理,什么是代理,就是我们把要做的事情交给比人去做,比如我们要租房子,我们就是被代理对象,中介就是代理对象。

动态代理是利用反射机制创建代理对象,动态代理分为两种:

  1. 一种是有接口的情况,我们通过jdk代理方式创建接口实现类的代理对象,实现类的增强方法
  2. 一种是没有接口的情况,我们通过CGLIB动态代理来创建子类的代理对象,实现类的增强方法。

为什么要通过反射呢?因为我们是在运行期间创建代理类,静态代理是是编译时创建,不利于程序的扩展,而且每个代理类只能为一个借口服务,这样在程序开发的过程中就会产生很多个代理类,那么我们最好通过一个代理类实现全部的代理功能。这就是动态代理。

2、JDK动态代理代码实现

package com.codingboy.spring5.test;

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

/**
 * @author: ljl
 * @date: 2020/9/4 11:33
 * @description:  动态代理举例
 */

//接口
interface Human{
    void eat(String food);
    //信仰
    String getBelieve();
}

//实现类
class SuperMan implements Human {

    @Override
    public void eat(String food) {
        System.out.println("我喜欢吃" + food);
    }

    @Override
    public String getBelieve() {
        return "good good study,day day up!";
    }
}

/*
要实现动态代理,需要解决的问题?
    问题一: 如何根据加载内存的被代理类,动态的创建代理类及其对象?
    问题二: 当通过代理类调用方法时,如何动态的调用被代理类的同名方法?
*/


class ProxyFactory {
    //调用此方法,返回一个代理类对象
    public static Object getProxyInstance(Object obj) {
        MyInvocationHandler handler = new MyInvocationHandler(obj);
        //使用Proxy.newProxyInstance创建代理类对象,代理类和被代理类实现的是同一个接口
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
    }
}

//解决问题二
class MyInvocationHandler implements InvocationHandler {
    private Object obj; //声明被代理类

    //通过构造方法进行绑定
    public MyInvocationHandler (Object obj) {
        this.obj = obj;
    }

    //当代理类执行方法时,就会执行下面的方法,所以我们可以在里面写代理类执行的方法,这时候就需要声明一个被代理类
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //在这里可以增强写法
        System.out.println("执行方法前,增强......");

        //通过反射执行被代理类中的方法
        Object returnValue = method.invoke(obj,args);

        System.out.println("执行方法后,增强......");

        return returnValue;
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        //创建被代理对象
        SuperMan superMan = new SuperMan();
        //创建代理对象,注意这里是Human类型,接口类型
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //通过代理对象执行被代理对象中的方法
        proxyInstance.eat("北京烤鸭");
    }
}

运行结果:
在这里插入图片描述

(三)操作术语

1、连接点
类中哪些方法可以增强,这些方法称为连接点
2、切入点
具体增强的方法称为切入点
3、通知
增强方法中的逻辑部分称为通知。

通知又分为如下几种:
前置通知、后置通知、环绕通知、异常通知、最终通知

4、切面
切面是一个动作,将通知应用于切入点的过程称为切面

(四)AOP操作(AspectJ注解)

1、前期准备

AspectJ不是Spring的组成部分,他是一个独立的框架,一般把AspectJ和Spring一起使用来实现AOP的操作,假如不使用maven构建工程的话还要引入相关jar包。

除此之外,还要了解什么是切入点表达式,知道对哪个类里面的哪个类方法进行增强。

切入点表达式格式:execution([修饰符] 返回值类型 包名.类名.方法名(参数)),
修饰符默认为public可以不用写,返回值类型必须写*可以匹配所有的返回值类型。

示例一:
对com.coding.spring5中包中所有的类中的所有方法进行增强(…)是参数列表,不管一个参数还是多个参数

execution(  *  com.codingboy.spring5.*.*(..))

2、具体步骤

  1. 创建类和增强的类,并写相应的方法
  2. 进行通知配置
    (1)开启注解扫描
    <context:component-scan base-package="com.codingboy.spring5"></context:component-scan>

(2)使用注解@Component创建对象(即增强类和要增强的类)
(3)在增强类上加上@Aspect注解(注意选择的包是在aspectJ)
(4)在Spring配置文件中开启生成代理对象,让@Aspect生效

    <!--织入切面-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

3.在增强类中配置不同的通知,在方法上使用不同的注解,以及切入点表达式来表明对哪个方法进行增强

package com.codingboy.spring5.aopannotation;

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

/**
 * @author: ljl
 * @date: 2020/9/4 16:35
 * @description: book的增强类
 */
@Aspect
@Component
public class BookProxy {
    //前置通知
    @Before(value = "execution(* com.codingboy.spring5.aopannotation.Book.*(..)")
    public void before() {
        System.out.println("前置通知 before...");
    }
    //后置通知(返回通知)
    @AfterReturning(value = "execution(* com.codingboy.spring5.aopannotation.Book.*(..)")
    public void afterReturning() {
        System.out.println("后置通知(返回通知) afterReturning");
    }
    //环绕通知
    @Around(value = "execution(* com.codingboy.spring5.aopannotation.Book.*(..)")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知前 around...");
        //被增强方法执行
        proceedingJoinPoint.proceed();
        System.out.println("环绕通知后 around...");
    }
    
    //最终通知
    @After(value = "execution(* com.codingboy.spring5.aopannotation.Book.*(..)")
    public void after() {
        System.out.println("最终通知 after...");
    }
    //异常通知
    @AfterThrowing(value = "execution(* com.codingboy.spring5.aopannotation.Book.*(..)")
    public void afterThrowing() {
        System.out.println("异常通知 afterThrowing...");
    }


}

在这里插入图片描述

3、通知执行顺序

这是一个方法只被一个类拦截时通知执行的顺序
正常执行:
在这里插入图片描述
出现异常:
在这里插入图片描述

4、注意事项

  1. 相同的切入点抽取,我们发现我们写的例子切入点表达式都是同一个,那么我们能不能把他抽取出来呢?of course,yes。自定义一个方法,然后使用@Pointcut注解,value指定要增强的方法,然后下面value等于方法名,allright。
    @Pointcut(value = "execution(* com.codingboy.spring5.aopannotation.Book.*(..)")
    public void point() {

    }
    //前置通知
    @Before(value = "point()")
    public void before() {
        System.out.println("前置通知 before...");
    }
  1. 假如不同的增强类对同意方法进行增强,那如何确定这连个增强类的优先级呢?使用@Order注解,里面是数字,数字越小优先级越高。
  2. 假如是完全注解开发,xml文件一丁点都不用,那么如何实现aop呢?我们创建一个配置类,那么如何实现织入切面这个功能呢?使用@EnableAspectJAutoProxy这个注解,相当于前面的
  <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

这句话,下面是配置类的内容

@Configuration
@ComponentScan(basePackages = {"com.codingboy.spring5"})
@EnableAspectJAutoProxy
public class AopConfig {
}

(五)AOP操作(基于AspectJ配置文件)

使用配置文件的形式在实际开发过程中使用的不多,但是还是要有所了解的。这里不再对各个标签进行描述,直接上代码:

<!--创建对象-->
    <bean id="book" class="com.codingboy.spring5.aopannotation.Book"></bean>
    <bean id="bookProxy" class="com.codingboy.spring5.aopannotation.BookProxy"></bean>

    <!--配置aop-->
    <aop:config>
        <!--配置切点-->
        <aop:pointcut id="point" expression="execution(* com.codingboy.spring5.aopannotation.Book.*(..))"/>
        <!--配置切面 ref 指把哪个类当做切面-->
        <aop:aspect ref="bookProxy">
            <!--配置通知,method指把哪个方法作为通知-->
            <aop:after method="before" pointcut-ref="point" ></aop:after>
        </aop:aspect>
    </aop:config>

三、结束语

一天的内容,学了学aop,让我收获最多的是动态代理,看视频看了两遍才搞清楚,学习这条路任重而道远啊,要学的东西是在太多了。以上还仅仅是基础,学完这一篇还要在在了解了解源码,什么时候上SpringBoot这条船还不得而知。

长春的风太大,吹乱了我的秀发…一个人在风中前行,孤独又增添了几分。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值