基础概念:
- Target(目标对象):代理的目标对象。
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
-
Joinpoint(连接点):所谓的连接点是指那些被拦截到的点。在spring中,这些点指的就是方法。通俗来讲就是
可以被增强的方法叫做连接点
。 -
Pointcut(切入点):所谓切入点就是指我们要对拿些连接点进行拦截的定义。
切点只是连接的一部分,真正被增强的方法
。 -
Advice(通知/增强):所谓通知是指拦截到Joinpoint只后所要做的事情就是通知。
封装增强业务逻辑的方法
- Aspect(切面):是指切点和通知的结合。
-
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态织入方式,而AspectJ采用编译期织入和类装载期织入。
将切点和通知进行结合的过程。
使用AOP可以实现事物的控制,权限控制,日志切面等,其中spring中事物就是基于AOP的,下面通过日志切面的DEMO来介绍具体的实现。
项目运行结果:
项目基础:
涉及到的数据库sql:
CREATE TABLE `sys_log` (
`id` varchar(32) NOT NULL,
`log_content` varchar(1000) DEFAULT NULL COMMENT '日志内容',
`ip` varchar(100) DEFAULT NULL COMMENT 'IP',
`method` varchar(500) DEFAULT NULL COMMENT '请求java方法',
`request_url` varchar(255) DEFAULT NULL COMMENT '请求路径',
`request_param` longtext COMMENT '请求参数',
`request_type` varchar(10) DEFAULT NULL COMMENT '请求类型',
`cost_time` bigint(20) DEFAULT NULL COMMENT '耗时',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DAO层涉及到的sql语句
<insert id="saveSysLog" parameterType="com.study.logaop.entity.SysLog">
INSERT INTO sys_log(
id,
log_content,
ip,
method,
request_url,
request_param,
request_type,
cost_time
)VALUES (#{id},#{logContent},#{ip},#{method},#{requestUrl},#{requestParam}
,#{requestType},#{costTime})
</insert>
pom.xml:
<dependencies>
<!--集成springmvc框架并实现自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 数据库驱动 https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<!-- mybatisPLUS-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!--简化实体类,用@Data代替getset方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<!--spring切面aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
基于元注解的AOP所以先创建自定义注解@AutoLog
@Target(ElementType.METHOD) //作用于方法上
@Retention(RetentionPolicy.RUNTIME) //运行时生效
@Documented
public @interface AutoLog {
/**
* 日志内容
* @return
*/
String value() default "";
}
自定义切面处理类,用于方法的增强,使用的是@Around 环绕通知
/**
* 切面处理类
*/
@Aspect
@Component
public class AutoLogAspect {
@Autowired
private SysLogService sysLogService;
//方法上面有 @AutoLog 这个注解时生效
@Pointcut("@annotation(com.study.logaop.aspect.annotion.AutoLog)")
private void logPointCut(){
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行增强方法 ,joinPoint.proceed()执行结果(proceed )得到的是 方法上面有 @AutoLog 这个注解的方法的返回值
Object proceed = joinPoint.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
saveSysLog(joinPoint,time);
return proceed;
}
//保存日志操作
private void saveSysLog(ProceedingJoinPoint joinPoint, long time) throws NoSuchMethodException, ClassNotFoundException {
SysLog sysLog = new SysLog();
//获取request
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AutoLog annotation = signature.getMethod().getAnnotation(AutoLog.class);
//获取日志信息 value
String value = annotation.value();
//获取Ip
String ip = IpUtils.getIpAddr(request);
//获取方法 (这里格式为 类名.方法名())
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
//请求路径 URL
String requestURI = request.getRequestURI();
//请求参数
Map fieldsName = AopUtils.getFieldsName(joinPoint);
//请求方式 类型 GET POST ..
String method = request.getMethod();
sysLog.setId(IdWorker.getIdStr());
sysLog.setLogContent(value);
sysLog.setIp(ip);
sysLog.setRequestUrl(requestURI);
sysLog.setRequestParam(JSON.toJSONString(fieldsName));
sysLog.setRequestType(method);
sysLog.setCostTime(time);
sysLogService.saveSysLog(sysLog);
}
}
**涉及到的一些工具类**
- AopUtils
/**
* 切面相关工具类
*/
@Slf4j
public class AopUtils {
/**
* 获取AOP代理的方法的参数名称和参数值
*
* @param cls
* @param clazzName
* @param methodName
* @param args
* @return
* @throws NotFoundException
*/
public static StringBuffer getNameAndArgs(Class<?> cls, String clazzName, String methodName, Object[] args) {
StringBuffer sb = new StringBuffer();
try {
Map<String, Object> nameAndArgs = new HashMap<>();
ClassPool pool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(cls);
pool.insertClassPath(classPath);
CtClass cc = pool.get(clazzName);
CtMethod cm = cc.getDeclaredMethod(methodName);
MethodInfo methodInfo = cm.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
if (attr == null) {
// exception
}
int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
for (int i = 0; i < cm.getParameterTypes().length; i++) {
// paramNames即参数名
nameAndArgs.put(attr.variableName(i + pos), args[i]);
}
// nameAndArgs的两种类型,用实体类接收的类似这样:
// reqParams=com.whoareyou.fido.rest.User@616b9c0e
// 用Map<String,Object>接收的是这样:menuNo=56473283,遍历这个map区分两种不同,使用不同的取值方式。
// 根据获取到的值所属的不同类型通过两种不同的方法获取参数
boolean flag = false;
if (nameAndArgs != null && nameAndArgs.size() > 0) {
for (Map.Entry<String, Object> entry : nameAndArgs.entrySet()) {
if (entry.getValue() instanceof String) {
flag = true;
break;
}
}
}
sb = new StringBuffer();
if (flag) {
// 从Map中获取
sb.append(JSON.toJSONString(nameAndArgs));
} else {
if (args != null) {
for (Object object : args) {
if (object != null) {
if (object instanceof MultipartFile || object instanceof ServletRequest
|| object instanceof ServletResponse) {
continue;
}
sb.append(JSON.toJSONString(object));
}
}
}
}
} catch (Exception e) {
log.error(e.getMessage());
}
return sb;
}
/**
* 获取AOP代理的方法的参数名称和参数值
*
* @param cls
* @param clazzName
* @param methodName
* @param args
* @return
*/
public static Map<String, Object> getNameAndArgsMap(Class<?> cls, String clazzName, String methodName, Object[] args) {
Map<String, Object> nameAndArgs = new HashMap<>();
try {
ClassPool pool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(cls);
pool.insertClassPath(classPath);
CtClass cc = pool.get(clazzName);
CtMethod cm = cc.getDeclaredMethod(methodName);
MethodInfo methodInfo = cm.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
if (attr == null) {
// exception
}
int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
for (int i = 0; i < cm.getParameterTypes().length; i++) {
String variableName = attr.variableName(i + pos);
System.out.println("参数名" + attr.variableName(i + pos));
// paramNames即参数名
nameAndArgs.put(attr.variableName(i + pos), args[i]);
}
} catch (Exception e) {
log.error(e.getMessage());
}
return nameAndArgs;
}
/**
* 获取参数名和值
*
* @param joinPoint
* @return
*/
public static Map getFieldsName(ProceedingJoinPoint joinPoint) throws ClassNotFoundException, NoSuchMethodException {
// 参数值
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
String[] parameterNames = methodSignature.getParameterNames();
// 通过map封装参数和参数值
HashMap<String, Object> paramMap = new HashMap();
for (int i = 0; i < parameterNames.length; i++) {
paramMap.put(parameterNames[i], args[i]);
}
return paramMap;
}
- IpUtils 获取请求的IP信息工具类
/**
* IP相关工具类
*/
@Slf4j
public class IpUtils {
/**
* 获取当前网络ip
*
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
//根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 //"***.***.***.***".length() = 15
if (ipAddress != null && ipAddress.length() > 15) {
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
return ipAddress;
}
/**
* 获取真实IP
*
* @param request
* @return
*/
public static String getRealIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
return checkIp(ip) ? ip : (
checkIp(ip = request.getHeader("Proxy-Client-IP")) ? ip : (
checkIp(ip = request.getHeader("WL-Proxy-Client-IP")) ? ip :
request.getRemoteAddr()));
}
/**
* 校验IP
*
* @param ip
* @return
*/
private static boolean checkIp(String ip) {
return !StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip);
}
/**
* 获取操作系统,浏览器及浏览器版本信息
*
* @param request
* @return
*/
public static Map<String, String> getOsAndBrowserInfo(HttpServletRequest request) {
String browserDetails = request.getHeader("User-Agent");
String userAgent = browserDetails;
String user = userAgent.toLowerCase();
String os = "";
String browser = "";
//=================OS Info=======================
if (userAgent.toLowerCase().indexOf("windows") >= 0) {
os = "Windows";
} else if (userAgent.toLowerCase().indexOf("mac") >= 0) {
os = "Mac";
} else if (userAgent.toLowerCase().indexOf("x11") >= 0) {
os = "Unix";
} else if (userAgent.toLowerCase().indexOf("android") >= 0) {
os = "Android";
} else if (userAgent.toLowerCase().indexOf("iphone") >= 0) {
os = "IPhone";
} else {
os = "UnKnown, More-Info: " + userAgent;
}
//===============Browser===========================
try {
if (user.contains("edge")) {
browser = (userAgent.substring(userAgent.indexOf("Edge")).split(" ")[0]).replace("/", "-");
} else if (user.contains("msie")) {
String substring = userAgent.substring(userAgent.indexOf("MSIE")).split(";")[0];
browser = substring.split(" ")[0].replace("MSIE", "IE") + "-" + substring.split(" ")[1];
} else if (user.contains("safari") && user.contains("version")) {
browser = (userAgent.substring(userAgent.indexOf("Safari")).split(" ")[0]).split("/")[0]
+ "-" + (userAgent.substring(userAgent.indexOf("Version")).split(" ")[0]).split("/")[1];
} else if (user.contains("opr") || user.contains("opera")) {
if (user.contains("opera")) {
browser = (userAgent.substring(userAgent.indexOf("Opera")).split(" ")[0]).split("/")[0]
+ "-" + (userAgent.substring(userAgent.indexOf("Version")).split(" ")[0]).split("/")[1];
} else if (user.contains("opr")) {
browser = ((userAgent.substring(userAgent.indexOf("OPR")).split(" ")[0]).replace("/", "-"))
.replace("OPR", "Opera");
}
} else if (user.contains("chrome")) {
browser = (userAgent.substring(userAgent.indexOf("Chrome")).split(" ")[0]).replace("/", "-");
} else if ((user.indexOf("mozilla/7.0") > -1) || (user.indexOf("netscape6") != -1) ||
(user.indexOf("mozilla/4.7") != -1) || (user.indexOf("mozilla/4.78") != -1) ||
(user.indexOf("mozilla/4.08") != -1) || (user.indexOf("mozilla/3") != -1)) {
browser = "Netscape-?";
} else if (user.contains("firefox")) {
browser = (userAgent.substring(userAgent.indexOf("Firefox")).split(" ")[0]).replace("/", "-");
} else if (user.contains("rv")) {
String IEVersion = (userAgent.substring(userAgent.indexOf("rv")).split(" ")[0]).replace("rv:", "-");
browser = "IE" + IEVersion.substring(0, IEVersion.length() - 1);
} else {
browser = "UnKnown";
}
} catch (Exception e) {
log.error("获取浏览器版本失败");
log.error(e.getMessage());
browser = "UnKnown";
}
Map<String, String> result = new HashMap<>(2);
result.put("OS", os);
result.put("BROWSER", browser);
return result;
}
/**
* 判断是否是内网IP
*
* @param ip
* @return
*/
public static boolean isInner(String ip) {
String reg = "(10|172|192)\\.([0-1][0-9]{0,2}|[2][0-5]{0,2}|[3-9][0-9]{0,1})\\.([0-1][0-9]{0,2}|[2][0-5]{0,2}|[3-9][0-9]{0,1})\\.([0-1][0-9]{0,2}|[2][0-5]{0,2}|[3-9][0-9]{0,1})";
Pattern p = Pattern.compile(reg);
Matcher matcher = p.matcher(ip);
return matcher.find();
}
/**
* 获取IP
*
* @return
*/
public static String getHostIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
}
return "127.0.0.1";
}
/**
* 获取主机名
*
* @return
*/
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
}
return "未知";
}
}
- SpringContextUtils 获取spring容器的一些信息工具类
* @Component
public class SpringContextUtils implements ApplicationContextAware {
/**
* 上下文对象实例
*/
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 获取HttpServletRequest
*/
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
public static String getDomain(){
HttpServletRequest request = getHttpServletRequest();
StringBuffer url = request.getRequestURL();
return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
}
public static String getOrigin(){
HttpServletRequest request = getHttpServletRequest();
return request.getHeader("Origin");
}
/**
* 通过name获取 Bean.
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
版权声明:本文为qq_578978992原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。