Spring AOP面向切面编程

AOPAspect Oriented Program 面向切面编程

首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。

所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务

所谓的周边功能,比如性能统计,日志,事务管理等等

周边功能在Spring的面向切面编程AOP思想里,即被定义为切面

在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发
然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP

Spring AOP原理和用法

常用术语

join point

连接点(join point)中文翻译大致意思是join point在spring aop中作为一种方法或者是对报错的处理。比如说刚举的例子,切面log是基于f方法的,而这个f方法就是连接点,我个人理解就是切面和主逻辑的连接处。

Advice

1581753129397
也就是增强,为了实现log日志,得编写一些逻辑代码Logic,但此时,我们需要在不同的连接点编写代码,可以说advice决定了在原有方法功能中出现的位置和时机。可分为around before after

Pointcut

可以与任何join point点进行匹配,匹配后的位置就是切面的位置。

target object

顾名思义,目标对象
1581753136685
如上图,f方法里实现的m功能再进行一些修改后(或者说增强)实现了n功能,而target object,也就是基础对象,增强后的方法f,则是被存在代理对象proxy object中。

AOP Proxy

aop代理,也就是为了实现方法增强而编写的对象,编写方式有jdk动态代理等,在我的另一篇文章也有写到。

Weaving

编织,也就是把切面应用到目标对象来创建新的代理对象的过程。也就是把上图的target object通过切面穿件proxy object的过程。

实现方式

先把测试用的类创好

package service;

public class ProductService {
    public void doSomeService(){
        System.out.println("doSomeService");
    }
}
public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(new String("applicationContext.xml"));
        ProductService s = (ProductService) context.getBean("s");
        s.doSomeService();
    }
}

通过Spring API实现1

创建一个LoggerAspect类

package aspect;

import org.aspectj.lang.ProceedingJoinPoint;

public class LoggerAspect {
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("start log: "+joinPoint.getSignature().getName());
        Object object = joinPoint.proceed();
        System.out.println("end log: "+joinPoint.getSignature().getName());
        return object;
    }
}

在xml文件中添加以下

    <bean name="s" class="service.ProductService"></bean>
    <bean id="loggerAspect" class="aspect.LoggerAspect"/>
	<aop:config>
        <aop:pointcut id="loggerCutPoint" expression="execution(* service.ProductService.*(..)) "/>
        <aop:aspect id="logAspect" ref="loggerAspect">
            <aop:around pointcut-ref="loggerCutPoint" method="log"/>
        </aop:aspect>
    </aop:config>

解释一下

config中,pointcut是切点,id是标识符(用于下面的pointcut-ref),expression是执行表达式,也就是触发的条件

执行表达式的格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) 

除了返回类型模式(上面代码片断中的ret-type-pattern),名字模式和参数模式以外,所有的部分都是可选的。 返回类型模式决定了方法的返回类型必须依次匹配一个连接点。 你会使用的最频繁的返回类型模式是 * ,它代表了匹配任意的返回类型。 一个全称限定的类型名将只会匹配返回给定类型的方法。名字模式匹配的是方法名。 你可以使用 * 通配符作为所有或者部分命名模式。 参数模式稍微有点复杂:() 匹配了一个不接受任何参数的方法, 而 (..) 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 () 匹配了一个接受一个任何类型的参数的方法。 模式 (,String) 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型。
一些常见切入点表达式的例子。

execution(public * (..))  任意公共方法的执行;
execution(
set(..))  任何一个以“set”开始的方法的执行;
execution(
com.xyz.service.AccountService.(..))   AccountService接口的任意方法的执行;
execution(
com.xyz.service..(..))  定义在service包里的任意方法的执行;
execution(* com.xyz.service...(..))  定义在service包或者子包里的任意方法的执行;

aop:aspect标签中注册了相应的切面,子标签around说明了是包围式的

可以看到LoggerAspetc类中

        System.out.println("start log: "+joinPoint.getSignature().getName());
        Object object = joinPoint.proceed();
        System.out.println("end log: "+joinPoint.getSignature().getName());

中间的那句就是将来与某个核心功能编织之后,用于执行核心功能的代码

结果:1581753538769

通过Spring API实现2

创建Log类和LogAfter类,实现相应的接口

package aspect;

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+"的"+method.getName()+"执行了");
    }
}
package aspect;

import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

public class LogAfter implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("执行了"+o1.getClass().getName()+"的"+method.getName()+"结束了");
    }
}

修改xml

    <bean name="s" class="service.ProductService"></bean>
    <bean id="logBefore" class="aspect.Log"/>
    <bean id="logAfter" class="aspect.LogAfter"/>
    <aop:config>
        <aop:pointcut id="loggerCutPoint" expression="execution(* service.ProductService.*(..)) "/>
        <aop:advisor advice-ref="logBefore" pointcut-ref="loggerCutPoint"/>
        <aop:advisor advice-ref="logAfter" pointcut-ref="loggerCutPoint"/>
    </aop:config>

解释一下

和第一个方式不同的是,这里用了aop:advisor,这是通过Spring提供的API实现的。

因为Log类和LogAfter类实现了MethodBeforeAdvice和AfterReturningAdvice方法,所以可以在核心代码执行前后进行输出。

1581753813192

自己实现Aspect

上面这种方法,可能程序复杂以后就会出现记不住接口的情况
记录下第三种方法

做一个MyLog.java

package aspect;

public class MyLog {
    public void before(){
        System.out.println("-----before-----");
    }
    public void after(){
        System.out.println("-----after------");
    }
}

修改xml

    <bean name="s" class="service.ProductService"></bean>
    <bean id="myLogger" class="aspect.MyLog"/>
    <aop:config>
        <aop:pointcut id="loggerCutPoint" expression="execution(* service.ProductService.*(..)) "/>
        <aop:aspect id="logAspect" ref="myLogger">
            <aop:before method="before" pointcut-ref="loggerCutPoint"/>
            <aop:after method="after" pointcut-ref="loggerCutPoint"/>
        </aop:aspect>
    </aop:config>

这里通过aop:before和aop:after分别置顶method来包围核心代码

1581753956610

推荐连接

Spring AOP 教程

浅析Spring AOP(一)——基本概念

Spring(4)——面向切面编程(AOP模块)

原文地址:https://www.cnblogs.com/cpaulyz/p/12401703.html