Spring5学习:AOP(面向切面编程)


前言

跟随尚硅谷学习Spring5
AOP(面向切面编程)

一、AOP(概念和原理)

  • 什么是 AOP
    • 面向切面编程(方面), 利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得
      业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
    • 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
  • AOP(底层原理)
    • AOP 底层使用动态代理
        有接口情况,使用 JDK 动态代理:创建接口实现类代理对象,增强类的方法
        没有接口情况,使用 CGLIB 动态代理:创建子类的代理对象,增强类的方法
  • AOP本质
    • 就是在原先方法的基础上增加新的方法,但是为了不影响之前的方法,使用AOP融合新方法和旧方法,可以做到不影旧的方法

二、AOP(JDK 动态代理)

  • 使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象
    • 调用 newProxyInstance 方法
static object nevProxyInstance(ClassLoader loader,类<?>[] interfaces,InvocationHandler h)
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

  该方法有三个参数:
  第一参数,类加载器
  第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
  第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分

  • 编写 JDK 动态代理代码
     创建接口,定义方法
package com.atguigu.spring5.aop.jdk.dao;

public interface UserDao {
    public int add(int a, int b);
    public void sendMsg(String msg);
}
package com.atguigu.spring5.aop.jdk.dao;

import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao {

    public int add(int a, int b) {
        System.out.println("UserDaoImpl.add()方法执行了");
        return a + b;
    }

    public void sendMsg(String msg) {
        System.out.println("UserDaoImpl.sendMsg()方法执行了,内容为:" + msg);
    }
}

  创建接口实现类,实现方法

package com.atguigu.spring5.aop.jdk.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;

// 创建代理对象代码
class UserDaoProxy implements InvocationHandler {

    // 1、创建谁的代理对象,把谁传递过来;这里为了通用一些,写成Object
    private Object object;

    // 有参数构造传递
    public UserDaoProxy(Object object) {
        this.object = object;
    }

    // 增强的逻辑,需要传入被代理的接口
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 方法之前
        System.out.println("UserDaoProxy.invoke() - 方法之前执行了,方法名为:" + method.getName() + ":传递的参数..." + Arrays.toString(args) + "当前对象:" + object);

        // 被增强的方法执行
        Object res = method.invoke(object, args);

        // 方法之后
        System.out.println("UserDaoProxy.invoke() - 方法之后执行了,方法名为:"  + method.getName() + ":传递的参数..." + Arrays.toString(args) + "当前对象:" + object);

        return res;
    }
}
package com.atguigu.spring5.aop.jdk.proxy;

import com.atguigu.spring5.aop.jdk.dao.UserDao;
import com.atguigu.spring5.aop.jdk.dao.UserDaoImpl;

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

public class JDKProxy {
    public static void main(String[] args) {

        Class[] interfaces = {UserDao.class};
        UserDaoImpl userDaoImpl = new UserDaoImpl();

        // 创建接口实现类的代理对象

        // 写法1, InvocationHandler使用匿名内部类
        /*
        Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return null;
            }
        });
        */

        // 写法2:不使用InvocationHandler匿名内部类;而是使用一个实现InvocationHandler的代理类实例
        // 接口 = 实现类
        /**
         * Proxy.newProxyInstance()方法参数说明:
         * 第一个参数:当前类自身的类加载器;
         * 第二个参数:需要被代理的类所实现的(一个或多个)接口
         * 第三个参数:InvocationHandler接口实现类,写增强的地方,这里需要传入被代理的实现类对象"userDaoImpl"
         */
        UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDaoImpl));

        System.out.println("\n---------- 开始测试第一个方法:有返回值 ----------");
        int result = dao.add(1, 2);
        System.out.println("返回值为:" + result);

        System.out.println("\n---------- 开始测试第二个方法:没有返回值 ----------");
        dao.sendMsg("你好啊");
    }
}

三、AOP(术语)

  • 连接点
    • 类里面哪些方法可以被增强,这些方法称为连接点
  • 切入点
    • 实际被真正增强的方法,称为切入点
  • 通知(增强)
    • 实际增强的逻辑部分称为通知(增强)
    • 通知有多钟类型
      前置通知@Before
      后置通知@After
      环绕通知@Around
      异常通知@AfterThrowing
      最终通知@AfterReturning
  • 切面
    • 把通知应用到切入点过程

四、AOP 操作(准备工作)

  • Spring 框架一般都是基于 AspectJ 实现 AOP 操作
    • AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使
      用,进行 AOP 操作
  • 基于 AspectJ 实现 AOP 操作
    • 基于 xml 配置文件实现
    • 基于注解方式实现(通常使用)
  • 在项目工程里面引入 AOP 相关依赖
  • 切入点表达式
    • 切入点表达式作用:知道对哪个类里面的哪个方法进行增强
    • 语法结构: execution( [权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))
举例 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.*.* (..))

五、AOP 操作(AspectJ 注解)

  • 创建类,在类里面定义方法
package com.atguigu.spring5.aop.annotation;

import org.springframework.stereotype.Component;

//需要被增强的类
@Component
public class User {
    public void add() {
//        int i = 10/0; // 这里用于测试抛出异常情况
        System.out.println("User.add()...");
    }
}
  • 创建增强类(编写增强逻辑)
//增强的类
public class UserProxy {
	public void before() {//前置通知
		System.out.println("before.. . ...");
	}
}
  • 进行通知的配置
     (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 https://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 完全注解写法可以消除该配置文件,写法如下
        1、创建一个配置类在类里面添加如下注解
            @Configuration
            @ComponentScan(basePackages = {"com.atguigu"})
            @EnableAspectJAutoProxy(proxyTargetClass = true)
            public class SpringConfig {
            }
    -->

    <!--开启组件扫面 - 等同于 @ComponentScan(basePackages = {"com.atguigu"})-->
    <context:component-scan base-package="com.atguigu.spring5.aop.annotation"/>
    
</beans>

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

@Component
public class User {
//增强的类
@Component
public class UserProxy {

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

//增强的类
@Component
@Aspect // 生成代理对象
public class UserProxy {

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

<!--开启Aspect生成代理对象 - 等同于 @EnableAspectJAutoProxy(proxyTargetClass = true)-->
    <aop:aspectj-autoproxy/>
  • 配置不同类型的通知
     (1)在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//增强的类
@Component
@Aspect // 生成代理对象
public class UserProxy {

    //环绕通知 - 之前之后都执行
    @Around(value = "execution(* com.atguigu.spring5.aop.annotation.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("UserProxy.around()...环绕之前");
        System.out.println("UserProxy.around()...环绕之后");
    }

    //前置通知
    @Before(value = "execution(* com.atguigu.spring5.aop.annotation.User.add(..))")
    public void before() {
		System.out.println("UserProxy.before()...前置通知");
    }

    //后置通知(最终通知)
    @After(value = "execution(* com.atguigu.spring5.aop.annotation.User.add(..))")
    public void after() {
		System.out.println("UserProxy.after()...后置通知(最终通知)");
    }

    //后置返回通知(返回结果执行)
    @AfterReturning(value = "execution(* com.atguigu.spring5.aop.annotation.User.add(..))")
    public void afterReturning() {
		 System.out.println("UserProxy.afterReturning()...后置返回通知(返回结果执行)");
    }

    //异常通知
    @AfterThrowing(value = "execution(* com.atguigu.spring5.aop.annotation.User.add(..))")
    public void afterThrowing() {
		System.out.println("UserProxy.afterThrowing()...异常通知");
    }
}

  (2)总结
    @AfterReturning方法之后通知,在返回结果之后执行,有异常不执行
    @AfterThrowing有异常才通知
    @After方法之后通知,直接在方法后执行,有异常也执行
    @Before方法之前通知
    @Around方法之前和之后都通知

  • 相同的切入点抽取
//增强的类
@Component
@Aspect // 生成代理对象
public class UserProxy {

    @Pointcut(value = "execution(* com.atguigu.spring5.aop.annotation.User.add(..))")
    public void pointcutAddMethod() {
    }

    //环绕通知 - 之前之后都执行
    @Around(value = "pointcutAddMethod()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("UserProxy.around()...环绕之前");
        System.out.println("UserProxy.around()...环绕之后");
    }
}
  • 有多个增强类多同一个方法进行增强,设置增强类优先级
     在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
@Component
@Aspect // 生成代理对象
@Order(1) // 在增强类上添加注解@Order(数字类型值),数字类型值越小优先级越高
public class UserProxy {
@Component
@Aspect // 生成代理对象
@Order(2) // 在增强类上添加注解@Order(数字类型值),数字类型值越小优先级越高
public class UserProxy2 {
//增强的类
@Component
@Aspect // 生成代理对象
@Order(3) // 在增强类上添加注解@Order(数字类型值),数字类型值越小优先级越高
public class UserProxy3 {
  • 完全使用注解开发
     创建配置类,不需要创建 xml 配置文件
@Configuration
@ComponentScan(basePackages = {" com. atguigu"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
	public class ConfigAop {
	
}

  以上几项操作测试:

package com.atguigu.sprint5;

import com.atguigu.spring5.aop.annotation.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAop {

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

六、AOP 操作(AspectJ 配置文件)

  创建两个类,增强类和被增强类,创建方法

package com.atguigu.spring5.aop.xml;

//需要被增强的类
public class User {
    public void add() {
        System.out.println("User.add()...");
    }
}

package com.atguigu.spring5.aop.xml;

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

  在 spring 配置文件中创建两个类对象
  在 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 https://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">


    <!-- 1. 创建两个类的对象 -->
    <bean id="user" class="com.atguigu.spring5.aop.xml.User"></bean>
    <bean id="userProxy" class="com.atguigu.spring5.aop.xml.UserProxy"></bean>

    <!-- 2. 配置aop增强-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pointcutAddMethod" expression="execution(* com.atguigu.spring5.aop.xml.User.add(..))"/>

        <!--配置切面: ref引用代理类-->
        <aop:aspect ref="userProxy">
            <!--配置增强作用在具体的方法上,这里是UserProxy类上的before方法作用在切入点add()方法上-->
            <aop:before method="before" pointcut-ref="pointcutAddMethod"/>
        </aop:aspect>
    </aop:config>

</beans>

  测试:

package com.atguigu.sprint5;

import com.atguigu.spring5.aop.xml.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAop {

    @Test
    public void testAopXML() {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
        User user = context.getBean("user", User.class);
        user.add();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值