在spring中要想实现动态代理很简单,应为它为我们提供了大量的API与注解供开发者使用,那我们就先用框架自带的注解我们一起简单的实现下。
spring实现方式
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnhanceDog {
}
public interface IAnimals {
void eat();
void run();
}
@Service
public class DogService implements IAnimals{
@Override
public void eat() {
System.out.println("dog eat");
}
@Override
public void run() {
System.out.println("dog run");
}
}
@Aspect
@Component
public class DogAspect {
// 切入点
@Pointcut(value = "@annotation(com.example.customers.standard.自定义注解.EnhanceDog)")
public void myPointcut(){
}
//环绕增强
@Around("myPointcut()")
public Object dowork(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("主人发出命令");
Object ret = pjp.proceed();
System.out.println("主任给出奖励");
return ret;
}
}
测试
@RestController
@RequestMapping("/test")
public class Controller {
@Autowired
private DogService dogService;
@GetMapping("test")
@EnhanceDog
public void test(){
dogService.eat();
}
}
主人发出命令
dog eat
主任给出奖励
JDK实现方式
/**
* @author heian
* @create 2020-03-27-3:18 下午
* @description 不使用注解 用jdk代理
*/
public class EnhanceDog2 implements InvocationHandler {
private Object target;
public EnhanceDog2(Object target) {
this.target = target;
}
public IAnimals getProxyAnimalsFactory(){
return (IAnimals) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("主人发出命令 2");
Object invoke = method.invoke(target, args);
System.out.println("主任给出奖励 2");
return invoke;
}
}
测试
@RestController
@RequestMapping("/test")
public class Controller {
@Autowired
private DogService dogService;
//防止每次调用产生代理对象
static ConcurrentHashMap<Class,IAnimals> map = new ConcurrentHashMap<>();
@GetMapping("test")
@EnhanceDog
public void test(){
dogService.eat();
}
@GetMapping("test2")
public void test2(){
EnhanceDog2 enhanceDog2 = new EnhanceDog2(dogService);
if (map.get(dogService.getClass()) == null){
IAnimals animals = enhanceDog2.getProxyAnimalsFactory();
map.put(dogService.getClass(),animals);
animals.run();
}else {
System.out.println("---存在------");
IAnimals animals = map.get(dogService.getClass());
animals.run();
}
}
}
主人发出命令 2
dog run
主任给出奖励 2
---存在------
主人发出命令 2
dog run
主任给出奖励 2
发出两次请求,将代理对象结果存于map防止多次代理同一个dog对象。当然也可以存于分布式缓存中,这个得看项目有没有,没有的话就存于堆内存,可以用软引用缓存起来。
注解参数
java中元注解(用来标识注解的注解)有四个: **@Retention @Target @Document @Inherited**;我们项目中常用的话也就两个注解**@Retention @Target**<br />**@Retention****:注解的保留位置 ** <br />**@Retention(RetentionPolicy.SOURCE)** //注解仅存在于源码中,在class字节码文件中不包含<br /> **@Retention(RetentionPolicy.CLASS)** // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,<br /> **@Retention(RetentionPolicy.RUNTIME)** // 注解会在class字节码文件中存在,在运行时可以通过反射获取到<br />**@Target:注解的作用目标** <br /> **@Target(ElementType.TYPE)** //接口、类、枚举、注解<br /> **@Target(ElementType.FIELD)** //字段、枚举的常量<br /> **@Target(ElementType.METHOD)** //方法<br /> **@Target(ElementType.PARAMETER)** //方法参数<br /> **@Target(ElementType.CONSTRUCTOR)** //构造函数<br /> **@Target(ElementType.LOCAL_VARIABLE)**//局部变量<br /> **@Target(ElementType.ANNOTATION_TYPE)**//注解<br /> **@Target(ElementType.PACKAGE)** ///包 <br />**@Document:说明该注解将被包含在javadoc中**<br />**@Inherited:说明子类可以继承父类中的该注解**
项目应用
最近公司需要对后台的设计增删改的操作都记录到日志中,并且把具体操作参数存到日志中,比如我对xxx进行了xx操作,所以传统的环绕是无法做到这一点的,所以还是不得不侵入代码,然后把结果参数封装起来进行,存到一个局部变量(注解)或者全局变量中,然后通过反射去拿到该变量的值,为了做到线程安全可以定义threadlocal来实现。<br /> (一)通过局部变量(注解)的方式实现起来比较简单干脆:如下:
@Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LocalParamAnnotation {
}
@PostMapping("/info/type")
public Tip getAboutInfoByType(@RequestBody Map<String, String> map) {
xx业务代码
@LocalParamAnnotation
String desc = "操作描述xxx";
return new SuccessTip(desc);
}
因为java的注解可以放在局部变量上,但不知道如何获取该注解,所以没法拿到里面的值。 这里希望大神能指点一二! <br />(二)通过threadlocal,记录每个线程所拥有的业务变量
private ThreadLocal<String> recordLocal = new ThreadLocal<>();
@PostMapping("/insertRoleUser")
@OperateAnnotation
public Tip insertRoleUser(@RequestBody RoleUser roleUser) {
xxx业务代码
recordLocal.set("执行了对" + roleUser.getAccount() + "账号的机构修改了机构信息");
return new SuccessTip(roleUserMapper.insert(roleUser));
}
切面增强
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
@Aspect
@Component
@Slf4j
public class OperateAspect {
@Autowired
private CommonService commonService;
@Autowired
private RedisUtil redisUtil;
// 切入点
@Pointcut(value = "@annotation(xxx.annotation.OperateAnnotation)")
public void myPointcut(){
}
/**
* 增强逻辑
* @param jp 目标类连接点对象 绕增强请使用pjg连接点对象
* @param args 方法返回结果
*/
@AfterReturning(pointcut = "myPointcut()",returning = "args")
public void dowork(JoinPoint jp,Object args) throws Exception{
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
MethodSignature signature = (MethodSignature) jp.getSignature();
Method method = signature.getMethod();
OperateAnnotation annotation = method.getAnnotation(OperateAnnotation.class);
if (annotation != null){
//login方法是不带token,所以没法保存 roleUser,需要对结果进行解析
if (request.getRequestURI().indexOf("/coke/user/login") != -1) {
Tip ret = (Tip) args;
if (ret.getCode() == 200){
Map<String,Object> data = (Map<String, Object>) ret.getData();
String token = (String) data.get("token");
if (null != token) {
String tokenKey = RedisConstant.USER_TOKEN_PREFIX + token;
String userToken = redisUtil.getString(tokenKey);
if (StringUtils.isNotBlank(userToken)){
RoleUser roleUser = JSON.parseObject(userToken, RoleUser.class);
if (null != roleUser) {
redisUtil.expire(tokenKey, RedisConstant.USER_TOKEN_TIMEOUT);
AppContext.getRequestContext().setUserId(roleUser.getUserId());
AppContext.getRequestContext().setCurrentUser(roleUser);
}
}
}
}
}
RoleUser roleUser = AppContext.getRequestContext().getCurrentUser();
if (roleUser != null){
Field[] declaredFields = jp.getTarget().getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
if (declaredField.getName().equals("recordLocal")){
declaredField.setAccessible(true);
ThreadLocal<String> local = (ThreadLocal<String>) declaredField.get(jp.getTarget());
//行为描述
String descprition = local.get();
OperateRecord record = new OperateRecord();
record.setOperateType(exchange(roleUser.getUserType()));
record.setOperateAccount(roleUser.getAccount());
record.setOperateDescription(DateUtil.getNow2() + " 方法:"+ jp.getSignature().getName()+ " "+descprition);
record.setIp(request.getRemoteAddr());
record.setOperateTime(new Date());
commonService.insertOperateRecord(record);
}
}
}else {
log.error("登陆用户获取失败");
}
}
//Object[] args = pjp.getArgs();//拦截该方法的参数 后期加了字段可能会对入参进行分析
}
//1超级管理员、2管理员 3机构、4客服 5销售
private String exchange(int type){
String storage = "";
switch (type) {
case 1:
storage = "超级管理员";
break;
case 2:
storage = "管理员";
break;
case 3:
storage = "机构";
break;
case 4:
storage = "客服";
break;
case 5:
storage = "销售";
break;
default:
storage = "其它";
}
return storage;
}
}