1. 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;
    }

}