术语:
(1) 切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面
是通知(Advice)。实际就是对主业务逻辑的一种增强。
(2) 连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
(3) 切入点(Pointcut)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不
能被增强的。
(4) 目标对象(Target)
目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象 。 上 例 中 的
StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,
不被增强,也就无所谓目标不目标了。
(5) 通知(Advice)
通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理
解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方
法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。
学习aspectj框架的使用。
1)切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强)
在aspectj框架中使用注解表示的。也可以使用xml配置文件中的标签
1)@Before
2)@AfterReturning
3)@Around
4)@AfterThrowing
5)@After
2)表示切面执行的位置,使用的是切入点表达式。
创建Maven工程选择archetype创建,选择图中的组件,next
输入包名和文件名,next
finish创建完成
1)spring依赖
2)aspectj依赖
3)junit单元测试
<!--单元测试依赖--> <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>5.2.5.RELEASE</version> </dependency> <!--aspectj依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
因为aspectj框架的底层实现是通过JDK动态代理实现的,所以必须要声明接口。目标类(需要功能增强的类)
如果创建了接口类则是JKD动态代理(接口实现),如果没有则是CGLIB动态代理(继承实现),当然有接口也可以使用CGLIB动态代理,只要可以被基础就可以使用CGLIB
package com.tmp.service; public interface SomeService { void doSome(); }
package com.tmp.service.impl; import com.tmp.service.SomeService; public class SomeServiceImpl implements SomeService { @Override public void doSome() { System.out.println("执行doSome方法"); } }
切面类里面定义了增强要到的非业务代码。
1)在类的上面加入 @Aspect
2)在类中定义方法, 方法就是切面要执行的功能代码
在方法的上面加入aspectj中的通知注解,例如@Before
有需要指定切入点表达式execution()
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
解释:
modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
declaring-type-pattern 包名类名
name-pattern(param-pattern) 方法名(参数类型和参数个数)
throws-pattern 抛出异常类型
?表示可选的部分
以上表达式共 4 个部分。
execution(访问权限 方法返回值 (方法声明(参数) 异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可
以使用以下符号:
举例: execution(public * *(..)) 指定切入点为:任意公共方法。 execution(* set*(..)) 指定切入点为:任何一个以“set”开始的方法。 execution(* com.xyz.service.*.*(..)) 指定切入点为:定义在 service 包里的任意类的任意方法。 execution(* com.xyz.service..*.*(..)) 指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后 面必须跟“*”,表示包、子包下的所有类。 execution(* *..service.*.*(..)) 指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点 execution(* *.service.*.*(..)) 指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点 execution(* *.ISomeService.*(..)) 指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点 execution(* *..ISomeService.*(..)) 指定所有包下的 ISomeSerivce 接口中所有方法为切入点 execution(* com.xyz.service.IAccountService.*(..)) 指定切入点为:IAccountService 接口中的任意方法。 execution(* com.xyz.service.IAccountService+.*(..)) 指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意 方法;若为类,则为该类及其子类中的任意方法。 execution(* joke(String,int))) 指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参 数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用 全限定类名,如 joke( java.util.List, int)。 execution(* joke(String,*))) 指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类 型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,String s3)不是。 execution(* joke(String,..))) 指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有任意个参数且 参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3) 都是。 execution(* joke(Object)) 指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob) 是,但,joke(String s)与 joke(User u)均不是。 execution(* joke(Object+))) 指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。 不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。
AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解
的方法一般使用 private 的标识方法,即没有实际作用的方法。
/**
* @Pointcut: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的,可以复用的。
* 可以使用@Pointcut
* 属性:value 切入点表达式
* 位置:在自定义的方法上面
*
* 特点:
* 当使用@Pointcut定义在一个方法的上面 ,此时这个方法的名称就是切入点表达式的别名。
* 其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了
*/
@Pointcut(value = "execution(public * com.tmp.service.Impl.SomeServiceImpl.doOther(..))") private void bieming(){ //空方法 }
在使用切入点表达式"execution(public * com.tmp.service.Impl.SomeServiceImpl.doOther(..))"
可以用bieming()
来代替
完整代码
package com.tmp.service; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspectj { @Around(value = "bieming()") public Object doOtherAdd(ProceedingJoinPoint pjt) throws Throwable { //执行目标方法doSome Object res = null; System.out.println("doOther方法执行前"); res = pjt.proceed();//proceed是调用目标函数,所以返回值就是目标函数的返回值 System.out.println("doOther方法执行之后"); //修改函数返回值 res = "修改了返回值"; return res; } @Pointcut(value = "execution(public * com.tmp.service.Impl.SomeServiceImpl.doOther(..))") public void bieming(){ //空方法 } }
这些注解方法都有一个JoinPoint的参数,可以获取目标类,参数,目标方法等数据,这个参数必须要放在方法参数列表的第一位.
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、
目标对象等。
不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该
参数。
/** * 定义方法,方法是实现切面功能的。 * 方法的定义要求: * 1.公共方法 public * 2.方法没有返回值 * 3.方法名称自定义 * 4.方法可以有参数,也可以没有参数。 * 如果有参数,参数不是自定义的,有几个参数类型可以使用。 */
package com.tmp.service; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspectj { @Before("execution(public void com.tmp.service.Impl.SomeServiceImpl.doSome(..))") public void doSomeAdd(JoinPoint jp){ System.out.println(jp.getSignature()); System.out.println("在doSome之前"); } }
/** * @Before: 前置通知注解 * 属性:value ,是切入点表达式,表示切面的功能执行的位置。 * 位置:在方法的上面 * 特点: * 1.在目标方法之前先执行的 * 2.不会改变目标方法的执行结果 * 3.不会影响目标方法的执行。 */
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning
属性就是用于指定接收方法返回值的变量名的。所以,被注解为后 置通知的方法,除了可以包含 JoinPoint
参数外,还可以包含用于接收返回值的变量。该变 量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
/** * 后置通知定义方法,方法是实现切面功能的。 * 方法的定义要求: * 1.公共方法 public * 2.方法没有返回值 * 3.方法名称自定义 * 4.方法有参数的,推荐是Object ,参数名自定义(参数名要与切入点表达式的returning参数的值一样) */
package com.tmp.service; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspectj { @AfterReturning(value = "execution(public void com.tmp.service.Impl.SomeServiceImpl.doOther(..))", returning="res") public void doSomeAdd(JoinPoint jp, Object res){ //res参数为目标方法的返回值 System.out.println("后置通知,在doSome之后"); } }
/** * @AfterReturning:后置通知 * 属性:1.value 切入点表达式 * 2.returning 自定义的变量,表示目标方法的返回值的。 * 自定义变量名必须和通知方法的形参名一样。 * 位置:在方法定义的上面 * 特点: * 1。在目标方法之后执行的。 * 2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能 * Object res = doOther(); * 3. 可以修改这个返回值 * */
与jdk动态代理相似.可以修改行数的返回值
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
/** * 环绕通知方法的定义格式 * 1.public * 2.必须有一个返回值,推荐使用Object * 3.方法名称自定义 * 4.方法有参数,固定的参数 ProceedingJoinPoint */ /* 环绕通知,等同于jdk动态代理的,InvocationHandler接口 * * 参数: ProceedingJoinPoint 就等同于 Method * 作用:执行目标方法的 * 返回值: 就是目标方法的执行结果,可以被修改。 * */
package com.tmp.service; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspectj { @Around(value = "execution(public * com.tmp.service.Impl.SomeServiceImpl.doOther(..))") public Object doOtherAdd(ProceedingJoinPoint pjt) throws Throwable { //执行目标方法doSome Object res = null; System.out.println("doOther方法执行前"); res = pjt.proceed();//proceed是调用目标函数,所以返回值就是目标函数的返回值 System.out.println("doOther方法执行之后"); //修改函数返回值 res = "修改了返回值"; return res; } }
/** * @Around: 环绕通知 * 属性:value 切入点表达式 * 位置:在方法的定义什么 * 特点: * 1.它是功能最强的通知 * 2.在目标方法的前和后都能增强功能。 * 3.控制目标方法是否被调用执行 * 4.修改原来的目标方法的执行结果。 影响最后的调用结果 * * 环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务 *
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。
当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的
名称,表示发生的异常对象。
/** * 异常通知方法的定义格式 * 1.public * 2.没有返回值 * 3.方法名称自定义 * 4.方法有个一个Exception, 如果还有是JoinPoint, */
/** * @AfterThrowing:异常通知 * 属性:1. value 切入点表达式 * 2. throwinng 自定义的变量,表示目标方法抛出的异常对象。 * 变量名必须和方法的参数名一样 * 特点: * 1. 在目标方法抛出异常时执行的 * 2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。 * 如果有异常,可以发送邮件,短信进行通知 * * 执行就是: * try{ * SomeServiceImpl.doSecond(..) * }catch(Exception e){ * myAfterThrowing(e); * } */
增加业务方法:
方法实现:
定义切面:
无论目标方法是否抛出异常,该增强均会被执行
/** * 最终通知方法的定义格式 * 1.public * 2.没有返回值 * 3.方法名称自定义 * 4.方法没有参数, 如果还有是JoinPoint, */
/** * @After :最终通知 * 属性: value 切入点表达式 * 位置: 在方法的上面 * 特点: * 1.总是会执行 * 2.在目标方法之后执行的 * * try{ * SomeServiceImpl.doThird(..) * }catch(Exception e){ * * }finally{ * myAfter() * } * */
增加方法:
方法实现:
定义切面:
声明对象你可以使用注解或者xml配置文件
下面是采用注解的方法(需要在配置文件中加入组件扫描器)
1 )声明目标对象
package com.tmp.service.Impl; import com.tmp.service.SomeService; import org.springframework.stereotype.Component; @Component("someService") public class SomeServiceImpl implements SomeService { @Override public void doSome() { System.out.println("执行doSome方法"); } @Override public String doOther(String name) { System.out.println("执行doOther"); return name; } }
2)声明切面类对象
package com.tmp.service; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspectj { @Around(value = "bieming()") public Object doOtherAdd(ProceedingJoinPoint pjt) throws Throwable { //执行目标方法doSome Object res = null; System.out.println("doOther方法执行前"); res = pjt.proceed();//proceed是调用目标函数,所以返回值就是目标函数的返回值 System.out.println("doOther方法执行之后"); //修改函数返回值 res = "修改了返回值"; return res; } @Pointcut(value = "execution(public * com.tmp.service.Impl.SomeServiceImpl.doOther(..))") public void bieming(){ //空方法 } }
3)声明aspectj框架中的自动代理生成器标签。
自动代理生成器:用来完成代理对象的自动创建功能的。
<aop:aspectj-autoproxy />
这个标签的参数proxy-target-class表示动态代理的方式, “true” 表示CGLIB, false表示JKD. 默认方式是JKD动态代理
完整的配置配置文件
<?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" 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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.tmp.service"/> <aop:aspectj-autoproxy /> </beans>
通过代理执行方法,实现aop的功能增强。
获取的目标对象实际已经变成了代理对象
package com.tmp; import com.tmp.service.SomeService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { @Test public void test02(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); SomeService someService = (SomeService)applicationContext.getBean("someService"); someService.doSome(); } }
获取对象的方法和Spring框架获取对象的方法一样