欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring AOP的相关内容

程序员文章站 2022-03-02 15:40:13
...

一、AOP?

1、AOP:面向切面编程,扩展功能不修改源代码实现。
2、什么是面向切面编程

将系统逻辑定义为切面,使得业务逻辑中不需要关注系统逻辑的实现,由切面来负责系统逻辑的具体实现。

面向切面编程往往被定义为促使软件系统实现关注点的分离技术。系统由许多不同的组件组成,每一个组件各负责一块特定功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。诸如日志、事务管理和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中去,这些系统服务通常被称为横切关注点,因为它们会跨越系统的多个组件。
如果将这些关注点分散到多个组件中去,你的代码将会带来双重的复杂性。

  • 实现系统关注点功能的代码将会重复出现在多个组件中。
  • 组件会因为那些与自身核心业务无关的代码而变得混乱。

二、Spring AOP术语

通知(Advice)

通知定义了切面是什么及何时使用。除了描述切面要完成的工作(日志记录),通知还决定了何时执行。
1、前置通知(Befor):目标方法调用前调用通知;
2、后置通知(After):目标方法调用后调用通知;
3、返回通知(After-returning):目标方法返回成功后调用通知;
4、异常通知(After-throwing):目标方法抛出异常后调用通知;
5、环绕通知(Around):目标方法调用前与调用后都会调用通知;

连接点(Join point)

连接点是指在应用执行过程中能够插入切面的一个具体点(调用方法时),就是类里面可以被增强的方法。

切点(Poincut)

切点定义了何处使用通知。定义匹配通知所要织入的一个或多个连接点。通常使用明确的类和方法名,或是利用正则表达式匹配。

切面(Aspect)

切面就是通知和切点的结合。

织入(Weaving)

把切面应用到目标对象,并创建新的代理对象的过程。

三、Spring的aop操作

A:aspectj实现aop的方式

1、在spring里面进行aop操作,使用aspectj实现

(1)aspectj不是spring一部分,和spring一起使用进行aop操作
(2)Spring2.0以后新增了对AspectJ支持

2、使用aspectj实现aop有两种方式

(1)基于aspectj的xml配置
(2)基于aspectj的注解方式

3、使用表达式配置切入点

(1)切入点:实际增强的方法
(2)常用的表达式
execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
eg1:execution(* cn.itcast.aop.Book.add(..))
eg2:execution(* cn.itcast.aop.Book.*(..))
eg3:execution(* *.*(..))
eg4:匹配所有save开头的方法 execution(* save*(..))

4、Aop操作准备

(1)除了导入基本的jar包之外,还需要导入aop相关的jar包
Spring AOP的相关内容
(2)创建spring核心配置文件,导入aop的约束
Spring AOP的相关内容

5、使用aspectj实现aop

方式一:基于aspectj的注解方式
通知配置:

@Pointcut:切点配置
@Befor:前置拦截器注解
@After:后置拦截器注解
@AfterThrowing:后置异常拦截器注解
@AfterReturning:后置正常拦截器注解
@Around:环绕拦截器注解

运用举例:
springConfig.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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context  
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
    <!-- 扫描包路径实例化bean -->
    <context:component-scan base-package="com.prosay.aop.aspectj"/>
</beans>

基本类:

public interface ComputerSystem {

    public void run(Person name);
}
@Component
public class Windows10 implements ComputerSystem {

    public void run(Person name) {
        System.out.println("ComputerSystem,arg:"+name.getName());
        System.out.println("启动了windows10系统");
        System.out.println("启动了windows10系统");
    }

}
public class Person {

    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
public interface ComputerInterface {

    public void work(Person name);
}
@Component
public class Computer implements ComputerInterface {

    @Autowired
    private ComputerSystem system;

    //业务逻辑
    //连接点
    public void work(Person name){
        this.system.run(name);
    }
}
public class Client {

    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("/com/prosay/aop/aspectj/springConfig.xml");
        ComputerInterface computer = (ComputerInterface) context.getBean("computer");
        Person obj = new Person();
        obj.setName("张三");
        obj.setAge(15);
        computer.work(obj);
    }
}

关键的aop类,AopAdvice是通过注解实现aop的:

/**
 * 
 * @author Kevin老师
 * @createTime 2018年5月23日 下午9:37:49
 * @Aspect :标识这是一个配置切面的类
 * @EnableAspectJAutoProxy : 开启切面自动代理
 */
@Aspect
@Component
@EnableAspectJAutoProxy
public class AopAdvice {
@Pointcut("execution(** com.prosay.aop.aspectj.Computer.work(..)) && args(arg)")
        public void aopDemo(Person arg){}

        @Before("aopDemo(arg)")
        public void aopMethodBefor(Person arg){
            System.out.println("After,arg:"+arg.getName());
            System.out.println("Befor the system work, do something.");
        }

        @After("aopDemo(arg)")
        public void aopMethodAfter(Person arg){
            System.out.println("After,arg:"+arg.getName());
            System.out.println("After the system work, do something.");
        }

        @Around("aopDemo(arg)")
        public void aroundMethod(ProceedingJoinPoint proc,Person arg){
            System.out.println("arg:"+arg.getName());
            try {
                proc.proceed();
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
}

方式二:基于aspectj的xml配置
通知配置:

<aop:pointcut />: 配置一个切点
<aop:befor />: 配置前置拦截器
<aop:after />: 配置后置拦截器
<aop:after-throwing />: 配置后置异常拦截器
<aop:after-returning />: 配置后置正常拦截器
<aop:around />: 配置环绕拦截器
Object[] args1= jp.getArgs();
Object[] args2= proc.getArgs();
ProceedingJoinPoint.proceed(): 执行被代理的具体方法
JoinPoint.getSignature().getName(): 被代理的方法名
JoinPoint.getArgs(): 被代理方法的参数数组
JoinPoint.getTarget: 具体被代理的对象

运用举例:
其他代码可以不用改变,需要改变的只有:springConfig.xml、和aop类AopAdvice;
springConfig.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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context  
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">

    <bean id="windows10" class="com.prosay.aop.pojo.Windows10"/>
    <bean id="computer" class="com.prosay.aop.pojo.Computer">
        <!-- 构造函数注入 -->
        <constructor-arg ref="windows10"></constructor-arg> 
    </bean>

    <bean id="beforAdvice" class="com.prosay.aop.pojo.AopAdvice"></bean>

    <aop:config>
        <aop:aspect ref="beforAdvice">
            <aop:pointcut id="myPointcut" expression="execution(** com.prosay.aop.pojo.Computer.work(..))" />
            <!-- <aop:before method="beforAdvice" pointcut-ref="myPointcut"/>
            <aop:after method="afterAdvice" pointcut-ref="myPointcut"/> -->
            <aop:around method="aroundAdvice" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

AopAdvice:

public class AopAdvice {

    public void beforAdvice(JoinPoint point){
        System.out.println("在启动电脑前,做一些事情....");
//      point.getSignature().getName() //获取被代理的方法名称
//      point.getArgs();
//      point.getTarget();
    }

    public void afterAdvice(){
        System.out.println("在启动电脑后,做一些事情....");
    }

    public void aroundAdvice(ProceedingJoinPoint proc){
        try {
            beforAdvice(proc);
            Object returnValue = proc.proceed();
            afterAdvice();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

补充:这两种方式可以混合使用。

6、切面参数获取

ProceedingJoinPoint.proceed(): 执行被代理的具体方法
JoinPoint.getSignature().getName(): 被代理的方法名
JoinPoint.getArgs(): 被代理方法的参数数组
JoinPoint.getTarget: 具体被代理的对象
单参数:
上面基于aspectj的注解方式的例子就是单参数的例子,上例输出结果:

arg:张三
After,arg:张三
Befor the system work, do something.
ComputerSystem,arg:张三
启动了windows10系统
After,arg:张三
After the system work, do something.

多参数
参数的传递
1. 表达式中需要加入参数
2. 利用argNames属性,声明参数
3. 对于符合execution表达式,但不符合参数类型的方法,不会被织入切面
还是用上面的例子,把参数改成两个

参数的修改
只有在环绕通知中可以修改参数.
1.获得参数的数组:Object[] args = pjp.getArgs();
2.修改参数的数组元素:args[i]=XXX;
3.执行目标对象方法时候,传入新数组;Object ret = pjp.proceed(args);

public interface ComputerInterface {

//  public void work(Person name);
    public void work(Person name,String str);
}
@Component
public class Computer implements ComputerInterface {

    @Autowired
    private ComputerSystem system;

    //业务逻辑
    //连接点
    public void work(Person name,String str){
        this.system.run(name,str);
    }
//  public void work(Person name){
//      this.system.run(name);
//  }

}
public interface ComputerSystem {

//  public void run(Person name);
    public void run(Person name,String str);
}
@Component
public class Windows10 implements ComputerSystem {

    public void run(Person name,String str) {

        System.out.println("ComputerSystem,arg:"+name.getName());
        System.out.println("ComputerSystem,str:"+str);
        System.out.println("启动了windows10系统");
    }
//  public void run(Person name) {
//      
//      System.out.println("ComputerSystem,arg:"+name.getName());
////        System.out.println("ComputerSystem,str:"+str);
//      System.out.println("启动了windows10系统");
//  }

}
public class Client {

    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("/com/prosay/aop/aspectj/springConfig.xml");
        ComputerInterface computer = (ComputerInterface) context.getBean("computer");
        Person obj = new Person();
        obj.setName("张三");
        obj.setAge(15);
//      computer.work(obj);
        computer.work(obj,"test");
    }
}
@Aspect
@Component
@EnableAspectJAutoProxy
public class AopAdvice {

     @Pointcut(value="execution(** com.prosay.aop.aspectj.Computer.work(..)) && args(arg,str)",argNames="arg,str")
        public void aopDemo(Person arg,String str){}

//      @Before("aopDemo()")
        public void aopMethodBefor(){
            System.out.println("Befor the system work, do something.");
        }

        @After(value="aopDemo(arg,str)",argNames="arg,str")
        public void aopMethodAfter(Person arg,String str){
            System.out.println("After,arg:"+arg.getName());
            System.out.println("After,str:"+str);
            System.out.println("After the system work, do something.");
        }

        @Around(value="aopDemo(arg,str)",argNames="arg,str")
        public void aroundMethod(ProceedingJoinPoint proc,Person arg,String str){
            System.out.println("arg:"+arg.getName());
            //修改参数
            arg.setName("叫爸爸");
            aopMethodBefor();
            try {
//              proc.proceed();
                Object[] args = proc.getArgs();
                args[0]=arg;
                args[1]="hahah";
               proc.proceed(args);
            } catch (Throwable e) {
                e.printStackTrace();
            }
//          aopMethodAfter();

        }

//      @Pointcut("execution(** com.prosay.aop.aspectj.Computer.work(..)) && args(arg)")
//      public void aopDemo(Person arg){}
//
//      @Before("aopDemo(arg)")
//      public void aopMethodBefor(Person arg){
//          System.out.println("After,arg:"+arg.getName());
//          System.out.println("Befor the system work, do something.");
//      }
//
//      @After("aopDemo(arg)")
//      public void aopMethodAfter(Person arg){
//          System.out.println("After,arg:"+arg.getName());
//          System.out.println("After the system work, do something.");
//      }
//
//      @Around("aopDemo(arg)")
//      public void aroundMethod(ProceedingJoinPoint proc,Person arg){
//          System.out.println("arg:"+arg.getName());
//          try {
//              proc.proceed();
//          } catch (Throwable e) {
//              e.printStackTrace();
//          }
//      }
}

运行结果

arg:张三
Befor the system work, do something.
ComputerSystem,arg:叫爸爸
ComputerSystem,str:hahah
启动了windows10系统
After,arg:叫爸爸
After,str:test
After the system work, do something.

注意:环绕对所有符合表达式的方法,都会进行织入切面即没有形参的方法,且符合表达式,也会被织入切面 而加入形参pjp.proceed(args);会报错.

B:基于代理的经典Spring AOP

这种模式的AOP通知类要继承接口,有三个接口,可以根据需要选择继承
MethodBeforAdvice:前置拦截器
befor(Method method, Object[] args, Object target) throws Throwable {
method: 被代理的方法名
args: 被代理方法的参数数组
target: 具体被代理的对象
}
AfterReturningAdvice:后置拦截器
afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
returnValue: 被代理方法的返回值
method: 被代理的方法名
args: 被代理方法的参数数组
target: 具体被代理的对象
}
MethodInterceptor:环绕拦截器拦
invoke(MethodInvacation invacation) throws Throwable {
invacation.proceed(): 执行被代理的具体方法
invacation.getMethod(): 被代理的方法名
invacation.getArguments(): 被代理方法的参数数组
invacation.getThis(): 具体被代理的对象
}

springConfig.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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context  
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <bean id="windows10" class="com.prosay.aop.spring.Windows10"/>
    <bean id="computer" class="com.prosay.aop.spring.Computer">
        <!-- 构造函数注入 -->
        <constructor-arg ref="windows10"></constructor-arg> 
    </bean>

    <bean id="beforAdvice" class="com.prosay.aop.spring.AopAdvice"></bean>
    <!-- 切点 -->
    <bean id="computerProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interfaces">
            <!-- 被代理的对象所实现接口 -->
            <value>com.prosay.aop.spring.ComputerInterface</value>
        </property>
        <!-- 具体被代理的对象/实例 -->
        <property name="target" ref="computer"></property>
        <property name="interceptorNames">
            <!-- 配置通知 -->
            <list>
                <value>beforAdvice</value>
            </list>
        </property>
    </bean>

</beans>

基本类型:

public interface ComputerSystem {

    public void run();
}
public class Windows10 implements ComputerSystem {

    public void run() {
        System.out.println("启动了windows10系统");
    }

}
public interface ComputerInterface {

    public void work(String name);
}
public class Computer implements ComputerInterface {

    private ComputerSystem system;

    //1.构造注入
    public Computer(ComputerSystem system) {
        this.system = system;
    }

    //业务逻辑
    //连接点
    public void work(String name){
        this.system.run();
    }


    //连接点
    public void work2(){
        this.system.run();
    }
}

通知类

//通知
public class AopAdvice implements MethodBeforeAdvice,AfterReturningAdvice, MethodInterceptor {

    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {

        System.out.println("完事之后, 抽根烟");
    }

    //  具体时间点
    public void before(Method method, Object[] args, Object target) throws Throwable {
        //谁谁谁 什么时间 访问了什么接口。。。
        System.out.println("method:"+method);
        System.out.println("args:"+args[0]);
        System.out.println("target:"+target);
        LogUtil.insertLog();
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("around 开始");
//      invocation.getMethod()
//      invocation.getArguments()
//      invocation.getThis()
        Object returnValue = null;
        try {
            //具体业务方法
            returnValue = invocation.proceed();
        } catch (Exception e) {
            System.out.println("around 异常");
            e.printStackTrace();
        }
        System.out.println("around 正常返回");

        return returnValue;
    }

}

测试类

public class Client {

    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("/com/prosay/aop/spring/springConfig.xml");
        ComputerInterface computer = (ComputerInterface) context.getBean("computerProxy");
        computer.work("张三");
    }
}

运行结果

around 开始
method:public abstract void com.prosay.aop.spring.ComputerInterface.work(java.lang.String)
args:张三
target:com.prosay.aop.spring.Computer@15d0c81b
插入日志
启动了windows10系统
完事之后, 抽根烟
around 正常返回

补充:这种方式参数传递不需要做过多的处理,因为Advice方法中自带参数。

AOP的实现原理是代理模式,需要了解的朋友可以去看我之前的博客:Java代理模式