AOP
概述
OOP(Object Oriented Programming,面向对象编程),本质是以建立模型体现出来的抽象思维过程和面向对象的方法,其适合定义纵向关系,但不适合定义横向关系(如日志、事务、权限认证等),因此导致大量代码重复,不利于程序的可重用性。
AOP(Aspect Oriented Programming,面向切面编程),通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。即将非业务逻辑代码(如日志、事务、权限认证等)封装到一个可重用模块,并且与业务逻辑代码分离,还能够织入到业务方法中,从而降低模块之间的耦合度,提高程序的可重用性,提高开发的效率。另外,AOP 是 OOP 的补充和完善。
简单示例:
OOP日志设计
首先在 pom.xml 文件中添加以下配置:
xml
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
然后定义一个日志类,声明三个方法并对业务代码进行日志跟踪记录:
java
package cn.edu.springdemo.aopDemo;
import org.apache.log4j.Logger;
public class LogDemo {
//获取日志记录器,控制日志信息
private static Logger logger = Logger.getLogger(LogDemo.class);
public static void main(String[] args) {
device1("device-");
add(520,521);
subtract(521,520);
}
public static String device1(String str1){
logger.info("device1方法执行前!!!"); //业务无关代码
str1 = str1 + "Executing";
logger.info("device1方法执行后,运行结果为:" + str1); //业务无关代码
return str1;
}
public static int add(int a , int b){
logger.info("add方法执行前!!!");
int c = a + b ;
logger.info("add方法执行后,运行结果为:" + c);
return c;
}
public static int subtract(int a , int b){
logger.info("subtract方法执行前!!!");
int c = a - b ;
logger.info("subtract方法执行后,运行结果为:" + c);
return c;
}
}
接着,log4j 日志框架可以使用 xml 或 properties 的形式进行配置,一般使用 properties 的形式并命名为 log4j.properties ,添加以下配置:
xml
#日志级别,分为八个级别( Off-关闭日志记录 > Fatal-严重错误 > Error-错误 > Warn-警告 > Info-运行信息 > Debug-调试 > Trace-低级信息 > All-所有日志记录)
#日志级别越高,过滤的信息越多
#配置根节点
log4j.rootLogger=Debug,stdout,D
#配置控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
##输出格式(%d %p [%1] %m %n------日期时间 类 路径 信息 换行)
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%l] %m %n
#配置文件输出
log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.Append=true
log4j.appender.D.File=./log4j.log
log4j.appender.D.Threshold=Debug
#输出格式
log4j.appender.D.layout=org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%d %p [%l] %m %n
最后测试运行,在项目根目录下生成 log4j.log 日志文件,日志文件与控制台结果如图:
上述可以看出 OOP 设计存在业务逻辑代码与业务无关代码混在一起,且业务无关代码重复。而 AOP 利用横切技术将业务无关代码从业务逻辑代码中分离,并独立到非指导业务逻辑的方法后,再织入到业务方法中。
知识点:
横切:多个类的公共行为封装到一个可重用模块,并命名为切面(Aspect)
AOP 把软件系统分为:核心关注点(业务处理部分)与横切关注点(业务无关部分)
AOP 的底层原理是动态代理,由 Spring IoC 容器负责生成和管理。默认使用 JDK 动态代理,当没有接口时,使用 CGLIB 动态代理
代理模式:通过给目标对象提供一个代理对象,并由代理对象控制目标对象的引用,从而实现在目标对象基础上增强额外功能,类似中介
动态代理:没有代理类,运行时在 JVM 里创建代理对象
AOP编程:1.定义业务组件
2.定义切入点,横切一个或多个业务组件
3.定义增强处理,为业务组件增加额外处理动作
简单示例:
基于上面例子的 AOP 日志设计
首先在 pom.xml 文件中添加以下配置:
xml
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
然后定义一个日志接口,声明三个方法:
java
package cn.edu.springdemo.aopDemo;
public interface LogAop {
public String device1(String str1);
public int add(int a , int b);
public int subtract(int a , int b);
}
再定义该接口实现类:
java
package cn.edu.springdemo.aopDemo;
import org.springframework.stereotype.Component;
//核心关注点的集合
@Component("logAop")
public class LogAopImpl implements LogAop {
@Override
public String device1(String str1) {
str1 = str1 + "Executing";
return str1;
}
@Override
public int add(int a, int b) {
int c = a + b ;
return c;
}
@Override
public int subtract(int a, int b) {
int c = a - b ;
return c;
}
}
接着,定义一个切面类,可以看出业务代码与业务无关代码已分离:
注解说明:
@Before():前置通知,切入目标方法执行前执行
@After():后置通知,切入目标方法执行后执行
@AfterReturning:返回通知,切入目标方法返回结果后执行
@AfterThrowing:异常通知,切入目标方法抛出异常后执行
@Around():环绕通知,围绕方法执行
java
package cn.edu.springdemo.aopDemo;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
//定义一个切面类,横切关注点的集合(由 Spring IoC 容器管理)
@Aspect
@Component
@Order(1) //多个切面类时,值小先执行
public class LoggerAspect {
private Logger logger = Logger.getLogger(this.getClass());
//aopDemo包下的所有类的任何修饰符和返回值类型以及任意参数 [修饰符 返回值类型 方法名(参数)]
@Pointcut("execution(* cn.edu.springdemo.aopDemo.*.*(..))") //公共表达式
public void commonExpression(){} //提取出来共用
@Before("commonExpression()")
public void beforeDevice(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName(); //获取方法名
logger.info(name + "方法执行前!!!");
}
//略显多余,注释
// @After("commonExpression()")
// public void afterDevice(JoinPoint joinPoint){
// String name = joinPoint.getSignature().getName(); //获取方法名
// List<Object> args = Arrays.asList(joinPoint.getArgs()); //获取参数
// logger.info(name + "方法执行后,参数为:" + args);
// }
@AfterReturning(value = "commonExpression()",returning = "result")
public void resultDevice(JoinPoint joinPoint,Object result){
String name = joinPoint.getSignature().getName(); //获取方法名
List<Object> args = Arrays.asList(joinPoint.getArgs()); //获取参数
logger.info(name + "方法执行后,参数为:" + args + ",运行结果为:" + result);
}
@AfterThrowing(value = "commonExpression()",throwing = "exception")
public void exceptionDevice(JoinPoint joinPoint,Exception exception){
String name = joinPoint.getSignature().getName(); //获取方法名
logger.info(name + "方法执行后,抛出异常为:" + exception.getMessage());
}
}
// 或者,可以直接使用 @Around() 注解,整合四个方法,这两种方式按需求选择使用
// @Around() 注解必须带有 ProceedingJoinPoint 参数和目标方法的返回值
/**
package cn.edu.springdemo.aopDemo;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
//定义一个切面类,横切关注点的集合(由 Spring IoC 容器管理)
@Aspect
@Component
@Order(1) //多个切面类时,值小先执行
public class LoggerAspect {
private Logger logger = Logger.getLogger(this.getClass());
//aopDemo包下的所有类的任何修饰符和返回值类型以及任意参数 [修饰符 返回值类型 方法名(参数)]
@Pointcut("execution(* cn.edu.springdemo.aopDemo.*.*(..))") //公共表达式
public void commonExpression(){} //提取出来共用
@Around("commonExpression()")
public Object aroundDevice(ProceedingJoinPoint proceedingJoinPoint){
String name = proceedingJoinPoint.getSignature().getName();
List<Object> args = Arrays.asList(proceedingJoinPoint.getArgs());
Object result = null;
try {
logger.info(name + "方法执行前!!!");
result = proceedingJoinPoint.proceed(); //切入目标方法移至在此执行
logger.info(name + "方法执行后,运行结果为:" + result);
} catch (Throwable throwable) {
logger.info(name + "方法执行后,抛出异常为:" + throwable.getMessage());
throwable.printStackTrace();
}
logger.info(name + "方法执行后,参数为:" + args); //后置通知
return result;
}
}
**/
xml配置1(基于注解):
xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="cn.edu.springdemo.aopDemo" />
<!-- 配置自动生成动态代理 -->
<aop:aspectj-autoproxy />
</beans>
xml配置2(基于 xml 配置文件,同时需要将对应的注解删除):
xml
<?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"
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">
<bean id="logAop" class="cn.edu.springdemo.aopDemo.LogAopImpl" />
<bean id="loggerAspect" class="cn.edu.springdemo.aopDemo.LoggerAspect" />
<aop:config>
<aop:pointcut id="commonExpression" expression="execution(* cn.edu.springdemo.aopDemo.*.*(..))" />
<aop:aspect ref="loggerAspect" order="1">
<aop:before method="beforeDevice" pointcut-ref="commonExpression" />
<!--<aop:after method="afterDevice" pointcut-ref="commonExpression" />-->
<aop:after-returning method="resultDevice" pointcut-ref="commonExpression" returning="result" />
<aop:after-throwing method="exceptionDevice" pointcut-ref="commonExpression" throwing="exception" />
</aop:aspect>
</aop:config>
</beans>
测试结果:
java
package cn.edu.springdemo.aopDemo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aopDemo.xml");
LogAop logAop = (LogAop) applicationContext.getBean("logAop");
logAop.device1("device-");
logAop.add(522,524);
logAop.subtract(522,521);
}
}
日志文件与控制台结果如图: