自定义注解的简单使用
本次实现场景为:注解作用到方法上,每次方法调用之前打印日志
1.先自定义一个注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author liull
* @date 2021/2/4 15:19
* @description 接口类,用于在方法前打印日志
*/
@Target({ElementType.METHOD})// 设置注解的使用范围,这里仅用于方法上
@Retention(RetentionPolicy.RUNTIME) // 设置注解的生命周期,这里设置在运行时。
public @interface LogBefore {
String value() default "";
}
2.写一个注解的解释器(切面)
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
/**
* @author liull
* @date 2021/2/4 15:20
* @description 这是一个切面类
*/
@Aspect//声明这是一个切面
public class LogAspect {
private static Logger logger = LoggerFactory.getLogger(LogAspect.class);
public LogAspect() {
logger.info("log aspect...");
}
// 把切面的连接点放在了注解上
@Pointcut("@annotation(com.example.hello.aspect.LogBefore)")
public void logAspect() {
}
// 在这里定义前置切面
@Before("logAspect( )")
public void beforeMethod(JoinPoint joinPoint) throws Exception {
String value = null;
// 类名
String className = joinPoint.getTarget().getClass().getName();
// 方法名
String methodName = joinPoint.getSignature().getName();
// 第一种方法:可直接获取方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method1;
if (signature != null) {
method1 = signature.getMethod();
String value1 = method1.getAnnotation(LogBefore.class).value();
logger.info("进入方法:" + className + "#" + methodName + ":" + value1);
}
// 第一种方法:如果是作用到类,则可以通过类获取所有方法,遍历即可
// 获取相关参数
Object[] arguments = joinPoint.getArgs();
// 获取类
Class<?> classA = Class.forName(className);
// 获取该类中的方法
Method[] methods = classA.getMethods();
for (Method method : methods) {
if (!method.getName().equals(methodName)) {
continue;
}
Class[] clazzs = method.getParameterTypes();
if (clazzs.length != arguments.length) {
// 比较方法中参数个数与从切点中获取的参数个数是否相同,原因是方法可以重载
continue;
}
value = method.getAnnotation(LogBefore.class).value();
}
logger.info("进入方法:" + className + "#" + methodName + ":" + value);
}
// 在这里定义后置切面
@After("logAspect( )")
public void afterMethod(JoinPoint joinPoint) throws Exception {
// 类名
String className = joinPoint.getTarget().getClass().getName();
// 方法名
String methodName = joinPoint.getSignature().getName();
logger.info("退出方法:" + className + "#" + methodName);
}
}
3.切面配置
import com.example.hello.aspect.LogAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author liull
* @date 2021/2/4 17:01
* @description 切面配置类
*/
@Configuration
public class AopConfig {
@Bean
public LogAspect logOuAspect(){
return new LogAspect();
}
}
4.测试用
import com.example.hello.aspect.LogBefore;
import com.example.hello.aspect.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @author liull
* @date 2021/2/4 15:20
* @description 测试切面用
*/
@RestController
@RequestMapping("aspect")
public class AspectController {
@RequestMapping("/getUser")
@LogBefore(value = "aaa")//这是我们自定义的注解,加上这个注解后就能够切到这个方法了。
public User getUser(HttpServletRequest request) {
System.out.println("方法中。。。。。");
User user=new User();
user.setName("小明");
user.setPassword("xxxx");
return user;
}
}
日志效果:
进入方法:com.example.hello.controller.AspectController#getUser:aaa
进入方法:com.example.hello.controller.AspectController#getUser:aaa
方法中。。。。。
退出方法:com.example.hello.controller.AspectController#getUser
注意:
同一个类TestController,A方法直接调用B方法注解是不生效的
TestController调用TestService接口是生效的
这种调用方式是方法b的注解是不生效的
@LogBefore
void a(){
b();
}
@LogBefore
void b(){
...
}
原因(网上查的,还不是很理解,慢慢刚):
动态代理的坑,内部调用不走代理类,所以实现的附加操作肯定不会执行了。这个其实是因为实际执行的不是代理类而导致的,那我们解决的思路就想办法让方法b的调用走代理类就可以了
解决方法:
// 1.启动类增加注解
@EnableAspectJAutoProxy(exposeProxy = true)
// 2.调用方式修改为
@LogBefore
void a(){
TestController tc = (TestController) AopContext.currentProxy();
tc.b();
}
@LogBefore
void b(){
...
}
end.