AOP是JDK动态代理的规范化。
AOP术语解释
术语: (1) 切面(Aspect) 切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面 是通知(Advice)。实际就是对主业务逻辑的一种增强。 (2) 连接点(JoinPoint) 连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。 (3) 切入点(Pointcut) 切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不 能被增强的。 (4) 目标对象(Target) 目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象 。 上 例 中 的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然, 不被增强,也就无所谓目标不目标了。 (5) 通知(Advice) 通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理 解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方 法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。 切入点定义切入的位置,通知定义切入的时间。
Aspectj实现AOP
学习aspectj框架的使用。 1)切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强)
在aspectj框架中使用注解表示的。也可以使用xml配置文件中的标签 1)@Before 2)@AfterReturning 3)@Around 4)@AfterThrowing 5)@After
2)表示切面执行的位置,使用的是切入点表达式。
1. 创建Maven工程
创建Maven工程选择archetype创建,选择图中的组件,next 输入包名和文件名,next finish创建完成
2. 向pom.xml中加入Spring依赖
1)spring依赖 2)aspectj依赖 3)junit单元测试
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
3.创建目标类:接口和他的实现类
因为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方法");
}
}
4.创建切面类:普通类
切面类里面定义了增强要到的非业务代码。 1)在类的上面加入 @Aspect 2)在类中定义方法, 方法就是切面要执行的功能代码 在方法的上面加入aspectj中的通知注解,例如@Before 有需要指定切入点表达式execution()
(1) 切入点表达式(表示需要增强的函数)
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)也是。
使用辅助注解@Pointcut实现切入点表达式的别名
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。 其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解 的方法一般使用 private 的标识方法,即没有实际作用的方法。
/** * @Pointcut: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的,可以复用的。 * 可以使用@Pointcut * 属性:value 切入点表达式 * 位置:在自定义的方法上面 * * 特点: * 当使用@Pointcut定义在一个方法的上面 ,此时这个方法的名称就是切入点表达式的别名。 * 其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了 */
@Pointcut例子
@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 {
Object res = null;
System.out.println("doOther方法执行前");
res = pjt.proceed();
System.out.println("doOther方法执行之后");
res = "修改了返回值";
return res;
}
@Pointcut(value = "execution(public * com.tmp.service.Impl.SomeServiceImpl.doOther(..))")
public void bieming(){
}
}
(2)表示时间的注解
这些注解方法都有一个JoinPoint的参数,可以获取目标类,参数,目标方法等数据,这个参数必须要放在方法参数列表的第一位.
1)@Before 前置通知-方法有 JoinPoint 参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、 目标对象等。 不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该 参数。
方法定义
例子
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之前");
}
}
特点
2)@AfterReturning 后置通知-注解有 returning 属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后 置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变 量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
方法定义
例子
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){
System.out.println("后置通知,在doSome之后");
}
}
特点
3)@Around 环绕通知-增强方法有 ProceedingJoinPoint参数(功能最强大)
与jdk动态代理相似.可以修改行数的返回值
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
方法定义
例子
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 {
Object res = null;
System.out.println("doOther方法执行前");
res = pjt.proceed();
System.out.println("doOther方法执行之后");
res = "修改了返回值";
return res;
}
}
特点
4) [了解]@AfterThrowing 异常通知-注解中有 throwing 属性
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。 当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的 名称,表示发生的异常对象。
定义
特点
例子
增加业务方法:
方法实现: 定义切面:
5) [了解]@After 最终通知
无论目标方法是否抛出异常,该增强均会被执行
方法定义
特点
例子
增加方法:
方法实现:
定义切面:
5.创建spring的配置文件:声明对象,把对象交给容器统一管理
声明对象你可以使用注解或者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 {
Object res = null;
System.out.println("doOther方法执行前");
res = pjt.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>
6.创建测试类,从spring容器中获取目标对象(实际就是代理对象)。
通过代理执行方法,实现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框架获取对象的方法一样
|