1.面向切面编程中的一些专业术语
横切关注点: 首先一个方法可能有很多关注点,那么多个方法就有多个关注点,但是这些方法的关注点中可能有一两个是所有方法都需要关注的,也就是横跨了多个模块(比如各个模块的增删改查,对数据库进行了操作,我们需要记录日志信息),那么我们就可以将这些点叫做横切关注点。
通知方法: 通常在方法中我们比较关注的地方会做一些操作,不管是记录一些日志还是发送一些通知,这些都是通过方法进行的,并且因为这些方法高度通用,所以我们会将它抽象后定义到一个独立的class中,这个class就是“切面类”,切面类中的方法就是“通知方法”(我觉得叫“横切方法”更贴切)。
连接点: 通知方法可以进行切入的点。连接点一般是横切关注点的一部分,它是在方法的执行过程中才会真正定义的,因为只有定义了拦截器,方法在执行过程中被拦截器拦截到,我们才可能对这个“横切关注点”进行操作。不然你就只能在心里默默地关注它
切入点: 真正调用了“通知方法”的连接点才能被称为“切入点“,哪个连接点会变成“切入点”是由“切入点表达式”决定的,切入点表达式就是用来定义“通知方法”在哪个“目标方法”的什么位置进行切入的
2.spring依赖的一些外部拓展包
我们知道spring的aop底层其实就是动态代理,但是动态代理有一个缺陷就是,被代理类一定要实现接口,而如果我们需要支持不实现接口就能实现面向切面编程,就需要导入三个springAop的外部拓展包,https://repo.spring.io/webapp/#/search/quick/,大部分spring所需要的jar包都可以在这里搜索到。
需要的三个包分别是:
com.springsource.net.sf.cglib-2.2.0.jar
aspectjweaver-1.8.4.jar
aopalliance-1.0.jar
3.基于注解的AOP简单配置
1.导入spring的基础jar包及aop的拓展jar包
2. 创建切面类
创建切面类,并将切面类注入IOC容器,我们知道单纯将对象注入IOC容器中,spring是不能区分这个对象到底是model、service还是一个切面类的,所以我们要使用@Aspect注释来标识切面类,这样spring才能知道到底哪个类是切面类,基本语法:
@Aspect
@Component
public class AspectServiceImpl{
}
3.定义通知方法
切面类创建好了之后,我们需要在切面类中定义 “通知方法”,通知方法最主要的是其切入对象方法的时机确认(也就是什么时候对方法进行拦截,并执行通知操作)
-
spring中提供了5种注解来标识执行时机,分别是:
@Before:方法开始之前执行
@After:方法最终结束时调用,
@AfterReturning:方法正常返回后调用
@AfterThrowing:方法出现异常时执行
@Around:环绕通知,通知方法会将目标方法封装起来,around比较特殊,如果需要在执行around通知方式后能正常执行目标方法,需要在通知方法中进行调用 -
基本定义语法是:@注释(execution(切入点表达式))
//@Before(execution(访问修饰符 返回值类型 方法签名(形参类型列表)))
//语法示例
@Before("execution(public String
com.hao.spring.aop.service.impl.UserServiceImpl.*())")
public void beforeFunction() {
System.out.println("前置方法@Before");
}
//@around比较特殊
//环绕通知
@Around("execution(public String com.hao.spring.aop.service.impl.UserServiceImpl.*())")
public void aroundFunction(ProceedingJoinPoint pj) {
try {
System.out.println("方法前环绕");
pj.proceed();
System.out.println("方法后环绕");
} catch (Throwable e) {
e.printStackTrace();
}
}
- 各个通知方法的执行顺序是“@around前–before前置通知–对目标方法–around后–after后置方法–afterReturning正常返回/AfterThrowing异常返回”
4.开启基于注解的aop功能
切面类和通知方法创建完成后,只需要在applicationContext.xml中开启基于注解的aop面向切面编程的功能就可以了(需要先在头文件中引入aop的名称空间)。
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
5.测试
到这里如果上面的定义没有问题就可以测试一下aop面向切面的执行效果了
4.关于aop的一些详细说明
1.切入点指示符
切入点指示符除了execution还有within、this、target、args
execution:通过方法签名匹配连接点(public是可以不写或者用*号代替的),因为只能切入public的方法
execution(public String
com.hao.spring.aop.service.impl.UserServiceImpl.*(..))
within:通过类型全签名匹配该类下的所有连接点
within(com.hao.spring.aop.service.impl.UserServiceImpl)
this:通过接口类型全签名指定aop代理对象的代理类型,匹配该对象的所有连接点,这个是匹配代理对象的类型
this(com.hao.spring.aop.service.UserService)
target:通过接口类型全签名匹配实现了该接口的对象的所有连接点,这个是匹配实现类的类型
target(com.hao.spring.aop.service.UserService)
this和target都是通过指定接口(因为代理对象和目标对象的联系是通过接口进行的)的类型全签名来匹配连接点的,所以this和target是一样的,但是这两个的切入点表达式是不可以使用通配符的
args:通过指定参数类型来匹配形参列表相同的目标方法
args(java.lang.String)
2.切入点表达式中通配符“*”和“…”的使用
*:可以进行一个或多个字符的代替,例:
通配符 | 功能 | 示例 | 描述 |
---|---|---|---|
* | 指代任意返回值 | execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save()) | |
* | 指代任意单层路径 | execution(public * com.hao.spring.*.service.impl.UserServiceImpl.save()) | 匹配任意包下的实现包中的UserServiceImpl类 |
* | 指代任意单个参数 | execution(public * com.hao.spring.aop.service.impl.ServiceImpl.save()) | 指代以ServiceImpl结尾的方法 |
* | 以开始的包路径可以指代多层路径 | execution(public * *.ServiceImpl.save()) | 所有包下的以ServiceImpl结尾的方法 |
…:匹配任意多个参数或者任意多层路径
通配符 | 功能 | 示例 | 描述 |
---|---|---|---|
… | 指代任意多个参数 | execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(…)) | 这样就可以匹配任意形参类型的方法 |
… | 指代任意多层路径 | execution(public * com.hao.spring…service.impl.UserServiceImpl.save(…)) | 这样就可以匹配spring.aop和spring.aop.aopx下的方法了 |
3.切入点表达式中逻辑运算符符“&&”、“||”、“!”
“&&”同时满足两个表达式才进行切入:
save方法第一个参数是string且第二个参数是int时切入方法
execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(String,*))&&execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(*,int))
“||”满足其中一个表达式就进行切入:
save方法的第一个参数是string或者第二个参数是int就切入
execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(strign,*))||execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(*,int))
“!”不满足的情况就切入:
不切入save方法的参数是int,int的方法
!execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.save(int,int))
4.被切入方法信息的获取
我们可以使用org.aspectj.lang.JoinPoint接口所提供的方法来获取连接点的详细信息,主要方法有:
joinPoint.getArgs():获取方法的参数列表
joinPoint.getSignature():获取方法的完整签名
通过方法签名我们可以获取当前方法的详细信息
String name = signature.getName();//获取方法名
Class declaringType = signature.getDeclaringType();//获取带修饰符的类路径
String declaringTypeName = signature.getDeclaringTypeName();//获取类路径
int modifiers = signature.getModifiers();
joinPoint.getTarget():获取连接点所在的目标对象joinPoint.getThis():获取代理对象本身
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:
java.lang.Object proceed() :通过反射执行目标对象的连接点处的方法;
java.lang.Object proceed(java.lang.Object[] args) :通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。
代码示例:
@Before(value = "execution(public * com.hao.spring.aop.service.impl.UserServiceImpl.*(..))")
public void beforeFunction(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String kind = joinPoint.getKind();
SourceLocation sourceLocation = joinPoint.getSourceLocation();
StaticPart staticPart = joinPoint.getStaticPart();
Object target = joinPoint.getTarget();
Object this1 = joinPoint.getThis();
System.out.println("前置方法@Before");
System.out.println("getArgs()方法:"+args);
System.out.println("getKind()方法:"+kind);
System.out.println("getSourceLocation()方法:"+sourceLocation);
System.out.println("getStaticPart()方法:"+staticPart);
System.out.println("getTarget()方法:"+target);
System.out.println("getThis()方法:"+this1);
Signature signature = joinPoint.getSignature();
System.out.println("signature "+signature);
String name = signature.getName();
Class declaringType = signature.getDeclaringType();
String declaringTypeName = signature.getDeclaringTypeName();
int modifiers = signature.getModifiers();
System.out.println("signature的getName()方法:"+name);
System.out.println("signature的getDeclaringType()方法:"+declaringType);
System.out.println("signature的getDeclaringTypeName()方法:"+declaringTypeName);
System.out.println("signature的getModifiers()方法:"+modifiers);
}
执行结果:
前置方法@Before
getArgs()方法:[Ljava.lang.Object;@4b2bac3f
getKind()方法:method-execution
getSourceLocation()方法:org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@4e08711f
getStaticPart()方法:execution(String com.hao.spring.aop.service.UserService.save(String))
getTarget()方法:com.hao.spring.aop.service.impl.UserServiceImpl@ba2f4ec
getThis()方法:com.hao.spring.aop.service.impl.UserServiceImpl@ba2f4ec
signature String com.hao.spring.aop.service.UserService.save(String)
signature的getName()方法:save
signature的getDeclaringType()方法:interface com.hao.spring.aop.service.UserService
signature的getDeclaringTypeName()方法:com.hao.spring.aop.service.UserService
signature的getModifiers()方法:1025
User保存用户qianghao
后置方法@After
5.抽取可重用切入点表达式
- 声明一个没有实现和返回值的空方法
- 给方法标注@pointcut注解
- 用该方法名替换之前的切入点表达式