AOP的介绍与使用
一、概念
AOP(Aspect Orient Programming)就是面向切面编程,图解:
四段代码执行,定义了某个点为切点,图示红色,蓝色,绿色均为定义的切点并不是每一段代码执行相同代码进行切割,而是执行到定义的切点时才切割。
一旦切割,将执行定义的切面上所有的代码
简单解释:相当于你写了一份代码,是切面的代码。而定义的切点处,只要执行到了切点,就会执行你写的切面代码。
二、简单使用
1.首先引入aop坐标
以boot项目为例:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.定义切面类
以“后置通知”为案例,其他通知方式自行百度:
@Aspect
@Component
public class AspectLog {
//所谓后置通知,就是指定义的切点执行完毕以后执行此类
//常见日志打印
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
private final static Logger logger =LoggerFactory.getLogger(AspectLog.class);
/**
* 签名:定义切入点
* 第一种方式:使用切点函数指定包下任意类的任意方法。
* 每个通知都支持切点函数,但统一定义pointCut(),可以统一管理切点。
*/
@Pointcut("execution(* com.example.service..*(..))")
public void pointCut() {}
/**
* 后置通知—1
*/
@After("pointCut()")
public void after1() {
System.out.println("后置通知1");
}
/**
* 签名:定义切入点
* 第二种方式:使用注解方式。
* 包含 "自定义的@DataLog注解" 的方法都会被作为切入点
*/
@Pointcut("@annotation(com.example.utils.aop.DataLog)")
public void annotationPoinCut(){}
/**
* 后置通知—2
*/
@After("annotationPoinCut()")
public void after2() {
System.out.println("后置通知2");
}
其中关于切点函数签名方式的案例:
@Pointcut("execution(* com.example.service..*(..))")参数的各种写法举例说明(网上复制粘贴的,未详细整理):
* “execution(* add())”匹配所有的不带参数的add()方法。
* “execution(public * com.example..*.add*(..))”匹配所有com.example包及其子包下所有类的以add开头的所有public方法。
* “execution(* *(..) throws Exception)”匹配所有抛出Exception的方法。
* execution(public void com.example.Test.add(int, int)):只有add方法加入了4个通知,
* execution(public void com.example.Test.*(int, int)):任意方法,参数为int,int
* execution(public void com.example.Test.*(..)):Test中的任意方法,任意参数列表
* execution(public * com.example.Test.*(..)):Test中的任意方法,任意参数列表,任意返回值
* execution( * com.example.Test*(..)):Test中的任意方法,任意参数列表,任意返回值,任意访问修饰符
3.要使用的方法
两种方式:
1.由于切入点为
@Pointcut(“execution(* com.example.service…*(…))”)
public void pointCut() {}
也就是响应包下所有的方法:
package com.example.service.imp;
import com.example.dao.UserMapper;
import com.example.domain.User;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserServiceImp implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> getMapper1(String age) {
List<User> listForTest = new ArrayList<User>();
System.out.println("方法切入后置通知");
return listForTest;
}
}
2.由于切入点为
@Pointcut("@annotation(com.example.utils.aop.DataLog)")
public void annotationPoinCut(){}
也就是标记注解的方法:
@Override
@DataLog
public List<User> getMapper2(String age) {
List<User> listForTest = new ArrayList<User>();
System.out.println("注解切入,后置通知");
return listForTest;
}
三、其他使用
1.有返回值的通知
/**
*
* @param joinPoint 切入点
* @param keys 返回结果
*/
@AfterReturning(value = "operLogPoinCut()", returning = "keys")
public void saveOperLog(JoinPoint joinPoint, Object keys) {
// joinPoint的用途,比如获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// keys的用途
if(keys.equals("abcd")){
System.out.println("获得的返回值是"+keys+"符合要求");
System.out.println(joinPoint.getSignature().getName());
}else{
System.out.println("获得的返回值不等于abcd,不符合要求");
}
}
四、传统配置文件方式
1.引入包
非SpringBoot的传统Spring项目要导入一些maven依赖。自行百度
2.自定义AOP增强类
如自定义一个MyAOP
public class MyAOP{
public void before1() {
System.out.println("前置增强........");
}
public void after1() {
System.out.println("后置增强.......");
}
// 环绕通知
public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 方法之前
System.out.println("方法之前.........");
// 执行被增强的方法
proceedingJoinPoint.proceed();
// 方法之后
System.out.println("方法之后.........");
}
}
3.配置applicationContext.xml
<!-- 1.配置对象 -->
<bean id="myBook" class="com.test.aop.Test"></bean>
<bean id="myBook" class="com.test.aop.MyAOP"></bean>
<!-- 2.配置AOP操作 -->
<aop:config>
<!-- 2.1配置切入点 -->
<aop:pointcut expression="execution(* com.test.aop.Test.*(..))" id="pointcut1"/>
<!-- 2.2配置切面
把增强用到方法上面
-->
<aop:aspect ref="myBook">
<!-- 配置增强类型
method:增强类中使用哪个方法作为前置
-->
<aop:before method="before1" pointcut-ref="pointcut1"/>
<aop:after method="after1" pointcut-ref="pointcut1"/>
<aop:around method="around1" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
其中Test类为被增强的类,并没有给出Test的代码。切入点配置的是扫描Test类所有方法。
五、AOP实现日志
-
定义的字段主要如下
- 操作日志id
- 操作人
- 操作人工号
- 执行模块
- 执行方法
- 参数名称
- 参数值
- 操作内容
- 请求路径
- IP地址
- 操作时间
- 执行描述(1:执行成功、2:执行失败)
- 结果信息
- …等等
-
核心代码:
@Aspect
@Component
public class TestAspect{
private final Logger logger = LoggerFactory.getLogger(TestAspect.class);
@Pointcut("@annotation(com.example.utils.aop.DataLog)")
public void annotationPoinCut(){
}
@AfterReturning(value = "annotationPoinCut()", returning = "keys")
public void saveOperLog(JoinPoint joinPoint, Object keys) {
try {
} catch (Exception e) {
logger.error(e.toString()
}
}
}
- 其他代码:
获取IP代码:
{
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
String ip = getIpAddr(HttpServletRequest request);
}
private String getIpAddr(HttpServletRequest request) {
String[] HEADERS_TO_TRY = {
"X-Forwarded-For",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR",
"X-Real-IP"};
for (String header : HEADERS_TO_TRY) {
String ip = request.getHeader(header);
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
return ip;
}
}
return request.getRemoteAddr();
}
获取方法各种信息代码:
{
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
String ip = getIpAddr(HttpServletRequest request);
}
private String getIpAddr(HttpServletRequest request) {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 请求的参数设置
String[] parameterNames = signature.getParameterNames();
Object[] args = joinPoint.getArgs();
//判定方法上方是否有自定义注解DataLog
DataLogopLog = method.getAnnotation(DataLog.class);
// 获取请求的方法名
String methodName = method.getName();
}