目录
一、 AOP 的概念
1.1 概念
1.2 AOP解决了什么:
1.3 AOP的优点
二、AOP 的相关术语
三、使用
3.1 加入依赖
3.2 基于 xml 方式配置
3.3 通配符使用方式
3.4 基于注解方式配置
四、自定义注解配置切面
AOP 称为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
切面:即横向控制,能够将水平散布在各个类中的代码抽取出来,形成一个公共的可重用的模块。
例如一个日志功能,在传统的OOP编程中,更注重从上到下的关系,可日志模块的代码往往水平分布在各个对象层次中,而实际与业务处理毫无关系,这样的做法增加了代码的重复性,不利于代码的维护。
而 AOP 则解决了这个问题,它可以将那些影响了多个类的公共行为封装成一个可重用的模块,称为 Aspect(方面)。
所谓方面,就是将那些与业务无关,却为业务模块所共同调用的逻辑和责任封装起来,便于减少系统的重复性代码,降低模块间的耦合度,也便于后期的维护和拓展
从上面可以总结出,AOP有以下优点:
名称 | 描述 | |
Joinpoint | 连接点 | 在 Spring 中,这些连接点指的是方法,因为 Spring 只支持方法类型的连接点 |
Pointcut | 切入点 | 指我们要对那些 Joinpoint 进行拦截 |
Advice | 通知/增强 | 指拦截到 Joinpoint 之后要做的事情就是通知 通知的类型:前置通知、后置通知、异常通知、最终通知、环绕通知 前后置通知以 Joinpoint 方法调用来界定的 |
Introduction | 引介 | 引介是一种特殊的通知,在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或Field |
Target | 目标对象 | 即被代理对象 |
Weaving | 织入 | 将增强应用到目标对象来创建新的代理对象的过程 Spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入 |
Proxy | 代理 | 一个类被AOP织入增强后,产生的结果代理类 |
Aspect | 切面 | 即切入点和通知的结合 |
用于解析切入点表达式
org.aspectj
aspectjweaver
1.8.10
编写日志类
public class SysLogAspect {
public void printLog() {
System.out.println("Logger类中的printlog方法记录日志");
}
}
配置前置通知
关键字:execution(表达式)
标准的表达式写法:
public void xyz.tom.www.service.impl.AccountServiceImpl.saveAccount()
1)访问修饰符可以省略
void xyz.tom.www.service.impl.AccountServiceImpl.saveAccount()
2)返回值可以使用通配符表示任意返回值
* xyz.tom.www.service.impl.AccountServiceImpl.saveAccount()
3)包名可以使用通配符表示任意包,但是有几级包,就需要写几个
* * *.*.*.*.*.AccountServiceImpl.saveAccount()
4)使用.. 表明当前包及子包
* *..AccountServiceImpl.saveAccount()
5)类名和方法名都可以使用*做通配
* *..*.*()
6)参数列表可以直接写类型:
基本类型写名称: int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
7)可以用..表示有无参数均可,有参数可以是任意类型
全通配写法: * *..*.*(..)
实际开发中切入点表达式的通常写法: 切到业务层实现类的所有方法
* xyz.tom.www.service.impl.*.*(..)
翻译: 【*返回值】 xyz.tom.www.service.impl.【*类名】.【*方法】(【*任意参数】)
修改启动类,使用注解方式启动:
@EnableAspectJAutoProxy
public class main {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
}
}
修改配置类,开启AOP自动代理
@Configuration
@ComponentScan({"com.tom"})
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
编写切面类,实现环绕切面:
@Aspect
@Component
public class SysLogAspect {
@Pointcut("execution(* com.tom.service.impl.*.*(..))")
private void pt1(){}
@Pointcut("execution(public void com.tom.service.impl.IAccountServiceImpl.saveAccount())")
private void pt2(){}
@Before("pt2()")
public void printLog() {
System.out.println("前置通知");
}
@Around("pt1()")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("around 前");
Object object = joinPoint.proceed();
System.out.println("around 后");
return object;
}
}
Spring 支持使用 @annotation 注解实现自定义切面配置,即只有添加了注定注解的方法才会被切面增强
先定义注释类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
String value() default "";
}
@Target、@Retention、@Documented 三个元注解的讲解: https://blog.csdn.net/limj625/article/details/70242773
定义切面类
@Aspect
@Component
public class SysLogAspect {
@Pointcut("@annotation(com.tom.annotation.SysLog)")
public void logPt(){}
@Around("logPt()")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
Object object = joinPoint.proceed();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLog syslog = method.getAnnotation(SysLog.class);
System.out.println("方法名为:" + syslog.value());
return object;
}
}
这里面有一个坑:
若是将自定义注解放在 serviceimpl 的方法上,使用上面的方式获取自定义注解填写的信息,则会报 syslog 是null。
这是由于 serviceimpl 中的方法启动了事务管理,而事务管理又是基于aop的,这就让 @Around 获取到的是动态代理对象所代理出来的方法,代理对象的方法是不会把原来父类中的方法的注解加上去的,所以这里这个注解的对象为null
解决方法如下:
@Aspect
@Component
public class SysLogAspect {
@Pointcut("@annotation(com.tom.annotation.SysLog)")
public void logPt(){}
@Around("logPt()")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
Object object = joinPoint.proceed();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 若是在serviceimpl中定义
Method realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), method.getParameterTypes());
SysLog syslog = realMethod.getAnnotation(SysLog.class);
System.out.println("方法名为:" + syslog.value());
return object;
}
}
使用
@Service("accountService")
public class IAccountServiceImpl implements IAccountService {
@SysLog("保存账户")
public void saveAccount() {
System.out.println("impl saveAccount");
}
}