Spring<03>Spring的AOP

Spring<03>Spring的AOP

1. Spring的AOP简介

1.1 什么是AOP

​ AOP是Aspect Oriented Programming的缩写,也就是面向切面编程,是一个种编程思想,也是一种技术规范。AOP思想不是Spring的原创,Spring只是借鉴使用了这个思想、技术规范。

​ AOP可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下为程序统一添加/增强功能的一种技术。

​ 如果说面向对象编程是关注将需求功能划分为不同的并且相对独立、封装良好的类,并让它们有着属于自己的行为,依靠继承和多态等来定义彼此的关系的话;那么面向切面编程则是希望能够将通用需求功能从不相关的类当中抽离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需要修改这个行为即可。

​ 通过上面的一段话,大家有没有感觉到面向对象编程思想好像一个父类的思想,而面向切面编程思想更像是一个接口的思想

1.2 AOP有什么作用和优势

作用:在程序运行期间,在不修改源代码的情况下对目标方法(功能)进行增强

优势:提高代码复用性,提高开发效率,解耦合,便于维护

解耦合:将日志记录,性能统计,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

​ AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点(如Java)。可以简单的把AOP理解成一种横切增强操作,就像临时查酒驾一样。在程序里面的横切行为大致有如下:

日志记录、跟踪、优化和监控

性能的统计、优化

事务的处理

持久化

异常捕捉及处理

资源池(如数据库连接池)的管理

系统统一的认证、权限管理等

针对具体行业应用的横切行为

1.3 AOP的底层实现

​ AOP的底层是通过Spring提供的动态代理技术实现的。

​ 在运行期间,Spring通过动态代理技术动态的生成新的代理对象,代理对象的方法在被调用执行时被增强。调用代理对象方法时,会根据实际情况调用执行增强的代码,也会根据业务逻辑执行原目标对象的方法,从而完成目标对象功能的增强。目标对象的原方法和增强时添加的功能(代码或者方法)共同构成了增强后的方法(功能)。

1.4 AOP的动态代理技术

代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口(Java原生支持的模式)。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态“织入”。

​ 使用代理好处还不止这些,一个项目如果依赖另一个项目给的接口,但是另一个项目的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要一一修改业务代码。从这个意义上说,所有调外界的接口,我们都可以这么做,不让外界的代码对我们的代码有侵入,这叫防御式编程。代理其他的应用可能还有很多。

​ 上述例子中,类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,jdk动态代理和cglib动态代理:

​ JDK动态代理:jdk原生支持的,是基于接口的动态代理技术

​ cglib动态代理:第三方的,是基于父类的动态代理技术

Spring的AOP使用了上述两种代理技术,默认使用jdk的动态代理,如果被代理类没有接口,则使用cglib动态代理。

1.5JDK动态代理实现思路及代码

JDK动态代理实实现逻辑:

​ 目标类必须是实现了一个接口,代理类是在内存中创建的一个同样实现了上述接口的类(目标类和代理类是兄弟关系)。因为实现了相同的接口,所以两者方法个数及每个方法对应的声明一致。在生成的代理类中做目标类方法的增强。

JDK动态代理实现步骤:

① 创建目标类对象(该类需要实现一个接口)

② 通过Proxy.newProxyInstance(classLoader, interfaces, invocationHandler)创建动态代理对象

​ 该方法的返回值即为动态代理对象

​ 在创建该对象的时候,需要明确方法的增强逻辑,增强逻辑的代码需要写在实现了InvocationHandler接口的类的invoke()方法中。可以实现前置、后置、参数列表和返回值的增强。

③ 使用创建好的动态代理对象调用增强后的方法

JDK动态代理实现伪代码:

//第一步
目标对象类 目标对象 = new 目标对象类();
//第二部
目标对象类接口 动态代理对象 = Proxy.newProxyInstance(目标对象.getClass().getClassLoader(),
											  目标对象.getClass().getInterfaces(),
   new InvocationHandler(){
                  
       public Object invoke(动态代理对象, 目标对象的某个被增强的方法的包装类对象, 被增强方法的参数){
           
           //1)前置增强(在原方法运行前,添加代码,以增强/修改方法体)
           ..........
           
           //调用目标对象的要被增强的方法(该方法是目标对象原汁原味未被增强的方法)
           //2)这里可以增强(修改)参数列表
           被增强方法的返回值 = Method.invoke(目标对象, 被增强方法的参数列表);
           
           //3)后置增强(在原方法运行后,添加代码,以增强/修改方法体)
           ........
           
           //4)这里可以增强(修改)原方法的返回值
           return 被增强方法的返回值 或 null;
       }                                   
   }))

//第三步
动态代理对象.增强的方法(方法名与增强前相同)();

JDK动态代理代码:

①编写目标类接口

public interface TargetInterface {
	public void method();
}

② 编写目标类

public class Target implements TargetInterface {
	@Override
	public void method() {
		System.out.println("Target running....");
	}
}

③ 使用JDK动态代理

Target target = new Target(); //创建目标对象
//创建代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass()
.getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("前置增强代码...");
		Object invoke = method.invoke(target, args);
		System.out.println("后置增强代码...");
		return invoke;
	}
});

④ 测试调用代理对象的方法

// 测试,当调用接口的任何方法时,代理对象的代码都修改
proxy.method();

1.6 cglib动态代理

cglib动态代理实实现逻辑:

“代理”的目的是构造一个和被代理的对象(目标对象)有同样行为(行为相同,但行为能力已被增强)的对象。一个对象的行为是在类中定义的,对象只是类的实例,所以构造代理,不一定非得通过持有、包装对象这一种方式。

与JDK动态代理中代理类和目标类需要实现同一个接口不同,cglib动态代理中代理类不需要和目标类实现同一个接口,而是代理类继承了目标类,并重写了目标类中的方法。通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。当然这些增强都是程序运行时在内存中进行的。

CGLIB(CodeGenerationLibrary)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。

关于代理的总结:

  1. 对有接口业务类 使用 JDK动态代理, 没有接口业务类 使用 cglib 动态代理

  2. 优先对接口代理,便于解耦合

  3. 标记为final的方法,不能被代理,因为无法进行覆盖(主要指cglib代理)。

cglib动态代理实现步骤:

① 创建增强器

② 使用增强器设置父类(父类就是目标类)enhancer.setSuperclass(Class clazz)

③ 使用增强器设置回调(该方法无返回值)enhancer.setCallback(Callback callback)

​ 在回调时需要传入一个Callback接口的对象,通过创建实现了MethodInterceptor接口的匿名内部类的对象来实现。创建对象时,需要明确方法的增强逻辑,增强逻辑的代码需要写在实现了MethodInterceptor接口的类的intercept()方法中。可以实现前置、后置、参数列表和返回值的增强。

④ 使用增强器创建对象,返回的就是代理类对象(子类对象)enhancer.creat()

cglib动态代理伪代码:

//第一步
增强器类 增强器引用 = new 增强器类();

//第二步
增强器引用.设置父类(父类字节码文件对象);

//第三步
增强器引用.设置回调(new MethodInterceptor(){
                  
       public Object intercept(动态代理对象,父类方法包装对象,被增强方法的参数, 子类增强方法包装对象){
           
           //1)前置增强(在原方法运行前,添加代码,以增强/修改方法体)
           ..........
           
           //调用目标对象的要被增强的方法(该方法是目标对象原汁原味未被增强的方法)
           //2)这里可以增强(修改)参数列表
           被增强方法的返回值 = 父类增强方法包装对象.invokeSuper(动态代理对象, 被增强方法的参数列表);
           
           //3)后置增强(在原方法运行后,添加代码,以增强/修改方法体)
           ........
           
           //4)这里可以增强(修改)原方法的返回值
           return 被增强方法的返回值 或 null;
       }                                   
   }))

//第四步
动态代理对象.增强的方法(方法名与增强前相同)();

cglib动态代理代码实现:

package com.itheima.cglibproxy;


import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy {

    public static void main(String[] args) {

        //1. 创建增强器
        Enhancer enhancer = new Enhancer();

        //2. 设置父类(父类就是目标类)
        enhancer.setSuperclass(Lenovo.class);

        //3. 设置回调
        enhancer.setCallback(new MethodInterceptor() {
            /*
            	proxy	代理类对象,即this
            	method	父类方法包装对象
            	args	被增强方法的参数
            	methodProxy	子类增强方法包装对象
            */
            public Object intercept(Object proxy, Method method,
                                    Object[] args, MethodProxy methodProxy) throws Throwable {

                System.out.println("前置增强");
                System.out.println(method);
                System.out.println(methodProxy);
			   //这里建议使用methodProxy.invokeSuper(proxy, args)
                //不要使用method对象,避免出现递归调用造成内存溢出
                Object invoke = methodProxy.invokeSuper(proxy, args);

                System.out.println("后置增强");
                return invoke;
            }

        });
		//使用增强器创建对象
        Target target = (Target) enhancer.create();
        target.method();
    }
}

1.7 AOP相关概念

SpringAOP的底层实现就是对上面动态代理的代码进行了封装,封装后我们只需要编写需要关注的部分的代码,然后通过配置的方式实现指定目标方法的增强。

​ 在正式学习AOP的操作之前,我们需要学习并理解AOP的相关术语。

  • 常用术语如下:
  • Target(目标):被代理的类
  • Proxy(代理):一个类被AOP(织入)增强后,就产生一个代理类
  • Joinpoint(连接点):所有被拦截到的点,在Spring中就是指所有被拦截到的方法,因为Spring只支持方法类型的拦截点(可以被增强的方法叫做连接点)
  • ☆Pointcut(切点):拦截到并增强的连接点(拦截到并被增强的方法叫做切点)
  • ☆Advice(通知/增强):拦截到连接点之后所要做的事情(在拦截到并被增强的方法上增强的代码叫做增强/通知)
  • ☆Aspect(切面):切点 + 通知称为切面(目标方法+增强方法 = 切面)
  • Weaving(织入):是一个过程,将增强应用到切点的过程叫做织入。Spring采用动态代理织入,AspectJ使用编译器织入和类装载期织入。
    • AspecJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对AspectJ的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入
    • 早期Spring AOP开发使用的是自己的AOP技术,目前阶段 Spring AOP开发通常使用 AspectJ 技术来实现。

1.8 AOP开发明确的事项

​ 1)需要编写的内容

  • ​ 编写核心业务代码:目标类的目标方法

  • 编写切面类,切面类中有通知(增强的方法,增强的方法需要写在切面类内部)

  • 在配置文件中,配置织入关系(将某些通知与某些连接点结合)

    2)Spring使用AOP技术完成的工作

    ​ Spring框架监控切点方法的执行。通过配置指定那些方法是切点,一旦监控到切点方法被执行,就使用动态代理创建(切点所属类的)目标对象的代理对象;然后根据通知(增强)类别(前置增强、后置增强),在代理对象的对应位置,将通知(增强)的内容织入,完成完整的代码逻辑运行。

    3)AOP底层使用的代理方式

    ​ Spring会根据目标类的是否有父接口来选择代理方式。

    ​ 有父接口:选择使用JDK动态代理

    ​ 无父接口:选择使用cglib动态代理

1.9 知识要点

​ AOP:面向切面编程

​ AOP底层实现原理:基于JDK的动态代理 和 基于CGLIB的动态代理

​ AOP的重点概念:

Pointcut(切点):目标方法,被拦截到并要做增强的方法
Advice(通知/增强):曾庆方法,被拦截到并要做增强的方法的具体增强内容,Spring要求封装到一个方法中
Aspect(切面):目标方法 + 增强方法
Weaving(织入):增强应用到切点的过程

​ 开发明确事项:

谁是切点	-	切点表达式的配置
谁是通知	-	切面类中书写增强方法
怎么织入	-	将切点和通知进行配置

2. 基于XML的AOP开发

2.1 QuickStart

思路:

​ ① 导入AOP相关坐标

​ ② 编写目标类和其实现的接口(内含切点)

​ ③ 编写切面类(内含通知/增强方法)

​ ④ 将目标类和切面类的创建权交给Spring容器

​ ⑤ 在ApplicationContext.xml中配置织入关系

​ 告诉Spring框架,哪些方法(切点)需要进行哪些增强(前置、后置……)

​ ⑥ 测试

代码:

​ ① 导入AOP相关坐标

<Dependencies>
    <!--导入spring的context坐标,因为context包含aop -->
    <dependency>
    	<groupId>org.springframework</groupId>
    	<artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    
    <!-- Spring整合junit -->
    <dependency>
    	<groupId>org.springframework</groupId>
    	<artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    
	<!-- 使用AspectJ方式进行配置,所以需要导入AspectJ织入相关坐标 -->
    <dependency>
    	<groupId>org.aspectj</groupId>
    	<artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    
    <!-- Spring整合junit,导入Junit坐标-->
    <dependency>
    	<groupId>junit</groupId>
    	<artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</Dependencies>

​ 为了方便使用aop标签相关提示,需要在applicationContext.xml中添加aop的命名空间

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

​ ② 编写目标类和其实现的接口(内含切点)

package com.itheima.xmlaop;

//目标类接口,该情况下Spring将使用JDK动态代理完成AOP
public interface TargetInterface {
    public void save();
}

package com.itheima.xmlaop;

import com.itheima.xmlaop.TargetInterface;

//目标类
public class Target implements TargetInterface {
    //切点,即目标方法
    public void save() {
        System.out.println("save runnig.....");
    }
}

​ ③ 编写切面类(内含通知/增强方法)

package com.itheima.xmlaop;

//切面类
public class MyAspect{

    //通知,增强方法
    public void before(){
        System.out.println("前置增强");
    }
}

​ ④ 将目标类和切面类的创建权交给Spring容器

<!-- 配置目标对象,配置完以后,target仅仅是一个普通的bean,需要进一步配置才能成为目标对象 -->
<bean id="target" class="com.itheima.xmlaop.Target"></bean>

<!-- 配置切面对象,配置完以后,myAspect仅仅是一个普通的bean,需要进一步配置才能成为切面对象-->
<bean id="myAspect" class="com.itheima.xmlaop.MyAspect"></bean>

​ ⑤ 在ApplicationContext.xml中配置织入关系

​ 告诉Spring框架,哪些方法(切点)需要进行哪些增强(前置、后置……)

<!-- 上面的bean需要经过下面的织入配置后才能真正的进入角色-->
<aop:config>
    <!-- 配置切面,并指定切面类为MyAspect(内含增强内容)的bean -->
    <aop:aspect ref="myAspect">
        <!-- 配置切点为Target类的save方法,通知类型和通知(增强的类型和增强方法) -->
        <aop:before method="before"
                    pointcut="execution(public void com.itheima.xmlaop.Target.save())"/>
    </aop:aspect>
</aop:config>

​ ⑥ 测试代码

package com.itheima.xmlaop;

import com.itheima.xmlaop.TargetInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

//使用Spring整合Junit进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {

    //需要使用目标对象,将其注入该类
    @Autowired
    private TargetInterface target;

    @Test
    public void TestSave() {
        //我们只需要调用目标对象的目标方法即可,Spring框架会帮助我们通过动态代理实现增强
        target.save();
    }
}

2.2 XML配置AOP详解

1)标签解释:
标签说明
<aop:config> <</aop:config>>配置织入,在Spring容器中的bean需要被配置织入后才能真正的发挥相应作用。暂时不用为该标签配置属性,需要为其配置子标签。
<aop:aspect ref="">配置切面,并使用ref指向切面类在Spring容器的bean,该类中含有增强的内容。暂时不用为该标签配置属性,需要为其配置子标签。
<aop:before method="" pointcut=""/>配置切点和通知类型(增强类型)。method属性的表示通知(增强)的具体方法;pointcut属性表示切点,其值需要使用切点表达式。类型还包括aop:after-returnning等。
2)切点表达式:
execution([访问权限修饰符] 返回值类型 包名.类名.方法名(参数类型列表))
  • 访问权限修饰符可以省略
  • 返回值类型、包名、类名、方法名均可以使用"*"代表任意
  • 包名与类名之间如果有一个点“.”表示当前包下的类,两个点“…”表示当前包及其子包下的类
  • 参数类型列表可以使用两个点“…”表示任意个数、任意类型的参数类型列表
execution(public void com.itheima.aop.Target.method())
execution(void com.itheima.aop.Target.*(..))
execution(* com.itheima.aop.*.*(..))	//使用频率较高
execution(* com.itheima.aop..*.*(..))
execution(* *..*.*(..))
3)AOP通知(增强)的类型:

通知的配置语法

<aop:通知类型 method="切面类中的方法名" pointcut="切点表达式" />

通知(即:增强)的类型

通知类型标签说明
前置通知<aop:before>用于配置前置通知,增强方法先于目标方法执行
后置通知<aop:after-returnning>用于配置后置通知,目标方法先于增强方法执行
环绕通知<aop:round>用于配置环绕通知,目标方法前后都执行增强方法
异常抛出通知<aop:throwing>用于配置异常抛出通知,目标方法抛出异常时执行增强方法
最终通知<aop:after>用于配置最终通知,无论目标方法是否正常结束均执行增强方法

上述通知的执行细节及应用场景,见资料/Spring通知类型详解.md。

应用场景:

通知类型常见应用场景
环绕通知控制事务 权限控制
后置通知记录日志(方法已经成功调用)
异常通知异常处理 控制事务
最终通知记录日志(方法已经调用,但不一定成功)
4)切点表达式的抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在切面中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。

好处是方便维护。

<aop:config>
	<!--引用myAspect的Bean为切面对象-->
	<aop:aspect ref="myAspect">
        <!-- 将切点抽取出来,单独配置 -->
		<aop:pointcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"/>
        <!-- 后续如有使用到上述切点,直接使用pointcut-ref引用即可 -->
		<aop:before method="before" pointcut-ref="myPointcut"></aop:before>
	</aop:aspect>
</aop:config>

2.3 知识识要点

  • aop织入的配置
<aop:config>
    <!-- 切面配置 -->
    <aop:aspect ref=“切面类”>
        <aop:pointcut id="切点id" expression="切点表达式"></aop:pointcut>
        <aop:before method=“通知方法名称” pointcut-ref=“切点id"></aop:before>
    </aop:aspect>
</aop:config>
  • 通知的类型:前置通知、后置通知、环绕通知、异常抛出通知、最终通知
  • 切点表达式的写法:
execution([访问权限修饰符] 返回值类型 包名.类名.方法名(参数类型列表))

3. 基于注解的AOP开发

3.1 QuickStart

思路:

​ ① 导入AOP相关坐标

​ ② 编写目标类和其实现的接口(内含切点),并在实现类上添加@Repository注解

​ ③ 编写切面类(内含通知/增强方法),并在类上添加@Component注解

​ ④ 通过配置组件扫描,将目标类和切面类的创建权交给Spring容器

​ ⑤ 在切面类中使用注解配置织入关系

​ 告诉Spring框架,哪些方法(切点)需要进行哪些增强(前置、后置……)

​ 需要在切面类的增强方法上添加相应的注解

​ ⑥ 在配置文件中开启组件扫描和AOP的自动代理

​ ⑦ 测试

代码:

​ ① 导入AOP相关坐标

<Dependencies>
    <!--导入spring的context坐标,因为context包含aop -->
    <dependency>
    	<groupId>org.springframework</groupId>
    	<artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    
    <!-- Spring整合junit -->
    <dependency>
    	<groupId>org.springframework</groupId>
    	<artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    
	<!-- 使用AspectJ方式进行配置,所以需要导入AspectJ织入相关坐标 -->
    <dependency>
    	<groupId>org.aspectj</groupId>
    	<artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    
    <!-- Spring整合junit,导入Junit坐标-->
    <dependency>
    	<groupId>junit</groupId>
    	<artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</Dependencies>

​ 为了方便使用aop标签相关提示,需要在applicationContext.xml中添加aop的命名空间

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

​ ② 编写目标类和其实现的接口(内含切点),并在实现类上添加@Repository注解

package com.itheima.xmlaop;

//目标类接口,该情况下Spring将使用JDK动态代理完成AOP
public interface TargetInterface {
    public void save();
}

package com.itheima.xmlaop;

import com.itheima.xmlaop.TargetInterface;

//目标类
@Repository("target")   //该对象的创建权交给Sprin容器
public class Target implements TargetInterface {
    //切点,即目标方法
    public void save() {
        System.out.println("save runnig.....");
    }
}

​ ③ 编写切面类(内含通知/增强方法),并在类上添加@Component注解

package com.itheima.xmlaop;

//切面类
@Component("myAspect")  //该对象的创建权交给Sprin容器
@Aspect                 //标注该类为切面类
public class MyAspect{

    //通知,增强方法
    public void before(){
        System.out.println("前置增强");
    }
}

​ ④ 将目标类和切面类的创建权和依赖关系管理交给Spring容器

<!-- 使用注解方式,bean的依赖注入交给Spring容器 -->
<context:component-scan base-package="com.itheima"/>

​ ⑤ 在ApplicationContext.xml中配置织入关系

​ 告诉Spring框架,哪些方法(切点)需要进行哪些增强(前置、后置……)

​ 需要在切面类的增强方法上添加相应的注解

    <!-- 使用注解维护织入关系时,仅需要在配置文件中开启AOP自动代理 -->
    <aop:aspectj-autoproxy/>

​ 但是需要在切面类中的增强方法中配置指定的注解

package com.itheima.xmlaop;

//切面类
@Component("myAspect")  //该对象的创建权交给Sprin容器
@Aspect                 //标注该类为切面类
public class MyAspect{

    //通知,增强方法
    //@Before("切点表达式")
    @Before("execution(* com.itheima.annotationaop.Target.*(..))")
    public void before(){
        System.out.println("前置增强");
    }
}

⑥ 测试代码

package com.itheima.annotationaop;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

//使用Spring整合Junit进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-annotation.xml")
public class AnnotationAopTest {

    //需要使用目标对象,将其注入该类
    @Autowired
    private TargetInterface target;

    @Test
    public void TestSave() {
        //我们只需要调用目标对象的目标方法即可,Spring框架会帮助我们通过动态代理实现增强
        target.save();
    }
}

3.2 XML配置AOP详解

1)注解通知的类型

通知的配置语法:@通知注解(“切点表达式")

通知类型注解说明
前置通知@Before用于配置前置通知,增强方法先于目标方法执行
后置通知@AfterReturning用于配置后置通知,目标方法先于增强方法执行
环绕通知@Around用于配置环绕通知,目标方法前后都执行增强方法
异常抛出通知@AfterThrowing用于配置异常抛出通知,目标方法抛出异常时执行增强方法
最终通知@After用于配置最终通知,无论目标方法是否正常结束均执行增强方法
2) 切点表达式的抽取

同 xml配置aop 一样,我们可以将切点表达式抽取。

抽取方式是在切面类内定义一个空方法,在该方法上使用**@Pointcut注解定义切点表达式**,然后在通知(增强)方法注解中使用类名.方法的方式进行引用。具体如下:

@@Component("myAspect")
@Aspect
public class MyAspect {
    @Before("MyAspect.myPoint()")
    public void before(){
        System.out.println("前置代码增强.....");
    }
    @Pointcut("execution(* com.itheima.aop.*.*(..))")
    public void myPoint(){}
}
3.3 知识要点
  • 注解aop开发步骤

①使用@Aspect标注切面类

②使用@通知注解标注通知方法,织入关系

③在配置文件中配置aop自动代理<aop:aspectj-autoproxy/>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值