Spring之aop的概念、联系、实现

目录

一、概念
二、联系
三、相关知识
四、实现(四种实现)
4.1、经典的基于代理的AOP
4.2、@AspectJ注解驱动的切面
4.3、纯POJO切面((其实就是纯粹通过aop:fonfig标签配置,也是一种比较简单的方式)优点就是在代码中不体现任何AOP相关配置,纯粹使用xml配置)
4.4、注入式AspectJ切面(注入式在POJO切面已经体现即:切面、切点以注入的方式进行aop.config标签的编写)
四、基于事物控制

概念

面向切面编程(AOP是Aspect Oriented Program的首字母缩写) 面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。

但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法, 我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。

也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、 指定位置上的编程思想就是面向切面的编程。

一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。

这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。

AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。

联系:

回顾Spring的AOP时,对过滤器,拦截器,AOP的关系有点好奇,故记录做以备份。在实现一些公共逻辑的时候,很多功能通过过滤器,拦截器,AOP都能实现,但是不同的方式有不同的效率。具体有什么区别,看下文描述。

在这里插入图片描述

  • 加载过程:
    1、 用户发送请求,经过前端控制器Dispacherservlet(Controller的核心)将url交给处理器映射器HandlerMapping处理
    2、 处理器映射器HandlerMapping处理url,返回HandlerExecutionChain(可能包含拦截器,一定包含自定义的Controller(handler))
    3、 前端控制器将Controller交给处理器适配器HandlerAdapter处理,处理完成后,返回MV对象(ModelAndView)
    4、 前端控制器将MV交给视图解析器处理ViewResolver,处理的过程:将MV拆分成Model和view两个对象,并且将model渲染到view视图上,并且将view返回给前端控制器。
    5、 最后,前端控制器将视图响应给用户。

HandlerExecutionChain是通过HandlerMapping的getHandler方法返回的。继承该接口的类是来实现请求和handler对象的映射关系的。
这个接口中只有这样一个方法HandlerExecutionChaingetHandler(HttpServletRequest request) throws Exception;
HandlerExecutionChain类:由一个handler和若干的HandlerInterceptor构成。那么这个类的作用就显而易见了,就是将拦截器和handle组合*起来执行。就是对handle进行了包装。
这个类中有几个主要的方法:
1.applyPreHandle()看起,我们发现这个方法就是做的这样一个工作,按照列表中interceptor的顺序来执行它们的preHandle方法,直到有一个返回false。再看一下返回false后这个方法所做的工作,这时会调用triggerAfterCompletion方法,此时this.interceptorIndex指向上一个返回true的interceptor的位置,所以它会按逆序执行所有返回true的interceptor的afterCompletion方法。
2.applyPostHandle(),这个方法较为简单,就是按照逆序执行所有interceptor的postHandle方法。
3.triggerAfterCompletion()也是一样,就是从最后一次preHandle成功的interceptor处逆序执行afterCompletion方法。
举个例子:
整个拦截器的处理过程我们便可以很清晰地分为两种情况,一种是所有拦截器preHandle都返回true的情况,另一种是有拦截器preHandle返回false的情况。
我们先假设我们有三个拦截器A,B,C,D。
对于第一种情况,那么在DispatcherServlet中分别依次调用HandlerExecutionChain类中applyPreHandle、applyPostHandle和triggerAfterCompletion方法,
那么所有方法的执行顺序为:A.pre -> B.pre -> C.pre -> D.pre-> D.post -> C.post -> B.post -> A.post-> D.after -> C.after -> B.after -> A.after
对于第二种情况,我们不妨设C拦截器的preHandle返回为false。
这时DispatcherServlet类调用HandlerExecutionChain类applyPreHandle方法,然后由applyPreHandle调用triggerAfterCompletion方法,那么执行情况如下:A.pre -> B.pre -> C.pre ->B.after -> A.after也就是,指向上一个返回true的interceptor的位置(B位置),所以它会按逆序执行所有返回true的interceptor的afterCompletion方法。(也就是跳过了他们的postHandle方法)

1.过滤器拦截web访问url地址。 严格意义上讲,filter只是适用于web中,依赖于Servlet容器,利用Java的回调机制进行实现。
2.和框架无关,可以控制最初的http请求,但是更细一点的类和方法控制不了。
3.过滤器可以拦截到方法的请求和响应(ServletRequest request, ServletResponse response),并对请求响应做出像响应的过滤操作,比如设置字符编码,鉴权操作等
4.Spring中自定义过滤器(Filter)一般只有一个方法,返回值是void,当请求到达web容器时,会探测当前请求地址是否配置有过滤器,有则调用该过滤器的方法(可能会有多个过滤器),然后才调用真实的业务逻辑,至此过滤器任务完成。过滤器并没有定义业务逻辑执行前、后等,仅仅是请求到达就执行。

特别注意:过滤器方法的入参有request,response,FilterChain,其中FilterChain是过滤器链,使用比较简单,而request,response则关联到请求流程,因此可以对请求参数做过滤和修改,同时FilterChain过滤链执行完,并且完成业务流程后,会返回到过滤器,此时也可以对请求的返回数据做处理。

1.拦截器拦截以 .action结尾的url,拦截Action的访问。 Interfactor是基于Java的反射机制(APO思想)进行实现,不依赖Servlet容器。
2.拦截器可以在方法执行之前(preHandle)和方法执行之后(afterCompletion)进行操作,回调操作(postHandle),可以获取执行的方法的名称,请求(HttpServletRequest)
3.可以控制请求的控制器和方法,但控制不了请求方法里的参数(只能获取参数的名称,不能获取到参数的值)
4.用于处理页面提交的请求响应并进行处理,例如做国际化,做主题更换,过滤等

  • Spring AOP拦截器:

1.只能拦截Spring管理Bean的访问(业务层Service)。 具体AOP详情参照 Spring AOP:原理、 通知、连接点、切点、切面、表达式
2.实际开发中,AOP常和事务结合:Spring的事务管理:声明式事务管理(切面)
3.AOP操作可以对操作进行横向的拦截,最大的优势在于他可以获取执行方法的参数( ProceedingJoinPoint.getArgs() ),对方法进行统一的处理。
4.Aspect : 可以自定义切入的点,有方法的参数,但是拿不到http请求,可以通过其他方式如RequestContextHolder获得(ServletRequestAttributes servletRequestAttributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();)
5.常见使用日志,事务,请求参数安全验证等

  • springMVC拦截器的实现一般有两种方式
    1.第一种方式是要定义的Interceptor类要实现了Spring的HandlerInterceptor 接口
    2.第二种方式是继承实现了HandlerInterceptor接口的类,比如Spring已经提供的实现了HandlerInterceptor接口的抽象类HandlerInterceptorAdapter

    3.HandlerInterceptor 接口中定义了三个方法,我们就是通过这三个方法来对用户的请求进行拦截处理的。

preHandle(): 这个方法在业务处理器处理请求之前被调用,SpringMVC 中的Interceptor 是链式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor 中的preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean 类型的,当它返回为false 时,表示请求结束,后续的Interceptor 和Controller 都不会再执行;当返回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。
postHandle():这个方法在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。postHandle 方法被调用的方向跟preHandle 是相反的,也就是说先声明的Interceptor 的postHandle 方法反而会后执行
afterCompletion():该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的

  • Spring对AOP的支持:

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。

  • Spring创建代理的规则为:

1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

  • AOP编程其实是很简单的事情,AOP编程程序员只需要关注三点:

1、定义普通业务组件
2、定义切入点,一个切入点可能横切多个业务组件
3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
总结:进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,
即:代理对象的方法=增强处理+被代理对象的方法。

  • Filter与Interceptor联系与区别:

联系:

1.拦截器是基于java的反射机制,使用代理模式,而过滤器是基于函数回调。
2.拦截器不依赖servlet容器,过滤器依赖于servlet容器。
3.拦截器只能对action起作用,而过滤器可以对几乎所有的请求起作用(可以保护资源)。
4.拦截器可以访问action上下文,堆栈里面的对象,而过滤器不可以。
5.执行顺序:过滤前-拦截前-Action处理-拦截后-过滤后。

区别:

  • 作用域不同

1.过滤器依赖于servlet容器,只能在 servlet容器,web环境下使用
2.拦截器依赖于spring容器,可以在spring容器中调用,不管此时Spring处于什么环境

  • 细粒度的不同

1.过滤器的控制比较粗,只能在请求进来时进行处理,对请求和响应进行包装
2.拦截器提供更精细的控制,可以在controller对请求处理之前或之后被调用,也可以在渲染视图呈现给用户之后调用

  • 中断链执行的难易程度不同

1.拦截器可以 preHandle方法内返回 false 进行中断
2.过滤器就比较复杂,需要处理请求和响应对象来引发中断,需要额外的动作,比如将用户重定向到错误页面

  • 总结:
    拦截器相比过滤器有更细粒度的控制,依赖于Spring容器,可以在请求之前或之后启动,过滤器主要依赖于servlet,过滤器能做的,拦截器基本上都能做

  • Filter、Interceptor、aop拦截方向和抛出异常方向图
    在这里插入图片描述

  • 三者使用场景
    三者功能类似,但各有优势,从过滤器–》拦截器–》切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能。

相关知识

1.通知(Advice) 通知定义了在切入点代码执行时间点附近需要做的工作。 Spring支持五种类型的通知: Before(前) org.apringframework.aop.MethodBeforeAdvice After-returning(返回后) org.springframework.aop.AfterReturningAdvice After-throwing(抛出后) org.springframework.aop.ThrowsAdvice Arround(周围) org.aopaliance.intercept.MethodInterceptor Introduction(引入) org.springframework.aop.IntroductionInterceptor

2.连接点(Joinpoint)
程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法调用时、异常抛出时、方法返回后等等。

3.切入点(Pointcut)
通知定义了切面要发生的“故事”,连接点定义了“故事”发生的时机,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定。

4.切面(Aspect)
通知、连接点、切入点共同组成了切面:时间、地点和要发生的“故事”。

5.引入(Introduction)
引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)。

6.目标(Target)
即被通知的对象,如果没有AOP,那么通知的逻辑就要写在目标对象中,有了AOP之后它可以只关注自己要做的事,解耦合!

7.代理(proxy)
应用通知的对象,详细内容参见设计模式里面的动态代理模式。

8.织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器;
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术。

不基于框架实现拦截功能的文章前边我已经介绍过:

https://blog.csdn.net/weixin_43062845/article/details/100066042

实现

1、经典的基于代理的AOP
1.1工程结构:

在这里插入图片描述
1.2 pom.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.clement</groupId>
  <artifactId>tool</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>tool Maven Webapp</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <org.springframework.version>5.1.9.RELEASE</org.springframework.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!-- Spring -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${org.springframework.version}</version>
    </dependency>

    <!-- Spring AOP + AspectJ -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${org.springframework.version}</version>
      </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.8.9</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.9</version>
    </dependency>
  </dependencies>
</project>

1.3 spring-aop.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.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 1. 实现类的引入 切面 即要被代理的类 -->
    <bean id="iBizImpl" class="com.clement.aop.one.IBizImpl"></bean>

    <!--2.通知内容的引入,即切点前后或异常需要做的动作-->
    <!-- 前置增强  通知-->
    <bean id="adviceBefore" class="com.clement.aop.one.AdviceBefore"></bean>
    <!-- 后置增强  通知-->
    <bean id="adviceAfter" class="com.clement.aop.one.AdviceAfterReturning"></bean>
    <!-- 环绕增强  通知-->
    <bean id="adviceAround" class="com.clement.aop.one.AdviceAround"></bean>
    <!--异常增强   通知-->
    <bean id="adviceException" class="com.clement.aop.one.AdviceException"></bean>

    <!-- 3.定义切入点位置 -->
    <bean id="advicePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*Method"></property>
    </bean>

    <!--4.使切入点与通知相关联,完成切面配置-->
    <bean id="adviceHelper" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="adviceBefore"></property>
        <property name="pointcut" ref="advicePointcut"></property>
    </bean>

    <!-- 5.关联  代理工厂bean ProxyFactoryBean 和 通知 -->
    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
       <property name="targetName" value="iBizImpl"></property>
       <property name="interceptorNames" value="adviceHelper"></property>
        <property name="proxyInterfaces" value="com.clement.aop.one.IBiz"></property>
    </bean>
    <!-- 单独做环绕通知代理 可以如下
    <bean id="aroundServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">

        <property name="target" ref="iBizImpl"></property>
        <property name="interceptorNames" value="adviceAround"></property>
        <property name="proxyInterfaces" value="com.clement.aop.one.Advice"></property>
    </bean>
     -->

    <!-- 6.默认自动代理  默认只找通知-->
   <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>

</beans>

方案二:基于顾问 把pom.xml文件中的第3步到第6步替换为以下顾问的形式,测试直接用业务对象即可

    <!-- 1.顾问  包装advice -->
    <bean id="beforeAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="advice" ref="adviceBefore"></property>
        <property name="mappedNames" value="firstMethod,secondMethod"></property>
    </bean>

    <!-- 顾问  包装advice 正则表达式-->
    <!--<bean id="beforeAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
     <property name="advice" ref="adviceBefore"></property>
     <property name="pattern" value=".*Method"></property>
    </bean>-->



    <!-- 2.bean name自动代理 可以选择顾问或者通知-->
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames" value="iBizImpl"></property>
        <property name="interceptorNames" value="beforeAdvisor"></property>
    </bean>

1.4 业务接口及代码

package com.clement.aop.one;

/**
 * @ClassName: Advice
 * @Author: Clement
 * @Date: 2019/11/27 10:02
 * @Version: 1.0
 * @Description: 业务接口
 */
public interface IBiz {
    void firstMethod(String name);
    String secondMethod(String name);
}





package com.clement.aop.one;

/**
 * @ClassName: AdviceImpl
 * @Author: Clement
 * @Date: 2019/11/27 10:03
 * @Version: 1.0
 * @Description: 业务接口的方法实现
 */
public class IBizImpl implements IBiz {
    @Override
    public void firstMethod(String name) {
        System.out.println("我是第一个方法");
    }

    @Override
    public String secondMethod(String name) {
        System.out.println("我是第二个有返回值的方法");
        return "第二个业务方法已经执行";
    }


}

1.5 通知方法

package com.clement.aop.one;


import java.lang.reflect.Method;

/**
 * @ClassName: MethodBeforeAdvice
 * @Author: Clement
 * @Date: 2019/11/27 10:06
 * @Version: 1.0
 * @Description: 前置增强
 */
public class AdviceBefore implements org.springframework.aop.MethodBeforeAdvice{

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("==前置增强方法==");
        /*
        1、getCanonicalName() 是获取所传类从java语言规范定义的格式输出。
        2、getName() 是返回实体类型名称
        3、getSimpleName() 返回从源代码中返回实例的名称
        还可以获得o这个对象上的注解,进行注解的中间过程的操作
         */
        System.out.println("执行的类全限定名:"+o.getClass().getCanonicalName());
        System.out.println("执行的方法名:"+method.getName());
        for (Object object : objects) {
            System.out.println("传入的参数:"+object);
        }     
    }
}




package com.clement.aop.one;

import java.lang.reflect.Method;

/**
 * @ClassName: AfterReturningAdvie
 * @Author: Clement
 * @Date: 2019/11/27 10:25
 * @Version: 1.0
 * @Description: 后置增强
 */
public class AdviceAfterReturning implements org.springframework.aop.AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("==我是后置增强方法==");
    }
}




package com.clement.aop.one;

import org.aopalliance.intercept.MethodInvocation;

/**
 * @ClassName: SurroundAdvice
 * @Author: Clement
 * @Date: 2019/11/27 10:27
 * @Version: 1.0
 * @Description: 环绕增强
 */
public class AdviceAround implements org.aopalliance.intercept.MethodInterceptor{
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("==环绕增强之前==");
        String result= (String)methodInvocation.proceed();
        System.out.println("==环绕增强之后==");
        return result;
    }
}




package com.clement.aop.one;

/**
 * @ClassName: ExceptionAdvice
 * @Author: Clement
 * @Date: 2019/11/27 10:30
 * @Version: 1.0
 * @Description: 异常增强
 */
public class AdviceException implements org.springframework.aop.ThrowsAdvice{
    public void afterThrowing(Exception ex)
    {
        System.out.println("异常通知:"+ex.toString());
    }
}

1.6 测试
1.6.1:如果开启默认自动代理,直接获取业务对象操作方法,就可以实现通知
在这里插入图片描述
1.6.2:如果不开启默认自动代理,则需要获取代理对象,进行操作方法
在这里插入图片描述

2、@AspectJ注解驱动的切面
2.1工程结构:

在这里插入图片描述
2.2 pom.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.clement</groupId>
  <artifactId>tool</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>tool Maven Webapp</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <org.springframework.version>5.1.9.RELEASE</org.springframework.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!-- Spring -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${org.springframework.version}</version>
    </dependency>

    <!-- Spring AOP + AspectJ -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${org.springframework.version}</version>
      </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.8.9</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.9</version>
    </dependency>
  </dependencies>
</project>

2.3 spring-aspect.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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 目标对象 -->
    <bean id="iBizImpl" class="com.clement.aop.two.IBizImpl"></bean>
    <!-- 增强类 -->
    <bean id="aspectj" class="com.clement.aop.two.Aspect"></bean>
    <!-- 扫描整个项目 关联注解类和实现类 -->
    <aop:aspectj-autoproxy />

</beans>

2.4 业务接口及代码

package com.clement.aop.one;

/**
 * @ClassName: Advice
 * @Author: Clement
 * @Date: 2019/11/27 10:02
 * @Version: 1.0
 * @Description: 业务接口
 */
public interface IBiz {
    void firstMethod(String name);
    String secondMethod(String name);
}


package com.clement.aop.two;

/**
 * @ClassName: AdviceImpl
 * @Author: Clement
 * @Date: 2019/11/27 10:03
 * @Version: 1.0
 * @Description: 业务接口的方法实现
 */
public class IBizImpl implements IBiz {
    @Override
    public void firstMethod(String name) {
        System.out.println("我是第一个方法");
    }

    @Override
    public String secondMethod(String name) {
        System.out.println("我是第二个有返回值的方法");
        return "第二个业务方法已经执行";
    }


}

2.5 切面类

package com.clement.aop.two;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

/**
 * @ClassName: Aspect
 * @Author: Clement
 * @Date: 2019/11/27 12:52
 * @Version: 1.0
 * @Description: 自定义切面注解类
 */
@org.aspectj.lang.annotation.Aspect
public class Aspect {
    /**
     * 前置增强
     */
    @Before(value="execution(public * *Method(..))")
    public void asBefore(){
        System.out.println("这是前置增强");
    }

    /**
     * 后置增强
     */
    @AfterReturning(value="execution(public * *Method(..))")
    public void asAfterReturning(){
        System.out.println("这是后置增强");
    }

    /**
     * 环绕增强
     * @param pj
     */
    @Around(value="execution(public * *Method(..))")
    public void asAround(ProceedingJoinPoint pj){
        System.out.println("这是环绕前置增强");
        try {
            pj.proceed();
        } catch (Throwable e) {
            //抓捕异常
            e.printStackTrace();
        }
        System.out.println("这是环绕后置增强");
    }

    /**
     * 异常增强
     */
    @AfterThrowing(value="execution(public * *Method(..))")
    public void asThorws(){
        System.out.println("这是异常增强");
    }

    /**
     * 最终增强
     */
    @After(value="execution(public * *Method(..))")
    public void asAfter(){
        System.out.println("这是最终增强");
    }
}

2.6 测试

package com.clement.aop;

import com.clement.aop.two.IBiz;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @ClassName: AopTestTwo
 * @Author: Clement
 * @Date: 2019/11/27 12:59
 * @Version: 1.0
 * @Description: AspectJ注解驱动的切面的测试
 */
public class AopTestTwo {
    public static void main(String[] args) {
        /*
         * 解析配置文件
         */
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-aspect.xml");
        IBiz iBiz = (IBiz) ctx.getBean("iBizImpl");
        iBiz.firstMethod("111");
    }
}
3、纯POJO切面((其实就是纯粹通过aop:fonfig标签配置,也是一种比较简单的方式)优点就是在代码中不体现任何AOP相关配置,纯粹使用xml配置)
3.1 工程结构

在这里插入图片描述
3.2 pom.xml配置

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.clement</groupId>
  <artifactId>tool</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>tool Maven Webapp</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <org.springframework.version>5.1.9.RELEASE</org.springframework.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!-- Spring -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${org.springframework.version}</version>
    </dependency>

    <!-- Spring AOP + AspectJ -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${org.springframework.version}</version>
      </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.8.9</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.9</version>
    </dependency>
  </dependencies>
</project>

3.3 spring-pojo.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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 目标对象 -->
    <bean id="iBizImpl" class="com.clement.aop.three.IBizImpl"></bean>
    <!-- 增强类 -->
    <bean id="aspectj" class="com.clement.aop.three.Aspect"></bean>

    <!-- 配置写法1.Aspectj的XML配置文件 -->
 <!--   <aop:config>
        &lt;!&ndash;切点&ndash;&gt;
        <aop:pointcut expression="execution(public * Method(..))" id="pointcut"/>
        <aop:aspect ref="aspectj">
            <aop:before method="asBefore" pointcut-ref="pointcut"/>
            <aop:after-returning method="asAfterReturning" pointcut-ref="pointcut"/>
            <aop:after-returning method="asAround" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>-->

    <!-- 配置写法2.Aspectj的XML配置文件 -->
    <aop:config>
        <aop:aspect ref="aspectj">
            <aop:before  method="asBefore" pointcut="execution(public * *Method(..))" />
            <aop:after-returning  method="asAfterReturning" pointcut="execution(public * *Method(..))" />
            <aop:around method="asAround" pointcut="execution(public * *Method(..))"  />
        </aop:aspect>
    </aop:config>

</beans>

在这里插入图片描述

3.4 业务代码

package com.clement.aop.three;

/**
 * @ClassName: Advice
 * @Author: Clement
 * @Date: 2019/11/27 10:02
 * @Version: 1.0
 * @Description: 业务接口
 */
public interface IBiz {
    void firstMethod(String name);
    String secondMethod(String name);
}


package com.clement.aop.three;

/**
 * @ClassName: AdviceImpl
 * @Author: Clement
 * @Date: 2019/11/27 10:03
 * @Version: 1.0
 * @Description: 业务接口的方法实现
 */
public class IBizImpl implements IBiz {
    @Override
    public void firstMethod(String name) {
        System.out.println("我是第一个方法");
    }

    @Override
    public String secondMethod(String name) {
        System.out.println("我是第二个有返回值的方法");
        return "第二个业务方法已经执行";
    }


}

3.5 切面类

package com.clement.aop.three;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * @ClassName: Aspect
 * @Author: Clement
 * @Date: 2019/11/27 12:52
 * @Version: 1.0
 * @Description: 自定义切面注解类
 */
public class Aspect {
    /**
     * 前置增强
     */
    public void asBefore(JoinPoint jp){
        System.out.println("这是前置增强");
        //获得传入目标方法的参数
        Object[] args = jp.getArgs();
        //System.out.println("目标方法名为:" + jp.getSignature().getName());
        //System.out.println("目标方法所属类的简单类名:" +        jp.getSignature().getDeclaringType().getSimpleName());
        //System.out.println("目标方法所属类的类名:" + jp.getSignature().getDeclaringTypeName());
        //System.out.println("目标方法声明类型:" + Modifier.toString(jp.getSignature().getModifiers()));
        //获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
        System.out.println("方法:"+jp.getSignature().getName());
        for (Object arg : args) {
            System.out.println("参数:" + arg);
        }
    }

    /**
     * 后置增强
     */
    public void asAfterReturning(){
        System.out.println("这是后置增强");
    }

    /**
     * 环绕增强
     * @param pj
     */
    public void asAround(ProceedingJoinPoint pj){
        System.out.println("这是环绕前置增强");
        try {
            pj.proceed();
        } catch (Throwable e) {
            //抓捕异常
            e.printStackTrace();
        }
        System.out.println("这是环绕后置增强");
    }

    /**
     * 异常增强
     */
    public void asThorws(){
        System.out.println("这是异常增强");
    }

    /**
     * 最终增强
     */
    public void asAfter(){
        System.out.println("这是最终增强");
    }
}

3.6 测试类

package com.clement.aop;

import com.clement.aop.three.IBiz;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @ClassName: AopTestThree
 * @Author: Clement
 * @Date: 2019/11/27 12:59
 * @Version: 1.0
 * @Description: 纯POJO切面(其实就是纯粹通过<aop:config>标签配置,也是一种比较简单的方式)
 */
public class AopTestThree {
    public static void main(String[] args) {
        /*
         * 解析配置文件
         */
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-pojo.xml");
        IBiz iBiz = (IBiz) ctx.getBean("iBizImpl");
        iBiz.firstMethod("111");
    }
}
4、注入式AspectJ切面(注入式在POJO切面已经体现即:切面、切点以注入的方式进行aop.config标签的编写)

基于事物控制的配置:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 配置哪个数据源需要定义事务管理器 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置声明事务 -->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!-- 是否开启事务的方法
 REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
 
   SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
 
   MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
 
 REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
 
 NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
 
 NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
 
   NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务 -->
 
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- aop配置 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* com.clement.service.impl.*.*(..))" id="p3"/>
<!-- 指定配置的事务 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="p3"/>
</aop:config>
</beans>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的一个重要组件,它是一种编程范式,用于将横切关注点(Cross-Cutting Concerns)与核心业务逻辑进行分离。横切关注点是指那些与业务逻辑无关,但是对多个模块或层产生影响的代码,例如日志记录、性能统计、安全控制、事务处理等等。通过将这些横切关注点抽象成切面(Aspect),并将切面织入到应用程序中,可以实现对这些横切关注点的统一处理,从而提高应用程序的可维护性和可扩展性,减少重复代码的编写。 Spring AOP框架主要由以下几个概念组成: 1. 切面(Aspect):由切点和通知组成的一个模块,用于实现一个或多个横切关注点。 2. 切点(Pointcut):用于定义在哪些方法上进行拦截。 3. 通知(Advice):定义了拦截后要执行的操作,包括前置通知、后置通知、返回通知、异常通知和环绕通知等。 4. 连接点(Join Point):在应用程序中可以被拦截的点,例如方法调用、异常抛出等。 5. 切入点(Join Point):指定连接点的一组集合,用于定义切点。 6. 织入(Weaving):将切面应用到目标对象并创建新的代理对象的过程。 Spring AOP框架的实现方式主要是通过动态代理技术和字节码增强技术。在使用Spring AOP框架时,开发人员只需要定义切面、切点和通知,框架会自动将切面织入到应用程序中,从而实现对横切关注点的统一处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值