Spring基础(二)_面向切面(AOP)

面向切面编程

面向切面编程【AOP,Aspect Oriented Programming】:通过预编译方式和运行期间动态代理实现程序功能的统一维护的技术。AOP 是 Spring 框架中的一个重要内容,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

在 Spring 中,依赖注入管理和配置应用对象,有助于应用对象之间的解耦。而面向切面编程可以实现横切关注点与它们所影响的对象之间的解耦。

横切关注点:散布在应用中多处的功能,可以被提取出来集中处理。

面向切面编程所要解决的问题是:将横切关注点与应用的业务逻辑相分离。

AOP 常见的场景:日志、声明式事务、安全和缓存。

使用面向切面编程时,在一个地方定义通用功能,然后通过声明的方式定义这个通用功能要以何种方式在何处应用,而无需修改受影响的类。横切关注点可以被模块化为特殊的类,该类被称为切面。

好处

  1. 每个关注点集中在一个地方,而不是分散在多处代码中;
  2. 模块更简洁,主要的代码只关注业务逻辑代码;

1、专业术语

通知(Advice)

切面的工作被称为通知通知定义了切面是什么以及何时使用。Spring 含有 5 中类型的通知:

  • 前置通知(Before):在目标方法被==调用之前==调用通知;
  • 后置通知(After):在目标方法被==调用之后==调用通知,此时不关心方法的输出是什么;
  • 返回通知(After-returning):在目标方法==成功执行后==调用通知;
  • 异常通知(After-throwing):在目标方法==抛出异常后==调用通知;
  • 环绕通知(Around):在目标方法==调用之前和调用之后==均调用通知;

连接点(Join point)

连接点是应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。简单理解:一个方法即为一个连接点,只是在这个连接点调用通知的时间点可以自定义。

切点(Pointcut)

切点是一定数量的连接点;切点定义所要织入通知的一个或多个连接点。Spring 基于动态代理,只支持方法连接点。切点指明目标方法,当目标方法调用执行时,会调用对应的通知。

切面(Aspect)

切面是通知和切点的结合。通知和切点共同定义了切面的功能、在何时和何地完成功能。

引入(Introduction)

向现有的类添加新的方法或属性称为引入。

织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程。目标对象的生命周期里有多个点可以织入切面:

  • 编译期:切面在目标类编译时被织入;需要使用特殊的编译器,比如:AspectJ 的织入编译器。
  • 类加载器:切面在目标类加载到 JVM 时被织入;
  • 运行期:切面在应用运行在某个时刻被织入;一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。

2、Spring的AOP实现

Spring 提供了 4 种类型的 AOP 支持:

  • 基于代理的经典 Spring AOP;
  • 纯 POJO 切面;
  • @AspectJ 注解驱动的切面;
  • 注入式 AspectJ 切面(适用于 Spring 各版本);

前三种都是 Spring AOP 实现的变体,Spring AOP 构建在动态代理基础之上,因此,Spring 对 AOP 的支持局限于 方法拦截。

Spring 通知是用标准的 Java 类编写的。定义通知所应用的切点通常使用注解或在 Spring XML 配置文件中编写

Spring 在运行时通知对象。Spring 的切面由包裹了目标对象的代理类实现。代理类处理方法的调用,执行额外的切面逻辑,并调用目标方法。直到装配需要被代理的 Bean 时,Spring 才会创建代理对象

2.1 AspectJ 指示器

前面讲到,切点作用是用于定位,在所在位置执行时调用切面的通知。在 Spring AOP 中,要使用 AspectJ 的切点表达式来定义切点;而在 Spring AOP 所支持的 AspectJ 切点指示器有:

指示器 描述
arg() 限制连接点匹配参数为指定类型的执行方法;
@args() 限制连接点匹配参数由指定注解标注的执行方法;
execution() 用于匹配是连接点的执行方法;
this() 限制连接点匹配 AOP 代理的 Bean 引用为指定类型的类;
target() 限制连接点匹配目标对象为指定类型的类;
@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解;
within() 限制连接点匹配指定的类型;
@within() 限制连接点匹配指定注解所标注的类型;
@annotation() 限定匹配带有指定注解的连接点;
@bean() 限定连接点匹配指定 ID 的 Bean;

上述指示器中,除了 execution 之外,均用于匹配连接点;而 execution 指示器是执行通知:当括号内的连接点对应的方法被调用时,execution 会执行对应的通知操作

2.2 使用注解创建切面

2.2.1 创建切面类

AspectJ 5 引入了重要的特性:使用注解创建切面。要在类中使用注解创建切面,首先,必须要导入对应的 jar包( aspectjweaver.jar ),下面,我们来创建一个切面类:

import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AspectClass {

}

使用注解 AspectJ 表明,这个类不仅仅是一个简单的 Java 类,还是一个切面。但是前面说过,一个完整的切面应该包含切点和通知

2.2.2 定义通知方法

在切面类中,5 种通知类型分别对应 5 种注解,使用注解标注方法定义通知方法,这些注解分别是:

注解 通知
@After 目标方法返回或抛出异常后调用
@AfterReturning 目标方法返回后调用
@AfterThrowing 目标方法抛出异常后调用
@Around 将目标方法封装起来
@Before 目标方法调用之前执行

使用注解定义通知

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AspectClass {

    @Before()
    public void before(){
        System.out.println("前---置通知");
    }

    @After()
    public void before(){
        System.out.println("后---置通知");
    }

    @Around()
    public Object around(ProceedingJoinPoint jp){
        System.out.println("环绕通知---前");
        Object proceed=null;
        try {
            Object[] args = jp.getArgs();   //获取传入目标方法的参数
            proceed = jp.proceed(); //调用执行目标方法
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        System.out.println("环绕通知---后");

        return proceed;
    }
}

其中,环绕通知的方法定义不同。环绕通知方法中需要传入一个参数 ProceedingJoinPoint 接口,该接口控制目标方法:

  • getArgs() 获取目标方法的形参;
  • 通过 proceed() 执行目标方法,会返回目标方法的返回值;如果不调用此方法,会阻塞目标方法的调用;也可以多次调用
2.2.3 编写切点表达式

在注解内放入切点表达式。所谓切点表达式,是使用指示器匹配连接点。切点表达式中需要使用 execution 指示器(如下图)。

不同的指示器之间可以使用逻辑运算(and、or、not)拼接一起使用

下面,我们为切面添加切点,当执行器内指定的目标方法执行时会调用对应的通知方法。

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AspectClass {

    @Before("execution(* 包名.类名.目标方法())")
    public void before(){
        System.out.println("前---置通知");
    }

    @After("execution(* 包名.类名.目标方法())")
    public void after(){
        System.out.println("后---置通知");
    }

    @Around("execution(* 包名.类名.目标方法())")
    public Object around(ProceedingJoinPoint jp){
        System.out.println("环绕通知---前");
        Object proceed=null;
        try {
            proceed = jp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        System.out.println("环绕通知---后");

        return proceed;
    }
}

还有一种方式可以简化编写切点。在上面这个例子中,每个通知中都使用切点表达式来匹配连接点,这样做很繁琐。使用 @Poingcut 注解标注,为一个方法编写切点,然后在通知注解中引用切点方法即可

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

@Aspect
public class AspectClass {

    //切点方法
    @Pointcut("execution(* 包名.类名.目标方法())")
    public void work() {

    }

    @Before("work()")//引用切点方法
    public void before() {
        System.out.println("前---置通知");
    }

    @After("work()")//引用切点方法
    public void after() {
        System.out.println("后---置通知");
    }

    @Around("work()")//引用切点方法
    public Object around(ProceedingJoinPoint jp) throws Throwable {

        System.out.println("环绕通知---前");
        Object proceed = null;

        Object[] args = jp.getArgs();
        jp.proceed();

        System.out.println("环绕通知---后");

        return proceed;
    }
}
2.2.4 启动自动代理

到此为止,一个切面就创建好了。但是,如果没有启用自动代理功能,这个切面只能被当做一个 Bean,AspectJ 注解也不会被解析。在 JavaConfig 配置类和 XML 配置文件启动自动代理功能

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
//(1)启动 AspectJ 自动代理
@EnableAspectJAutoProxy
public class WorkAspectConfig {

    //(2)声明切面类的bean
    @Bean
    public AspectClass getAspectClass(){
        return new AspectClass();
    }
}
<?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/aop/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy/><!--(1)启用 AspectJ 自动代理-->

    <bean class="AspectClass"/><!--(2)声明切面类的bean-->

</beans>
2.2.4 处理通知中的参数

在此之前,我们在讲到前置通知和后置通知的方法定义中都是没有参数,除了环绕通知,可以通过 ProceedJoinPoint 接口获取目标方法的参数和传递返回值。但,如果前置通知和后置通知的方法中定义了参数,那么,切面如何访问和使用目标方法中的参数呢?

@Before("execution(* 包名.类名.目标方法名(Type...)) && args(name,...)")
public void work(Type name,...){

}

不同之处在于:

  • Execution 指示器内的方法需要指定参数类型,这个参数类型与传入通知方法的参数类型匹配
  • 使用逻辑运算符 && (或 ||、!拼接一个 args 指示器,这个指示器内指定参数名,表明传递给目标方法的 Type 类型参数也会传递给通知方法。
  • args 指示器内的参数名称与通知方法的名称要一致。
2.2.5 添加新功能

上面已经讲解讲解了:如何为现有方法添加额外的功能。那现在我们需要为一个对象添加新的方法,如何实现呢?具体步骤如下:

  1. 创建一个新的接口,接口内定义了新功能的方法;并创建新接口的实现类;
  2. 创建一个新的切面类,该类内定义一个步骤1声明接口的静态属性,该属性使用注解 @DeclareParents 标注;
  3. 在配置文件中装配切面类的 Bean,以及新接口的实现类的 Bean;

这样就完成了为一个现有对象添加了新方法。注意:要使用新方法,对象需要强制转换为新接口类型。在这里需要重点了解的是:@DeclareParents 的使用

//@DeclareParents 由三部分组成
@DeclareParents(value="package.OldInterface+"
                defaultImpl= newClassImplNewInterface.class)
public static NewInterface newInterface;
  • value :指定需要添加新功能的类,+表示 OldInterface 类的所有子类;
  • defaultImpl :指定添加了新功能的实现类;
  • @DeclareParents 注解标注的静态属性:指明了要引入新功能的接口;

2.3 XML 配置创建切面

在 Spring 的 XML 配置文件中, aop 命名空间提供了元素用来声明切面,如表:

AOP配置元素 用途
<aop:aspect-autoproxy> 启用 @AspectJ 注解驱动的切面
<aop:config> 顶层AOP配置元素。大多数aop元素必须在该元素内
<aop:aspect> 定义一个切面
<aop:advisor> 定义AOP通知器
<aop:after> 定义AOP后置通知
<aop:after-returning> 定义AOP返回通知(不管目标方法是否执行成功)
<aop:after-throwing> 定义AOP异常通知
<aop:around> 定义AOP环绕通知
<aop:before> 定义AOP前置通知
<aop:declare-parents> 以透明的方式为目标对象引入额外的接口
<aop:pointcut> 定义一个切点

已经了解了 XML 配置的基本使用元素。由于前面对切面的了解已经比较深入,现在了解如何使用 XML 配置 AOP,暂不深入过多。直接上例子:

1、原有代码,需要在现有代码中添加新功能。简称:目标对象、目标方法。

package xml;

public class Work {

    public String working(){

        System.out.println("工作ing");

        return "工作ing";
    }

    public void working(int time){

        System.out.println("工作时长:"+time);

    }
}

2、要向目标方法添加的新功能类,并以此类添加切面

package xml;

import org.aspectj.lang.ProceedingJoinPoint;

public class AspectClass {

    public void before() {
        System.out.println("前---置通知");
    }

    public void after() {
        System.out.println("后---置通知");
    }

    public Object around(ProceedingJoinPoint jp) throws Throwable {

        System.out.println("环绕通知---前");
        Object proceed = null;

        //(1)获取目标方法的参数
        Object[] args = jp.getArgs();
        //(2)调用目标方法,并获取返回值
        proceed = jp.proceed(args);

        System.out.println("环绕通知---后");

        return proceed;
    }

    public void afterWork(int time) {

        if (time > 0 && time < 8) {
            System.out.println("工作时长不够8小时");
        }else{
            System.out.println("工作时长:"+time);
        }
    }

}

3、要向目标对象添加新的方法

package xml;

public interface OtherWork {
    void addWorkTime();
}
package xml;

public class NightWork implements OtherWork {
    @Override
    public void addWorkTime() {
        System.out.println("加夜班");
    }
}

4、编写 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 class="xml.Work" id="w"/>  

    <!--创建Bean ,该Bean要作为切面类-->
    <bean class="xml.AspectClass" id="aspectClass"/>

    <!--对引入新方法的接口的实现类创建bean-->
    <bean class="xml.NightWork" id="nightWork"/>

    <!--AOP配置-->
    <aop:config>
        <!--定义一个切面-->
        <aop:aspect ref="aspectClass">

            <!--<aop:before method="before"
                     pointcut="execution(* xml.Work.working())"/>-->

            <!--定义切点-->
            <aop:pointcut id="work_pointcut"
                          expression="execution(* xml.Work.working())"/>

            <!--定义通知-->
            <aop:before method="before" pointcut-ref="work_pointcut"/>
            <aop:after method="after" pointcut-ref="work_pointcut"/>
            <aop:around method="around" pointcut-ref="work_pointcut"/>

            <!--定义带有参数的通知-->
            <aop:after method="afterWork"
                       pointcut="execution(* xml.Work.working(int)) and args(time)"/>

            <!--向原有对象中添加新的方法-->
            <aop:declare-parents types-matching="xml.Work"
                                 implement-interface="xml.OtherWork"
                                 default-impl="xml.NightWork"/>
        </aop:aspect>
    </aop:config>
</beans>

6、测试类

package test;

import xml.OtherWork;
import xml.Work;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:xml/AspectConfig.xml")
public class WorkTest {

    @Test
    public void test(){
        //解析XML配置文件
        ApplicationContext app =new ClassPathXmlApplicationContext("xml/AspectConfig.xml");

        //测试目标方法的新功能
        Work work = (Work) app.getBean("w");
        work.working(6);

        //测试目标对象的新方法
        OtherWork nightWork = (OtherWork) work;
        nightWork.addWorkTime();
    }
}

//测试结果:
//      工作时长:6
//      工作时长不够8小时
//      加夜班

到此为止,Spring AOP的基础学习完毕。

原文地址:https://www.cnblogs.com/Brilliance-Chan/p/12305781.html

时间: 2024-11-05 17:16:36

Spring基础(二)_面向切面(AOP)的相关文章

spring基础知识(三)——aop

spring基础知识(三)--aop面向切面编程 1.概念术语 aop面向切面编程(Aspect ariented Programming) 在开始之前,需要理解Spring aop 的一些基本的概念术语(总结的个人理解,并非Spring官方定义): 切面(aspect):用来切插业务方法的类. 连接点(joinpoint):是切面类和业务类的连接点,其实就是封装了业务方法的一些基本属性,作为通知的参数来解析. 通知(advice):在切面类中,声明对业务方法做额外处理的方法. 切入点(poin

Spring(二)面向切面编程AOP

这篇博客写的比较累赘,好多相同的程序写了好几遍,主要是为了是自己养成这样的一个编程思路,其中应该不乏错误之处,以后好好学,慢慢改吧.------jgp 1 AOP介绍 1.1什么是AOP 面向切面编程(Aspect Oriented Programing):通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AOP采取横向抽取机制,取代了传统纵向继承体系的重复性代码,主要体现在事务处理.日志管理.权限控制.异常处理等方面,使开发人员在编写业务逻辑时可以专心于核心业务,提高了代码的可

spring面向切面aop拦截器

spring中有很多概念和名词,其中有一些名字不同,但是从功能上来看总感觉是那么的相似,比如过滤器.拦截器.aop等. 过滤器filter.spring mvc拦截器Interceptor .面向切面编程aop,实际上都具有一定的拦截作用,都是拦截住某一个面,然后进行一定的处理. 在这里主要想着手的是aop,至于他们的比较,我想等三个都一一了解完了再说,因此这里便不做过多的比较. 在我目前的项目实践中,只在一个地方手动显示的使用了aop,那便是日志管理中对部分重要操作的记录. 据我目前所知,ao

Spring框架使用(控制反转,依赖注入,面向切面AOP)

参见:http://blog.csdn.net/fei641327936/article/details/52015121 Mybatis: 实现IOC的轻量级的一个Bean的容器 Inversion of control 控制反转:由容器控制程序之间的关系,不是程序代码操作 Depend Inject 依赖注入 Aspect oriented programming 面向切面编程 Spring能帮助我们根据配置文件创建及组装对象之间的依赖关系: Spring面向切面编程能帮助我们无耦合的实现日

解析Spring第三天(面向切面AOP)

面向切面:AOP 在不修改源代码的基础上,对方法进行增强.AOP的底层原理就是代理技术(第一种:jdk的动态代理(编写程序必须要有接口).第二种:cglib代理技术(生成类的子类).如果编写的程序有借口,则spring框架会自动使用jdk的动态代理技术增强,). Joinpoint(连接点) 所谓连接点是指那些被拦截到的点.在spring中,这些点指的是方法,因为spring只支持方法类型的连接点 Pointcut(切入点) -- 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义 Ad

Spring学习1_面向切面( AOP )实现原理

面向切面编程 (Aspect Oriented Programming,简称AOP) 是Spring的一个重要特性,其原理是采用动态代理方式实现. 下面通过一个Demo来模拟AOP实现 整个代码目录结构如下: 其中LogInterceptor类完成为所有Service方法添加日志记录的功能. 1.Dao层实现 package com.dao; public class UserDaoImpl implements UserDao { @Override public void save() {

Spring_面向切面(AOP)基础

努力不一定成功:但是放弃必定会失败. 面向切面编程 在软件开发中,散布于应用中多处的功能称为横切关注点.通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中).把这些横切关注点与业务逻辑分离正是面向切面编程(AOP)所要解决的问题. 如果要重用功能的话,最常见的面向对象技术是继承(inheritance)或委托(delegation).但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系:而使用委托可能需要对委托对象进行复杂的调用

Spring面向切面(AOP)

AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志.事务.权限等,Struts2的拦截器设计就是基于AOP的思想. AOP的基本概念 Aspect(切面):通常是一个类,里面可以定义切入点和通知 JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用. Advice(通知):AOP在特定的切入点上执行的增强处理,有before.after.afterReturning.afterThrowing.around Pointcut(切入点):AOP框架创

Spring4面向切面AOP

AOP(Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的横向多模块统一控制的一种技术.AOP是OOP的补充,是spring框架中的一个重要内容.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率.AOP可以分为静态织入与动态织入,静态织入即在编译前将需织入内容写入目标模块中,这样成本非常高.动态织入则不需要改变目标模块.Spring框架实现了AOP,使用注解