一、关于注解
Java中定义了4个元注解,分别是: @Target
,@Retention
,@Documented
,@Inherited
1. @Target注解
说明了注解所修饰的对象范围:注解可被用于 包、类、接口、枚举、Annotation类型、方法、构造方法、成员变量、枚举值、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用: 用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
2. @Retention注解
定义了该注解被保留的时间长短:某些注解仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的注解可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为注解与class在使用上是被分离的)。使用这个元注解可以对 Annotation的“生命周期”限制。
作用: 表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
3. @Documented注解
用于描述其它类型的注解应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。------------摘抄
4. @Inherited注解
是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
二、实现自定义注解
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogRecord {
/**
* 被标注的方法的功能
* @return
*/
String effect() default "";
}
三、整合AOP功能
1. maven添加AOP功能相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 编写AOP代码
import com.almond.mpdemo.common.annotation.LogRecord;
import com.almond.mpdemo.common.utils.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
/**
* @Classname LogAspect
* @Description 扫描自定义注解 实现日志记录增强
* @Date 2020/9/4 9:27
* @Created by hongxing
*/
@Slf4j
@Component
@Aspect
public class LogAspect {
@Resource
private TLogMapper tLogMapper;
// 设置切点
@Pointcut("@annotation(com.almond.mpdemo.common.annotation.LogRecord)")
public void annotationAspect() {
}
/**
* 环绕通知 获取方法相关信息
* 线程id
* 请求ip
*/
@Around(value = "annotationAspect()")
@Transactional(rollbackFor = {Exception.class})
public Object doAround(ProceedingJoinPoint joinPoint) {
log.info("代理执行, 代理方法{}", ((MethodSignature) joinPoint.getSignature())
.getMethod().getName());
LogRecord logRecord = ((MethodSignature) joinPoint.getSignature())
.getMethod()
.getAnnotation(LogRecord.class);
String name = "";
if (logRecord != null) {
name = ((MethodSignature) joinPoint.getSignature())
.getMethod().toGenericString();
}
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
String host = HttpUtil.getIpAddress(request);
Long id = Thread.currentThread().getId();
// 日志落库
// 在此处持久化日志信息
try {
// 放行,执行原方法
joinPoint.proceed();
} catch (Throwable throwable) {
log.error("代理发生异常, 异常信息{}", throwable.getMessage());
}
return null;
}
}
3. 相关工具类
package com.almond.mpdemo.common.utils;
import javax.servlet.http.HttpServletRequest;
/**
* @Classname HttpUtil
* @Description
* @Date 2020/9/4 10:49
* @Created by hongxing
*/
public class HttpUtil {
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
* 参考文章: http://developer.51cto.com/art/201111/305181.htm
*
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
* 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
*
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
* 192.168.1.100
*
* 用户真实IP为: 192.168.1.110
*
* @param request
* @return
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
4. 测试demo
将注解标注在方法上,调用方法,观察控制日志,如果出现代理执行, 代理方法XXX则表示功能实现.