spring中的AOP编程

通知(Advice): 除了目标方法以外的操作都称之为通知
切入点(PointCut): 要为哪些类中的哪些方法加入通知
切面(Aspect): 通知 + 切入点

在这里插入图片描述

AOP同样是用动态代理来实现的,能让被代理的类更专注的做自己的事情,其他事情交给代理来做就好了,比如事务,性能测试

当然spring中的AOP多种多样,有前置通知,后置通知,环绕通知(重点)以及异常通知,下面就来实现一下吧,首先实现AOP需要导入spring的jar包

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

1.前置通知,需要创建一个advice通知类,并实现MethodBeforeAdvice接口

MyBeforeAdvice类

//自定义记录业务方法名称前置通知 前置通知:目标方法执行之前先执行的额外操作
public class MyBeforeAdvice implements MethodBeforeAdvice {
    //before  参数1:当前执行方法对象  //参数2:当前执行方法的参数  //参数3:目标对象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("==============进入前置通知==============");
        //System.out.println("当前执行方法: "+method.getName());
        //System.out.println("当前执行方法参数: "+args[0]);
        //System.out.println("目标对象: "+target);
        System.out.println("当前执行方法名称: "+method.getName());
        //System.out.println("========================================");
    }
}

这样通知advice就已经完成,这个方法有三个参数可以使用,可以获取到这个方法的具体信息,方便我们的动态代理增强操作,下面就来配置一下切入点吧

spring配置文件spring.xml

    <!--注册通知-->
    <bean id="myBeforeAdvice" class="aop.MyBeforeAdvice"/>

    <!--组装切面-->
    <aop:config>
        <!--配置切入点pointcut
            id:切入点在工厂中唯一标识
            expression: 用来指定切入项目中哪些组件中哪些方法
                        execution(返回值 包.类名.*(..))
         -->
        <aop:pointcut id="pc" expression="execution(* aop.*ServiceImpl.*(..))"/>
        <!--配置切面 advice-ref:工厂中通知id pointcut-ref:工厂切入点唯一标识-->
        <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="pc"/>
    </aop:config>

这样切面就配置成功了,进行测试就行了

EmpServiceImpl类

//原始业务对象  目标对象
public class EmpServiceImpl implements EmpService {

    @Override
    public void save(String name) {
        System.out.println("处理业务逻辑调用save DAO~~"+name);
    }

    @Override
    public String find(String name) {
        System.out.println("处理业务调用 find DAO~~~"+name);
        return name;
    }
}

测试类

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

        ApplicationContext context = new ClassPathXmlApplicationContext("aop/spring.xml");
        EmpService empService = (EmpService) context.getBean("empService");
        System.out.println(empService.getClass());
        //代理对象
        empService.find("小陈");

    }
}

在这里插入图片描述
这样便得到了结果,而且他是动态代理对象

2.后置通知,需要创建一个advice通知类,并实现AfterReturningAdvice接口

//参数1:目标方法返回值  参数2: 当前执行方法对象  参数3:执行方法参数  参数4:目标对象
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("==========进入后置通知=========");
        System.out.println("返回值: "+returnValue);
        System.out.println("方法名: "+method.getName());
        System.out.println("方法的参数: " + args[0]);
        System.out.println("目标对象: " +target);
    }

切面构建方法和前置通知一样,可以说前置和后置通知是非常相似的

3.异常通知,需要创建一个advice通知类,并实现ThrowsAdvice接口

异常通知时比较特殊的,当继承了ThrowsAdvice接口后,他不会要你强制实现其方法,但如果你不实现他的一个方法就会报错,其实这个接口是存在多个方法的,我们只需要找自己需要的一个方法来实现他就可以了

点进ThrowsAdvice接口的源码就可以看到这几个方法了,他们被注释了

 * <pre class="code">public void afterThrowing(Exception ex)</pre>
 * <pre class="code">public void afterThrowing(RemoteException)</pre>
 * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, Exception ex)</pre>
 * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)</pre>

选择一个方法来进行实现

//出现异常时执行通知处理
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
        System.out.println("===========进入异常通知============");
        System.out.println("方法名: "+method.getName());
        System.out.println("方法的参数: " + args[0]);
        System.out.println("目标对象: " +target);
        System.out.println("异常信息: "+ex.getMessage());
    }

配置切面和前置通知是相同的,但出现异常时就会进入到异常通知了在这里插入图片描述

4.环绕通知(重点),需要创建一个advice通知类,并实现MethodInterceptor接口,MethodInterceptor是方法拦截器的意思

//参数1: invocation 获取当前执行方法 获取当前执行方法参数 获取目标对象
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("==========进入环绕通知===========");
//        System.out.println("当前执行方法:" +invocation.getMethod().getName());
//        System.out.println("方法的参数: "+invocation.getArguments()[0]);
//        System.out.println("获取当前的目标对象: "+invocation.getThis());
        try{
            long start = new Date().getTime();
            //放行目标方法
            Object proceed = invocation.proceed();//继续处理
            long end = new Date().getTime();
            System.out.println("方法: "+invocation.getMethod().getName()+",本次执行了 ["+(end-start)+"] ms!");
            return proceed;
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("出现异常时业务处理");
        }
        return null;
    }

可以看到环绕通知是非常独特的,其中最重要的一句代码是Object proceed = invocation.proceed();,这句代码是放行方法的意思,如果堆方法进行了放行且方法有返回值,那么就会得到一个Object 的返回值对象,我们需要将他返回给用户,可以看到环绕通知可以很轻松的做到对方法的性能测试的功能。

切面配置也是相同的

    <!--注册通知类-->
    <bean id="methodInvokeTimeAdvice" class="com.baizhi.advices.MethodInvokeTimeAdvice"></bean>


    <!--配置切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pc" expression="within(com.baizhi.service.DeptServiceImpl)"/>
        <!--组装切面-->
        <aop:advisor advice-ref="methodInvokeTimeAdvice" pointcut-ref="pc"/>
    </aop:config>

当然也可以配合注解来进行通知类的注入,那么就不需要再spring配置文件中声明通知类了

关于在spring配置文件中配置切面,其实是有多种方式的
其中配置切入点有两种方式

<!--配置切入点-->
<aop:pointcut id="pc" expression="execution(* aop.*ServiceImpl.*(..))"/>
<aop:pointcut id="pc" expression="within(com.baizhi.service.DeptServiceImpl)"/>

一种是通过execution方式,另一种是通过within方式,他们间的区别在与细粒度的不同,execution比起within具有更高的细粒度,因为他可以切到方法层面,而within只能切到类的层面,within等于是全部方法都切了,效率高。

execution表达式的意思是 返回值 aop.*ServiceImpl.方法名(方法参数) …代表不限制参数

组装切面也有两种方式

        <!--组装切面-->
        <aop:advisor advice-ref="methodInvokeTimeAdvice" pointcut-ref="pc"/>
        <aop:aspect>
            <aop:around method="invoke" pointcut-ref="pc"/>
        </aop:aspect>

他们的区别在于aop:advisor 中的advice-ref写的是增强类,并且这个类必须实现AOP的增强接口,例如实现了MethodInterceptor等,这样才可以使用aop:advisor
而aop:aspect只需要最简单的bean就可以了,aop:around代表的是环绕方法,还有其他的方法,method写的是方法名,这便是他们的不同
可以看出第一种是比较好的,因为配置文件会少很多。

这就是spring中的AOP编程了


版权声明:本文为weixin_44837750原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。