AOP(Aspect-Oriented Programming: 面向切面编程):将那些与业务无关,却为业务模块所共调用的逻辑(例如事务处理、日志管理、权限控制等)封装抽取成一个可重用的模块,这个模块被命名为“切面”(Aspect),便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性;
一、AOP的核心概念
-
切面(Aspect):
- 切面是一个模块,包含了横切关注点的实现。它可以定义在应用程序的多个地方应用的行为。
-
连接点(Join Point):
- 连接点是程序执行的一个点,例如方法调用、对象实例化等。AOP允许在这些连接点上插入切面。
-
通知(Advice):
- 通知是切面在特定连接点执行的动作。根据执行时机的不同,通知可以分为:
- 前置通知(Before):在连接点之前执行。
- 后置通知(After):在连接点之后执行。
- 环绕通知(Around):在连接点前后执行,可以控制连接点的执行。
- 异常通知(After Throwing):在连接点抛出异常时执行。
- 最终通知(After Returning):在连接点正常返回时执行。
- 通知是切面在特定连接点执行的动作。根据执行时机的不同,通知可以分为:
-
切入点(Pointcut):
- 切入点定义了哪些连接点会被通知所影响。它通常通过表达式来指定。
-
织入(Weaving):
- 织入是将切面与其他应用程序类型或对象结合的过程。织入可以在编译时、类加载时或运行时进行。
-
AOP目标对象(Target):
-
就是挖掉功能的方法对应的类生的对象,这种对象是无法直接完成最终工作的
-
-
AOC切面:切点+通知
二、AOP的优点
- 模块化:将横切关注点与业务逻辑分离,提高代码的可读性和可维护性。
- 重用性:切面可以在多个地方重用,减少代码重复。
- 灵活性:可以动态地改变应用程序的行为,而不需要修改业务逻辑代码。
三、AOP的实现
在Java中,AOP通常通过框架实现,如Spring AOP和AspectJ。
1.SpringAOP+AspectJ实现步骤
1.添加依赖,aop与aspectj表达式的依赖
2.创建spring的主配置文件,bean内的命名空间要添加aop的
3.创建业务代码并编写日志记录代码(事务管理代码)
4.将业务层与日志记录层注入spring容器 5.<aop:config>--aop配置 aop:aspect--aop切 面 aop:before--通知内容与通知类型
Xml
<!--注入业务层-->
业务层 Bean 注入:这行代码将业务层的实现类 AccountServiceImp 注入到 Spring 容器中
<bean id="accountServiceImp" class="com.xn.service.AccountServiceImp"></bean>
<!--注入日志记录层(通知)-->
日志记录层 Bean 注入:这行代码将日志记录器 Logger 注入到 Spring 容器中。
<bean id="logger" class="com.xn.util.Logger"></bean>
<!--配置AOP-->
AOP 配置:这部分用于配置 AOP
<aop:config>
<!--配置切面-->
切面配置:里定义了一个切面,引用了日志记录器 logger。
<aop:aspect id="aopAspect" ref="logger">
<!--切点-->切点 dian 定义了所有在 com.xn.service 包下的任意方法的执行。
<aop:pointcut id="dian" expression="execution(* com.xn.service.*.*(..))"/>
<!--通知-->这里使用了环绕通知 arroundMethod,它将在切点方法执行前后执行。
<!-- <aop:before method="beforeMethod" pointcut-ref="dian"></aop:before>-->
<!-- <aop:after-returning method="returnMethod" pointcut-ref="dian"></aop:after-returning>-->
<!-- <aop:after-throwing method="throwMethod" pointcut-ref="dian"></aop:after-throwing>-->
<!-- <aop:after method="afterMethod" pointcut-ref="dian"></aop:after>-->
<aop:around method="arroundMethod" pointcut-ref="dian"></aop:around>
</aop:aspect>
</aop:config>
public class Logger {
public void beforeMethod() {
System.out.println("日志类logger中调用beforeMethod方法进行日志记录");
}
public void returnMethod() {
System.out.println("日志类logger中调用returnMethod方法进行日志记录");
}
public void throwMethod() {
System.out.println("日志类logger中调用thowMethod方法进行日志记录");
}
public void afterMethod() {
System.out.println("日志类logger中调用afterMethod方法进行日志记录");
}
public Object arroundMethod(ProceedingJoinPoint pjp) {
Object obj = null;
try {
System.out.println("环绕通知===前置通知");
//切点方法
Object[] ars = pjp.getArgs();
obj = pjp.proceed(ars);
System.out.println("环绕通知===返回通知");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("环绕通知===异常通知");
} finally {
System.out.println("环绕通知===后置通知");
return obj;
}
}
}
注解
<!--扫描注解-->
<context:component-scan base-package="com.xn"></context:component-scan>
<!--开启spring注解的aop动态代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@Component
@Aspect
public class Logger {
@Pointcut("execution(* com.xn.service.AccountServiceImp.update())")
public void dian(){
}
@Before("dian()")
public void beforeLogger() {
System.out.println("前置通知===>日志类logger中调用beforeMethod方法进行日志记录");
}
@AfterReturning("dian()")
public void returnLogger()
{
System.out.println("返回通知===>日志类logger中调用returnMethod方法进行日志记录");
}
@AfterThrowing("dian()")
public void throwLogger() {
System.out.println("异常通知===>日志类logger中调用throwMethod方法进行日志记录");
}
@After("dian()")
public void afterLogger()
{
System.out.println("后置通知===>日志类logger中调用afterMethod方法进行日志记录");
}
//环绕通知
@Around("dian()")
public Object arroundLogger(ProceedingJoinPoint pjp) {
Object returnobj=null;//保存主业务方法的返回值
try {
//前置通知
System.out.println("环绕通知===前置通知");
//切点方法
Object[] objs = pjp.getArgs();//主业务方法的参数
returnobj = pjp.proceed(objs);//调用主业务方法
System.out.println("环绕通知===>后置通知");
} catch (Throwable e) {
//异常通知
e.printStackTrace();
System.out.println("环绕通知===异常通知");
} finally {
//最终通知
System.out.println("环绕通知===最终通知");
return returnobj;
}
}
}
2.切点表达式配置语法:
execution(修饰符 返回值 包名称.类名称.方法名称(参数列表))
eg: execution(public void com.apesource.service.ServiceImp.findAll())
1.修饰符可以省略代表任意
execution(返回值 包名称.类名称.方法名称(参数列表))
2.返回值可以使用“*”代表任意
execution(* 包名称.类名称.方法名称(参数列表))
3.包名可以使用“*”代表任意名称
execution(* *.*.*.类名称.方法名称(参数列表))
4.包名可以使用“..”代表任意个数
execution(* *...类名称.方法名称(参数列表))
5.类名与方法名可以使用“*”代表任意
execution(* *...*.*(参数列表))
6.参数列表可以使用".."代表任意个数任意类型
execution(* *...*.*(..))
如果有参数
int======>int
String===>java.lang.String