spring AOP

目录

1、什么是 AOP

2、AOP术语

3、AOP底层原理(动态代理)

1、AOP 底层使用动态代理

1.有接口(Dao接口的实现类)情况,使用 JDK 动态代理

2.没有接口(Dao接口的实现类)情况,使用 CGLIB 动态代理

4、AOP实现

1、AspectJ

2、在spring项目工程里面引入 AOP 相关依赖

3、切入点表达式

4、基于 AspectJ 实现 AOP 操作

(1)基于注解方式实现(使用)

(2)基于 xml 配置文件实现


1、什么是 AOP

(1)AOP的全称是Aspect-Oriented Programming,即面向切面(方面)编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。是面向对象编程(OOP)的一种补充。

(2)思想:AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。

在AOP思想中,类与切面的关系如图1所示。

图1 类与切面的关系

从图1可以看出,通过Aspect(切面)分别在Class1和Class2的方法中加入了事务、日志、权限和异常等功能。

(3)通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
(4)使用登录例子说明 AOP

2、AOP术语

AOP的专业术语包括Aspect、Joinpoint、Pointcut、Advice、Target Object、Proxy和Weaving,对于这些专业术语的解释,具体如下:

● Aspect(切面):在实际应用中,切面通常是指封装的用于横向插入系统功能(如事务、日志等)的类,如图3-1中的Aspect。该类要被Spring容器识别为切面,需要在配置文件中通过<bean>元素指定。

● Joinpoint(连接点):类里面可以在其前面或者后面增加功能的方法称为连接点,即可以被增强的方法。

在程序执行过程中的某个阶段点,它实际上是对象的一个操作,例如方法的调用或异常的抛出。在Spring AOP中,连接点就是指方法的调用。

● Pointcut(切入点):实际应用中被增强的方法(按照需要已经在其前面后者后面添加功能)

是指切面与程序流程的交叉点,即那些需要处理的连接点,如图1所示。通常在程序中,切入点指的是类或者方法名,如某个通知要应用到所有以add开头的方法中,那么所有满足这一规则的方法都是切入点。

图1 切面、连接点和切入点

● Advice(通知/增强处理):在连接点上增加的用于增强功能的代码

AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法,它是切面的具体实现。

    5种通知类型:

    前置通知(Before):在目标方法被调用之前调用通知功能
    后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
    返回通知(After-returning):在目标方法成功执行之后调用通知
    异常通知(After-throwing):在目标方法抛出异常后调用通知
    环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和之后执行自定义的行为

后置通知和返回通知的区别是,后置通知是不管方法是否有异常,都会执行该通知;而返回通知是方法正常结束时才会执行。

● Target Object(目标对象):是指所有被通知的对象,也被称为被增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理对象。

● Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。

● Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

3、AOP底层原理(动态代理)

1、AOP 底层使用动态代理

有两种情况动态代理

1.有接口(Dao接口的实现类)情况,使用 JDK 动态代理

⚫ 创建接口实现类代理对象,增强类的方法

(1)创建Dao层

    public interface UserDao {
        public void addUser();
        public void deleteUser();
    }

实现

// 目标类
public class UserDaoImpl implements UserDao {
    public void addUser() {
        System.out.println("添加用户");
    }

    public void deleteUser() {
        System.out.println("删除用户");
    }
}

(2)创建一个aspect切面包,创建切面类包含需要增强的的方法(即增加的功能)

//切面类:可以存在多个通知Advice(即增强的方法)
public class MyAspect {
    public void check_Permissions() {
        System.out.println("模拟检查权限...");
    }

    public void log() {
        System.out.println("模拟记录日志...");
    }
}

(3)创建一个jdk包存放代理类 ,实现InvocationHandler接口,并编写代理方法。在代理方法中,需要通过Proxy类实现动态代理。

/**
 * JDK代理类
 */
public class JdkProxy implements InvocationHandler {
    // 声明目标类接口
    private UserDao userDao;

    // 创建代理方法
    public Object createProxy(UserDao userDao) {
        this.userDao = userDao;
        // 1.类加载器
        ClassLoader classLoader = JdkProxy.class.getClassLoader();
        // 2.被代理对象实现的所有接口
        Class[] clazz = userDao.getClass().getInterfaces();
        // 3.使用代理类,进行增强,返回的是代理后的对象
        return Proxy.newProxyInstance(classLoader, clazz, this);
    }

    /*
     * 所有动态代理类的方法调用,都会交由invoke()方法去处理
     * proxy 被代理后的对象
     * method 将要被执行的方法信息(反射)
     * args 执行方法时需要的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 声明切面
        MyAspect myAspect = new MyAspect();
        // 前增强
        myAspect.check_Permissions();
        // 在目标类上调用方法,并传入参数
        Object obj = method.invoke(userDao, args);
        // 后增强
        myAspect.log();
        return obj;
    }
}

JdkProxy类实现了InvocationHandler接口,并实现了接口中的invoke()方法所有动态代理类所调用的方法都会交由该方法处理。在创建的代理方法createProxy()中,使用了Proxy类的newProxyInstance()方法来创建代理对象。newProxyInstance()方法中包含三个参数,其中第1个参数是当前类的类加载器,第2个参数表示的是被代理对象实现的所有接口,第3个参数this代表的就是代理类JdkProxy本身。在invoke()方法中,目标类方法执行的前后,会分别执行切面类中的check_Permissions()方法和log()方法。 

(4)测试:

    public static void main(String[] args) {
        // 创建代理对象
        JdkProxy jdkProxy = new JdkProxy();
        // 创建目标对象
        UserDao userDao = new UserDaoImpl();
        // 从代理对象中获取增强后的目标对象
        UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao);
        // 执行方法
        userDao1.addUser();
        userDao1.deleteUser();
    }

2.没有接口(Dao接口的实现类)情况,使用 CGLIB 动态代理

CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。

⚫ 创建子类的代理对象,增强类的方法 

 (1)创建Dao层,无实现类

//目标类
public class UserDao {
    public void addUser() {
        System.out.println("添加用户");
    }

    public void deleteUser() {
        System.out.println("删除用户");
    }
}

(2)创建切面如jdk方式

(3)创建一个cglib包存放代理类CglibProxy,该代理类需要实现MethodInterceptor接口,并实现接口中的intercept()方法。

// 代理类
public class CglibProxy implements MethodInterceptor {
    // 代理方法
    public Object createProxy(Object target) {
        // 创建一个动态类对象
        Enhancer enhancer = new Enhancer();
        // 确定需要增强的类,设置其父类
        enhancer.setSuperclass(target.getClass());
        // 添加回调函数
        enhancer.setCallback(this);
        // 返回创建的代理类
        return enhancer.create();
    }

    /**
     * proxy CGlib根据指定父类生成的代理对象
     * method 拦截的方法
     * args 拦截方法的参数数组
     * methodProxy 方法的代理对象,用于执行父类的方法
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable {
        // 创建切面类对象
        MyAspect myAspect = new MyAspect();
        // 前增强
        myAspect.check_Permissions();
        // 目标方法执行
        Object obj = methodProxy.invokeSuper(proxy, args);
        // 后增强
        myAspect.log();
        return obj;
    }
}

首先创建了一个动态类对象Enhancer,它是CGLIB的核心类;然后调用了Enhancer类的setSuperclass()方法来确定目标对象;接下来调用了setCallback()方法添加回调函数,其中的this代表的就是代理类CglibProxy本身;最后通过return语句将创建的代理类对象返回。intercept()方法会在程序执行目标方法时被调用,方法运行时将会执行切面类中的增强方法。

(4)测试

    public static void main(String[] args) {
        // 创建代理对象
        CglibProxy cglibProxy = new CglibProxy();
        // 创建目标对象
        UserDao userDao = new UserDao();
        // 获取增强后的目标对象
        UserDao userDao1 = (UserDao) cglibProxy.createProxy(userDao);
        // 执行方法
        userDao1.addUser();
        userDao1.deleteUser();
    }

4、AOP实现

1、AspectJ

AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使
用,进行 AOP 操作

2、在spring项目工程里面引入 AOP 相关依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.19</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.19</version>
</dependency>

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9.1</version>
</dependency>

3、切入点表达式

(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强
(2)语法结构: execution(返回值 包名.包名.包名...类名.方法名(方法参数))

    • 说明:包名有几个是根据自己的类所有在的包结构决定
    • 全匹配写法
      • public void cn.ideal.service.impl.AccountServiceImpl.addAccount()
    • 访问修饰符,如 public 可以省略,返回值可以使用通配符,表示任意返回值
      • void cn.ideal.service.impl.AccountServiceImpl.addAccount()
    • 包名可以使用通配符,表示任意包,有几级包,就需要写几个*.
      • * *.*.*.*.AccountServiceImpl.addAccount()
    • 包名可以使用..表示当前包及其子包
      • cn..*.addAccount()
    • 类名和方法名都可以使用*来实现通配,下面表示全通配
      • * *..*.*(..)
  • 方法参数
    • 可以直接写数据类型:例如 int
    • 引用类型写包名.类名的方式 java.lang.String
    • 可以使用通配符表示任意类型,但是必须有参数
    • 可以使用..表示有无参数均可,有参数可以是任意类型

举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
execution(* com.atguigu.dao.BookDao.add(..))
 
举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
execution(* com.atguigu.dao.BookDao.* (..))
 
举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
execution(* com.atguigu.dao.*.* (..))

4、基于 AspectJ 实现 AOP 操作

(1)基于注解方式实现(使用)

1.创建类,定义方法

public class User { 
 public void add() { 
 System.out.println("add......."); 
 } 
}

2、创建增强类(编写增强逻辑)

在增强类里面,创建方法,让不同方法代表不同通知类型

//增强的类 
public class UserProxy { 
 public void before() {//前置通知 
 System.out.println("before......"); 
 } 
}

3、进行通知的配置

(1)在 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:context="http://www.springframework.org/schema/context"
       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/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 开启注解扫描 -->
    <context:component-scan base-package="com"/>
</beans>

(2)使用注解创建 User 和 UserProxy 对象

(3)在增强类上面添加注解 @Aspect

//增强的类
@Component
@Aspect
public class UserProxy {
    public void before() {//前置通知
        System.out.println("before......");
    }
}

导入依赖后直接使用 @Aspect注解可能会报错找不到依赖,是因为    <scope>runtime</scope>属性一般是运行和测试环境使用,编译时候不用加入classpath,打包时候会打包到目标包中。将其去除即可

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9.1</version>
    <scope>runtime</scope>
</dependency>

(4)在 spring 配置文件中开启生成代理对象


    <!-- 开启 Aspect 生成代理对象-->
    <aop:aspectj-autoproxy/>

4、配置不同类型的通知

在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置

//增强的类
@Component
@Aspect
public class UserProxy {
    //前置通知
    @Before(value = "execution(* com.pojo.User.add(..))")
    public void before() {
        System.out.println("before......");
    }
    //后置通知(返回通知)
    @AfterReturning(value = "execution(* com.pojo.User.add(..))")
    public void afterReturning() {
        System.out.println("afterReturning.........");
    }
    //最终通知
    @After(value = "execution(* com.pojo.User.add(..))")
    public void after() {
        System.out.println("after.........");
    }
    //异常通知
    @AfterThrowing(value = "execution(* com.pojo.User.add(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing.........");
    }
    //环绕通知
    @Around(value = "execution(* com.pojo.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws
            Throwable {
        System.out.println("环绕之前.........");
        //被增强的方法执行
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后.........");
    }
}

5、测试

    @Test
    public void testAOP(){
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
        User user=context.getBean("user", User.class);
        user.add();
    }

 

 6、相同的切入点抽取

每次都写切入点路径非常麻烦,而且不易改动,将切入点抽取出来封装成一个方法,然后每个通知直接调用,简单易修改。结果同上。

//增强的类
@Component
@Aspect
public class UserProxy {
    //相同切入点抽取
    @Pointcut(value = "execution(* com.pojo.User.add(..))")
    public void pointdemo() {
    }

    //前置通知
    @Before(value = "pointdemo()")
    public void before() {
        System.out.println("before......");
    }
    //后置通知(返回通知)
    @AfterReturning(value = "pointdemo()")
    public void afterReturning() {
        System.out.println("afterReturning.........");
    }
    //最终通知
    @After(value = "pointdemo()")
    public void after() {
        System.out.println("after.........");
    }
    //异常通知
    @AfterThrowing(value = "pointdemo()")
    public void afterThrowing() {
        System.out.println("afterThrowing.........");
    }
    //环绕通知
    @Around(value = "pointdemo()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws
            Throwable {
        System.out.println("环绕之前.........");
        //被增强的方法执行
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后.........");
    }
}

7、有多个增强类多同一个方法进行增强,设置增强类优先级

创建一个PersonProxy也对add方法做增强,现在就有两个对add做增(默认优先级使用类名的ASCLL),在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高(从0开始)UserProxy设为0,PersonProxy设为1

//增强的类
@Component
@Aspect
@Order(1)
public class PersonProxy {
    //相同切入点抽取
    @Pointcut(value = "execution(* com.pojo.User.add(..))")
    public void pointdemo() {
    }

    //前置通知
    @Before(value = "pointdemo()")
    public void before() {
        System.out.println("Person before......");
    }
}

 

 7、完全使用注解开发

创建配置类,不需要创建 xml 配置文件

@Configuration //声明配置类
@ComponentScan(basePackages = {"com"}) //开启组件(注解)扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) //开启Aspect生成代理对象
public class ConfigAop { 
} 

(2)基于 xml 配置文件实现

1、创建两个类,增强类和被增强类,创建方法如上

2、在 spring 配置文件中创建两个类对象

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

3、在 spring 配置文件中配置切入点

<!--配置 aop 增强--> 
<aop:config> 
 <!--切入点--> 
 <aop:pointcut id="p" expression="execution(* 
com.atguigu.spring5.aopxml.Book.buy(..))"/> 
 <!--配置切面--> 
 <aop:aspect ref="bookProxy"> 
 <!--增强作用在具体的方法上--> 
 <aop:before method="before" pointcut-ref="p"/> 
 </aop:aspect> 
</aop:config> 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值