SpringAop的一些理解和实现
问题引入(重点)
1 什么是目标对象?
2 怎么找到目标对象需要增强(修改)的方法?
3 如何对要增强(修改)的方法进行处理?
Spring AOP相关概念
Spring的AOP实现底层就是对动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
AOP中的常见术语
Target(目标对象)
要被增强的对象,一般是业务逻辑类的对象。
Proxy(代理)
一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面)
表示增强的功能,就是一些代码完成的某个功能,非业务功能。
是切入点和通知的结合。
Joinpoint(连接点)
所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是
方法(一般是类中的业务方法),因为Spring只支持方法类型的连接点。
Pointcut(切入点)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
被标记为 final 的方法是不能作为连接点与切入点的。因为最终的
是不能被修改的,不能被增强的。
Advice(通知/增强)
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知定义了
增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执
行等。通知类型不同,切入时间不同。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Weaving(织入).
是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态
代理织入,而 AspectJ 采用编译期织入和类装载期织入。
AspectJ 对 AOP 的实现
AspectJ的通知类型
- 1.前置通知
- 2 后置通知
- 3.环绕通知
- 4.异常通知
- 5.最终通知
AspectJ的切入点表达式
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 表达式中就是方法的签名。
PS:表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
符号 | 意义 |
---|---|
* | 0-多个任意字符 |
. . | 用在方法参数中,表示任意个参数;用在包名后,表示当前及其子包路径 |
+ | 用在类名后,表示当前及其子类;用在接口后,表示当前接口及其实现类 |
其中+不常用
示例:
execution(* com.cave.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(* com.cave.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟
“*”,表示包、子包下的所有类。
execution(* com.cave.service.IUserService+.*(..))
指定切入点为:IUserService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,
则为该类及其子类中的任意方法。
注解方式实现AOP
1.创建项目依赖
<dependencies>
<!--spring 核心依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<!--测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!--编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
2.创建spring配置文件引入约束
<?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/springbeans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--在beans标签中 引入AOP和context约束-->
</beans>
3.创建核心业务类
接口
package com.cave.spring.service;
public interface UserService {
void insertUser(String name);
void updateUser(String name);
String findUserById(int id);
void deleteUserById(int id);
}
实现
package com.cave.spring.service;
import org.springframework.stereotype.Component;
@Component
public class UserServiceImpl implements UserService{
@Override
public void insertUser(String name) {
System.out.println("插入操作");
}
@Override
public void updateUser(String name) {
System.out.println("修改操作");
}
@Override
public String findUserById(int id) {
System.out.println("查询操作");
// String str = null;
// str.indexOf(100);
return "返回值:查询完成";
}
@Override
public void deleteUserById(int id) {
System.out.println("删除操作");
}
}
4.定义切面类
package com.cave.spring.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAspect {
/**
* Pointcut注解表示切入点表达式,方法一般声明为私有
* 其他通知可以直接在value中通过方法名称调用
*/
@Pointcut("execution(* com.cave.spring.service.*.*(..))")
private void pointCut(){
}
//前置通知:目标对象方法执行介绍之前
@Before("pointCut()")
public void before(JoinPoint jp){//jp指代要处理的目标对象的方法
System.out.println("前置通知:在目标对象方法执行之前执行:");
System.out.println("拦截到要处理的方法的名字:");
String name = jp.getSignature().getName();//要处理方法的名字
System.out.println("方法名:"+name);
System.out.println("拦截要处理的方法的参数:");
Object[] args = jp.getArgs();
for (Object arg : args){
System.out.println("参数:"+arg);
}
}
//后置通知:目标对象方法执行介绍之后,执行后置通知(可以获得当前方法的返回值)
@AfterReturning(value = "pointCut()",returning = "result")
public void afterReturning(Object result){
System.out.println("后置通知:获得目标对象方法的返回值");
System.out.println(result);
}
//异常通知:只有当目标对象的方法,出现异常,才执行异常通知,反之没有异常,不执行该通知
@AfterThrowing(value = "pointCut()",throwing = "ex")
public void exception(JoinPoint joinPoint,Throwable ex){
System.out.println(joinPoint.getSignature()+"方法抛出异常");
System.out.println(ex.getMessage());
}
//环绕通知:方法执行前和执行后都执行该通知
// 环绕通知携带ProceedingJoinPoint类型的参数,环绕通知类似于动态代理的全过程
// ProceedingJoinPoint类型的参数可以决定是否执行目标方法
//环绕通知必须有返回值,返回值即为目标方法的返回值
@Around(value = "pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("环绕通知:目标方法执行之前执行");
Object proceed = point.proceed();
System.out.println("环绕通知:目标方法执行之后执行");
return proceed;
}
//最终通知:在目标方法执行后(无论是否发生异常),执行的通知
@After(value = "pointCut()")
public void after(){
System.out.println("最终通知:在目标方法执行后(无论是否发生异常)执行");
}
}
5.在spring.xml配置文件中开启包扫描和注册aspectj的自动代理
<!--包扫描-->
<context:component-scan base-package="com.cave.spring.service,com.cave.spring.aop"></context:component-scan>
<!--开启注解AOP的使用-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
<!--aop:aspectj-autoproxy的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的,
是基于 AspectJ 的注解适配自动代理生成器。
其工作原理是,aop:aspectj-autoproxy通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找
到目标类的目标方法,再由通知类型找到切入的时间点。-->
6.测试类
public class test01 {
@Test
public void test01(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) applicationContext.getBean("userServiceImpl");
userService.findUserById(100);
}
}
7.结果
问题导入(答案)
1.什么是目标对象?
Target(目标对象):要被增强(修改)的方法的对象。企业中,一般以业务类的对象作为目标对象。
2.怎么找到目标对象中需要增强(修改)的方法?
切入点
Joinpoint(连接点)和PointCut(切入点)
Joinpoint(连接点):指代需要增强(修改)的目标对象中的特定方法。
PointCut(切入点):切入点就是表达式,用来找到对应的要增强(处理)的目标对象中的特定的方法。(即怎么找到连接点)
3.如何对要增强(修改)的方法进行处理?
通过Advice(通知)和Aspect(切面类)
Advice(通知):指定处理方法位置(PointCut),具体处理方法
Aspect(切面类):Advice所在的类就是切面类