Spring 学习(二)——面向切面编程AOP


前言
什么是AOP?
软件中,有些行为对于大多数应用都是通用的,但却不是应用重点关注的问题。AOP可以将这些应用与这些通用的行为分离,解除耦合,让应用只关注自身的业务逻辑。而其他方面的问题由其他应用对象来处理。

一、AOP的相关术语

  • joinpoint(连接点)
    类中可以被增强的方法,都可以被称为连接点

  • pointcut(切入点)
    实际被增强的方法

  • advice(通知/增强)
    指拦截到joinpoint之后可以做的事情称之为增强

    • 前置通知:在目标方法被调用之前调用通知功能
    • 后置通知:在目标方法完成之后调用通知、此时不关心方法的输出是什么?
    • 异常通知:在目标方法抛出异常后调用通知
    • 环绕通知:通知包裹了被通知的方法,在通知方法调用之前和调用之后执行自定义的行为。
    • 最终通知:在目标方法成功执行之后调用通知
  • Aspect(切面)
    实现的交叉功能 系统级别的功能,比如日志、事务、权限(只是笼统的概念,类似于接口)

  • Introduction(引入)
    特殊的通知,对原有的对象添加新的方法或者属性、破坏封装。引入允许我们向现有的类添加新的属性和方法。

  • Target(目标对象)
    被通知的对象(通知应用给谁)

  • Weaving(织入)
    织入是将切面应用到目标对象来创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入

编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器,AspectJ的织入编译器就是以这种方式织入切面的
类加载期:切面在目标类被加载到JVM时被织入,这种方式需要特殊的类加载器,它可以子啊目标类被引入应用之前增强该目标类的字节码
运行期:切面在应用运行的某一时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态的创建一个对象,Spring AOP就是以这种方式织入切面的。

  • Proxy(代理)
    将通知应用到目标对象后 创建对的对象

二、Spring 中AOP的实现

**AspectJ的execution()指示器 **
使用execution()指示器来配置切入点,execution() 指示器属于AspectJ的,所以需要在自己的项目中导入AspectJ的jar包。

execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)

在这里插入图片描述
匹配所有类public方法 execution(public .(…))
匹配指定包下所有类方法 execution(* com.tulun.bean.(…)) (不包含子包)
execution(
com.tulun.bean…(…)) (包含包、子包下所有类)
匹配指定类所有方法 execution(
com.tulun.bean.Book.(…))
匹配实现特定接口所有类方法 execution(
com.tulun.bean.Book+.(…))
匹配所有com开头的方法 execution(
com*(…))

AspectJ的 within()指示器
我们可以使用within()指示器来限制匹配

//within(com.tulun.dao.*) 是指com.tulun.dao包下任意类的方法被调用时
execution(* com.tulun.bean.User.show(..))&& within(com.tulun.dao.*)

这里使用 && 操作符把execution()和within()指示器连接在一起形成and 关系(切点必须匹配所有的指示器),类似的我们也可以用or、not。
在XML中&&有特殊的含义,所以在XML配置时,也可以使用and 代替&&,同样的, | | 和!也可以使用。
Spring 的bean()指示器
bean()指示器允许我们在切点表达式中使用Bean 的ID来标识Bean,使用Bean的ID或者Bean 的名字作为参数来限制切点只匹配特定的Bean

execution(* com.tulun.bean.User.show(..)) && bean(user)

还可以使用非操作作为除了指定ID的Bean 以外的其他Bean 应用通知。

1.通过XML配置实现AOP

创建User 类

public class User {
    private  int id;
    public void show(){
        System.out.println("用户");
    }
}

创建增强类

public class Log {
    public void writeLog() {
        System.out.println("写日志");
    }
    public void after(){
        System.out.println("After");
    }
    public void Around(ProceedingJoinPoint joinPoint){
        System.out.println("Around Before");
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        //切入点方法
        System.out.println("Around After");
    }
    
    public void execption(){
        System.out.println("AfterThrowing");
    }

    public void fin(){
        System.out.println("AfterReturning");
    }
}

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

创建bean

 <!--创建对象-->
    <bean id="user" class="com.tulun.bean.User3"/>
    <bean id="log"  class="com.tulun.bean.Log3"/>

开启AOP操作

  <!--开启AOP操作-->
   <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

配置AOP的操作

    <!--配置aop的操作-->
    <aop:config>
        <!--配置切入点(实际被增强的方法),使用表达式进行配置-->
        <aop:pointcut id="pointcut1" expression="execution(* com.tulun.bean.User.show(..))"></aop:pointcut>
        <!--配置切面:将增强用到方法上-->
        <aop:aspect ref="log">
            <!--前置通知-->
           <aop:before method="writeLog" pointcut-ref="pointcut1"></aop:before>
            <!--环绕通知-->
            <aop:around method="Around" pointcut-ref="pointcut1"></aop:around>
            <!--后置通知-->
            <aop:after method="after" pointcut-ref="pointcut1"></aop:after>
            <!--异常通知-->
            <aop:after-throwing method="execption" pointcut-ref="pointcut1"></aop:after-throwing>
            <!--最终通知-->
            <aop:after-returning method="fin" pointcut-ref="pointcut1"></aop:after-returning>
        </aop:aspect>
    </aop:config>

aop配置必须在<aop:config> 的标签里。

  • <aop:pointcut />:配置切入点
  • <aop:aspect />:配置切面
    ref 表示我们要配置哪个Bean 为切面
  • <aop:before />:前置通知
  • <aop:after />:后置通知
  • <aop:around />:环绕通知
  • <aop:after-throwing />:异常通知
  • <aop:after-returning />:最终通知

在每种类型的通知里,method表示要调用切面类的哪个方法作为通知,pointcut-ref表示切点,这是是我们配置好的某个切点的ID。

为通知传递参数
有时候通知的方法也需要参数
给User 增加一个name 的属性,然后增加一个方法

 private String name;
    public void sname(String name){
        this.name = name;
}
//Log 增加方法
public void testargs(String name){
        System.out.println("name = "+name);
    }

配置一个新的切点

<aop:pointcut id="pointcut2" expression="execution(* com.tulun.bean.User.sname(String)) and args(name)"></aop:pointcut>
//对切点进行增强
<aop:before method="testargs" arg-names="name" pointcut-ref="pointcut2"></aop:before>

切点标识了sname()方法,指定了String 参数,然后再args 参数中标识了将name 作为参数。在<aop:before>元素引用了name 参数,标识将该参数传递给Log的testarg()方法。

2.通过注解实现AOP

在增强类上增加注解@Aspect

@Aspect
public class Log {
	//……
}

增加通知的注解,作用在方法上,

  • @Before
  • @After
  • @Around
  • @AfterThrowing
  • @AfterReturning

以上注解都是作用于方法上,该方法为切入点,即要增加功能的方法。@Before、@After、@Around等的value的值是要使用execution的函数表达式, execution函数的使用和AOP配置中使用的作用及效果是一致的

@Before(value = "execution(* com.tulun.bean.User.show(..))")
    public void writeLog() {
        System.out.println("写日志");
    }
    @After(value = "execution(* com.tulun.bean.User.show(..))")
    public void after(){
        System.out.println("After");
    }
    @Around(value = "execution(* com.tulun.bean.User.show(..))")
    public void Around(ProceedingJoinPoint joinPoint){
        System.out.println("Around Before");
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        //切入点方法
        System.out.println("Around After");
    }
    @AfterThrowing(value = "execution(* com.tulun.bean.User.show(..))")
    public void execption(){
        System.out.println("AfterThrowing");
    }

    @AfterReturning(value = "execution(* com.tulun.bean.User.show(..))")
    public void fin(){
        System.out.println("AfterReturning");
    }

参数传递

@Before(value = "execution(* com.tulun.bean.User.sname(..)) && args(name)")
    public void testargs(String name){
        System.out.println("name = "+name);
    }

或者这样

    @Pointcut("execution(* com.tulun.bean.User.sname(..)) && args(name)")
    public void point(String name){
        
    }
    
    @Before("point(name)")
    public void getname(String name){
        System.out.println("getname");   
    }

在这里插入图片描述

三、总结

通过这两个小例子,我们可以这样解释一下AOP。
这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。AOP是Spring提供的关键特性之一。AOP即面向切面编程,是OOP编程的有效补充。使用AOP技术,可以将一些系统性相关的编程工作,独立提取出来,独立实现,然后通过切面切入进系统。从而避免了在业务逻辑的代码中混入很多的系统相关的逻辑——比如权限管理,事物管理,日志记录等等。这些系统性的编程工作都可以独立编码实现,然后通过AOP技术切入进系统即可。从而达到了 将不同的关注点分离出来的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值