SpringAOP

SpringAOP

小龙 458 2019-11-01

SpringAOP介绍

在Spring设计之中有两大核心的技术:Ioc&DI、AOP(面向切面编程),所谓的切面编程就是一种更为高级的代理设计模式,在实际的项目开发之中可以利用代理设计模式实现良好的事务控制,也可以进行日志、用户行为统计、权限管理等操作。
AOP(Aspect Oriented Programming)面向切面的编程,AOP的主要意义是进行“解耦合”,换一个角度来讲就是一个超级大的代理设计模式,在整合AOP里面不仅仅应用的是Java的原生技术,也包括Spring对其的重新设计,在整个的AOP设计里面所提倡的思想:可以轻松的实现各种切面的代理配置,在整个的AOP之中,包含有如下几个基本概念:
aopdingyi.png

  • 关注点:所有可能需要关注的操作业务;
  • 关注点分离:将所有的业务逐步的拆分,而后形成一个个独立的个体,将与业务有关的辅助性的功能抽取;
  • 横切点关注点:实现核心的代理功能,利用代理功能可以在多个辅助操作上对多个关注点进行处理,横切点可能有很多歌个,例如:在进行转账处理的时候,需要进行“转账”、“记账”、“身份验证”、“日志记录”等
  • 织入:利用横切点表达式确定所有与之辅助的独立模块合并在一个完整的业务之中。

AOP的产生动机

早在Spring出现之前,虽然java提供有动态代理的处理机制,在早期的项目开发之中许多的事务控制都是通过程序直接完成的,这样的开发是不标准的。后来随着容器技术的提升,事务又可以在容器之中去完成,再后来发现对一些程序的代码控制还是交到自己的手里完成会更好一些。
AOP的出现是为了解决传统动态代理设计之中所存在的切面的控制问题,因为如果按照代理的设计来讲肯定需要设置有一个标准的业务接口,而后利用代理类获取接口的实例,在调用接口业务的方法时本质上执行的就是代理类的操作

定义一个业务层接口

public interface IDeptService{
	public boolean add();
}

在没有使用到代理设计的时候就需要在业务层的实现子类里面手工进行各种数据库操作

public class DeptServiceImpl implements IDeptServic{
	@Override
	public boolean add(){
		try{
			1. 打开数据库的连接
			2. 关闭数据库的自动提交操作
			3. 调用数据层的处理
			4. 如果没有任何的问题则进行事务的提交(commit)
		}catch(Exception e){
			1. 出现了异常,向上抛出异常
			2. 进行事务的回滚操作(rollback)
		}finally{
			1. 关闭数据库操作
		}
		return false;
	}
}

上面的程序利用了业务层的实现子类直接进行了具体的事务控制,但是在一个项目之中业务方法可能会有成千上万个方法,那么如果所有的操作都是手工的进行如上的控制,那么代码的重复率太高了。为了解决这样的问题,就可以采用动态代理设计模式来完成。

public class ServiceProxy implements InvocationHandler{
	private Object targetObject;
	public Object bind(Object targetObject){
		this.targetObject = targetObject;
		return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),this.targetObject.getClass().getInterface(),this);
	}
	@Override
	public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
		try{
			1. 打开数据库的连接
			2. 关闭数据库的自动提交操作
			3. 调用数据层的处理
			4. 如果没有任何的问题则进行事务的提交(commit)
		}catch(Exception e){
			1. 出现了异常,向上抛出异常
			2. 进行事务的回滚操作(rollback)
		}finally{
			1. 关闭数据库操作
		}
		return false;
	}
}

此时利用了动态代理的机制,将所有业务处理的功能进行了抽象,随后在动态代理里面创建正常的代理业务对象,而后由代理业务对象调用方法,利用反射机制再实现最终业务对象的调用,而在业务调用里面只完成核心的处理。

public class DeptServiceImpl implements IDeptServiceImpl{
	@Override
	public boolean add(){
		- 调用数据层的处理
		return false;
	}
}

此时实现了一个代理的操作控制,但是如果按照传统的代理设计来讲,此时的代码存在有如下的问题:

  • 业务层的代码需要进行代理生产,而数据层的操作不需要代理生产,也就是说对象的实例化处理要区分;
  • 在进行业务层代理控制的时候,有可能要发生有一些方法的动态变化,那么如何可以进行操作的匹配;
  • 在进行代理配置的时候,有可能会随着不同的环境,有不同的代理处理位置,不可能每一次都手工进行源代码的修改处理,应该提供方便的配置操作;
  • 对于之前的静态代理而言实际上可以发现有不同的组成部分:执行业务操作之间、执行业务操作之后、产生异常之后、数据返回之后,对于这些内容并没有进行更加细分的操作。

AOP是一套完整的独立的配置模型,如果需要进行各种切面的定义以及关注点的织入,就必须使用如下的结构配置:
FZ3MKDJFNR4CSXCS80.png

AOP切入点表达式

如果要进行切入的处理,在Spring里面采用了AspectJ表达式实现切入点的定义,直接利用包进行切入点的控制处理
基本语法:

execution(注解配置? 修饰符配置? 方法返回值类型 操作类型匹配 方法名称匹配(参数匹配) 异常匹配)
例如:execution(public * com.xxx..*.*(..))
  • 【可选】注解配置:匹配方法上是否存在有指定的注解,例如:“@Override”;
  • 【可选】修饰符配置:主要匹配方法上的修饰符“public”,“protected”、“default”;
  • 【必须】方法返回值类中匹配:可以匹配具体的类型,如果发现返回值类型有很多,可以设置为“*”;
  • 【必选】操作类型匹配:定义方法所在的类(类的全名为“包.类”),考虑到这个包下可能有若干的子包,所有使用“..”设置任意层级,随后使用一个“*”表示任意的类
  • 【必选】方法名称:匹配那个方法进行切面控制,如果使用了“*”表示匹配任意与所有的方法
  • 【必选】:参数匹配:使用“..”匹配所有的参数类型
    xiangqing.png

AOP基础实现

要想在项目之中进行AOP的处理,一定要引入AOP相应的依赖库配置
maven

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>

gradle

compile group: 'org.springframework', name: 'spring-aop', version: '5.2.0.RELEASE'
compile group: 'org.springframework', name: 'spring-aspects', version: '5.2.0.RELEASE'

AOP的设里面是将所有的关注点单独独立出来,形成一个个具体的独立程序类,可以创建一个进行AOP处理的工具类,同时设置一些处理方法

public class ServiceAdvice {
    /**
     * 在业务操作之前进行调用
     */
    public void beforeHandle(Object param) {
        System.err.println("【ServiceAdive - beforeHandle()】业务调用之前。" + param);
    }
    /**
     * 在业务操作之后进行调用
     */
    public void afterHandle() {
        System.err.println("【ServiceAdive - afterHandle()】业务调用之后。");
    }
    /**
     * 异常产生的后置通知
     *
     * @param exe
     */
    public void throwHandle(Exception exe) {
        System.err.println("【ServiceAdive - throwHandle()】代码出现异常。" + exe);
    }
    /**
     * 调用完毕完后结果
     *
     * @param result
     */
    public void returnHandle(Object result) {
        System.err.println("【ServiceAdive - returnHandle()】代码调用完毕。" + result);
    }
    /**
     * 环绕通知
     *
     * @param point 获取所有的请求内容
     * @return
     */
    public Object aroundHandle(ProceedingJoinPoint point) {
        System.err.println("【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - BEFORE】方法执行之前:" + Arrays.toString(point.getArgs()));
        Object result = null;
        try {
            result = point.proceed(point.getArgs());
            System.err.println("【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - RETURNING】方法执行完毕有返回值:" + result);
        } catch (Throwable e) {
            System.err.println("【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - THROWING】方法产生了异常");
        }
        System.err.println("【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - AFTER】方法全部执行完毕");
        return result;
    }
}

在Spring-xxx.xml配置文件中引入AOP相关的命名空间,进行切入点的配置以及相关处理类的配置

<bean id="ServiceAdvice" class="com.xxx.xxx.ServiceAdvice"/>
    <aop:config>
        <aop:pointcut id="commonPointcut" expression="execution(public * com.xxx.service..*.*(..))"/>
        <aop:aspect ref="ServiceAdvice">
            <aop:before method="beforeHandle" arg-names="param" pointcut="execution(public * com.xxx.service..*.*(..)) and args(param)"/>
            <aop:after method="afterHandle" pointcut-ref="commonPointcut"/>
            <aop:after-throwing method="throwHandle" pointcut-ref="commonPointcut" throwing="exe" arg-names="exe"/>
            <aop:after-returning method="returnHandle" pointcut-ref="commonPointcut" returning="result" arg-names="result"/>
            <aop:around method="aroundHandle" pointcut-ref="commonPointcut"/>
        </aop:aspect>
    </aop:config>

配置完成之后,在业务层实现子类上使用注解进行Bean对象的定义,然后就可以编写测试类进行测试

@Service
public class MessageServiceImpl implements IMessageService {
    @Override
    public boolean echo(Dept vo) {
        if (vo == null) {
            throw new RuntimeException("操作的对象数据为NULL。");
        }
        System.out.println("【MessageService】修改部门信息,dept" + vo);
        return false;
    }
}
@ContextConfiguration(locations = {"classpath:spring/spring-base.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class testSpringAop {
    @Autowired
    private IMessageService messageService;
    @Test
    public void testAop() {
        Dept vo = new Dept();
        vo.setDeptno(10L);
        vo.setDname("开发部");
        System.err.println(this.messageService.echo(vo));
    }
}

正常执行结果

【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - BEFORE】方法执行之前:[Dept]
【ServiceAdive - beforeHandle()】业务调用之前。Dept

【MessageService】修改部门信息,deptDept

【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - RETURNING】方法执行完毕有返回值:false
【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - AFTER】方法全部执行完毕
【ServiceAdive - afterHandle()】业务调用之后。
【ServiceAdive - returnHandle()】代码调用完毕。false
false

出现异常之后结果

【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - BEFORE】方法执行之前:[null]
【ServiceAdive - beforeHandle()】业务调用之前。null
【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - THROWING】方法产生了异常
【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - AFTER】方法全部执行完毕
【ServiceAdive - afterHandle()】业务调用之后。
【ServiceAdive - returnHandle()】代码调用完毕。null

基于Annotations实现AOP配置

上面的AOP配置是直接通过配置文件的形式进行定义的,但是在Spring里面简化了AOP的处理难度,可以直接使用Annotation在一个具体的配置类上进行AOP的处理实现

首先需要开启注解支持,在“spring-xxx.xml”配置文件中加入如下配置

<context:component-scan base-package="com.yootk"/>
<!-- 开启注解配置 -->
<aop:aspectj-autoproxy/>
@Component
@Aspect //表示该类为AOP控制类
public class ServiceAdvice {
    /**
     * 在业务操作之前进行调用
     */
   ** @Before(value = "execution(public * com.yootk.service..*.*(..)) && args(param)" , argNames = "param")**
    public void beforeHandle(Object param) {
        System.err.println("【ServiceAdive - beforeHandle()】业务调用之前。" + param);
    }
    /**
     * 在业务操作之后进行调用
     */
   ** @After("execution(public * com.yootk.service..*.*(..))")**
    public void afterHandle() {
        System.err.println("【ServiceAdive - afterHandle()】业务调用之后。");
    }
    /**
     * 异常产生的后置通知
     *
     * @param exe
     */
   ** @AfterThrowing(value = "execution(public * com.yootk.service..*.*(..))",throwing = "exe",argNames = "exe")**
    public void throwHandle(Exception exe) {
        System.err.println("【ServiceAdive - throwHandle()】代码出现异常。" + exe);
    }
    /**
     * 调用完毕完后结果
     *
     * @param result
     */
   ** @AfterReturning(value = "execution(public * com.yootk.service..*.*(..))",returning = "result",argNames = "result")**
    public void returnHandle(Object result) {
        System.err.println("【ServiceAdive - returnHandle()】代码调用完毕。" + result);
    }
    /**
     * 环绕通知
     *
     * @param point 获取所有的请求内容
     * @return
     */
  **  @Around("execution(public * com.yootk.service..*.*(..))")**
    public Object aroundHandle(ProceedingJoinPoint point) {
        System.err.println("【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - BEFORE】方法执行之前:" + Arrays.toString(point.getArgs()));
        Object result = null;
        try {
            result = point.proceed(point.getArgs());
            System.err.println("【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - RETURNING】方法执行完毕有返回值:" + result);
        } catch (Throwable e) {
            System.err.println("【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - THROWING】方法产生了异常");
        }
        System.err.println("【⭐⭐⭐⭐ServiceAdvice - aroundHandle() - AFTER】方法全部执行完毕");
        return result;
    }
}

与之前的工具类是一样的,只是加入了注解。使用了注解,xml配置文件中的配置就可以不需要了。这样配置的效果与配置文件的效果一样。