Spring AOP如何自定义注解实现审计或日志记录(完整代码)

目录
  • 环境准备
  • 项目结构
  • 自定义审计注解
  • 定义切面类
  • 定义返回值处理基类
  • 定义返回值处理子类
  • 定义功能模块类
  • 定义操作类
  • 定义审计信息实体类
  • 书写mapper文件
  • 开启AOP拦截
  • 注解配置
  • 总结

环境准备

JDK 1.8,Springboot 2.1.3.RELEASE,spring-boot-starter-aop.2.1.4.RELEASE.jar,aspectjrt.1.9.2.jar,aspectjweaver.1.9.2.jar,pom依赖如下:

<!-- 添加aspectj -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.1.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.2</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.2</version>
</dependency>

项目结构

自定义审计注解

package com.cjia.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.METHOD })
public @interface Audit {

    /**
     * 模块代码
     */
    int moudleCode() default -1;

    /**
     * 扩展信息,用户返回操作类型及参数
     */
    Class<?> extension();
}

定义切面类

package com.cjia.common.aspect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
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 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 com.cjia.common.Moudle;
import com.cjia.common.Operate;
import com.cjia.common.annotation.Audit;
import com.cjia.common.reflect.BaseValueReturn;
import com.cjia.model.AuditMessage;
import com.cjia.model.ResponseContent;
import com.cjia.model.User;
import com.cjia.service.AuditService;
import com.cjia.service.UserService;

@Aspect
@Component
public class AuditAop {

    private static final Logger LOGGER = LoggerFactory.getLogger(AuditAop.class);
    @Autowired
    private AuditService auditService;
    @Autowired
    private UserService userService;
    // 操作发起者
    ThreadLocal<User> user = new ThreadLocal<>();
    // 操作应用
    ThreadLocal<Integer> appId = new ThreadLocal<>();
    // 功能模块
    ThreadLocal<Integer> moudleCode = new ThreadLocal<>();
    // 操作类型
    ThreadLocal<Integer> operateType = new ThreadLocal<>();
    // IP地址
    ThreadLocal<String> ip = new ThreadLocal<>();
    // 操作时间点
    ThreadLocal<String> operateTime = new ThreadLocal<>();
    // 操作信息实体
    ThreadLocal<AuditMessage> msg = new ThreadLocal<>();
    // 对CIS,额外的菜单id信息extension
    ThreadLocal<String> extension = new ThreadLocal<>();

    // 声明AOP切入点
    @Pointcut("@annotation(com.cjia.common.annotation.Audit)")
    public void audit() {
    }

    @Before("audit()")
    public void beforeExec(JoinPoint joinPoint) {
    }

    @After("audit()")
    public void afterExec(JoinPoint joinPoint) {
    }

    @Around("audit()")
    public Object aroundExec(ProceedingJoinPoint pjp) throws Throwable {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            extension.set(request.getParameter("extension"));
            operateTime.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            ip.set(getIpAddr(request));
            appId.set(Integer.valueOf(request.getParameter("appId")));

            MethodSignature ms = (MethodSignature) pjp.getSignature();
            Method method = ms.getMethod();
            // 获取注解的参数信息
            Audit auditAnno = method.getAnnotation(Audit.class);
            moudleCode.set(auditAnno.moudleCode());
            Class<?> external = auditAnno.extension();
            Constructor<?> cons = external.getConstructor(Integer.class, HttpServletRequest.class);
            BaseValueReturn bvr = (BaseValueReturn) cons.newInstance(moudleCode.get(), request);
            Map<String, Object> reqInfo = bvr.getRequestInfo();
            Integer operate_type = Integer.valueOf(reqInfo.get(BaseValueReturn.OPT) + "");
            operateType.set(operate_type);
            Object target = reqInfo.get(BaseValueReturn.TARGET);
            // 获取当前登录的用户,需注意:登录时没有msgKey
            String msgKey = request.getParameter("msgKey");
            User loginUser = null;
            if (operate_type.equals(Operate.LOGIN)) {
                List<User> users = userService.selectByEmail(String.valueOf(target));
                if (users != null && !users.isEmpty()) {
                    loginUser = users.get(0);
                }
            } else if (msgKey != null) {
                loginUser = userService.selectMsgFromRedisPurely(msgKey);
            }
            user.set(loginUser);

            AuditMessage am = new AuditMessage();
            // am.setUserId需判空,代表过期
            if (loginUser != null) {
                am.setUserId(loginUser.getId());
            } else {
                am.setUserId(-1);
            }
            am.setAppId(appId.get());
            am.setMoudleCode(moudleCode.get());
            am.setOperateType(operateType.get());
            am.setIp(ip.get());
            am.setOperateTime(operateTime.get());
            // TODO details=target
            String details = "";
            if (moudleCode.get() == Moudle.DATA && loginUser != null) {
                details = (String) target;
            } else {
                // TODO 其他模块
            }
            am.setTarget(details);
            msg.set(am);
            auditService.insert(msg.get());
        } catch (Exception e) {
            LOGGER.error("Error occured while auditing, cause by: ", e);
        } finally {
            // FATAL: remove threadLocal and set threadLocal = null
        }
        Object rtn = pjp.proceed();
        return rtn;
    }

    /**
     * 带参返回
     */
    @AfterReturning(pointcut = "audit()", returning = "rc")
    public void doAfterReturning(ResponseContent rc) {
        LOGGER.debug("afterReturning with returnType..");
    }

    /**
     * 不带参返回
     */
    @AfterReturning(pointcut = "audit()")
    public void doAfterReturning(JoinPoint joinPoint) {
        LOGGER.debug("afterReturning without returnType..");
    }

    @AfterThrowing(pointcut = "audit()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        LOGGER.error("Error occured, cause by {}", e.getMessage());
    }

    private String getRemoteHost(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.getRemoteAddr();
        }
        return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
    }

    /**
     * 获取用户真实IP地址,不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,
     * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值
     *
     * @return ip
     */
    private String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if( ip.indexOf(",")!=-1 ){
                ip = ip.split(",")[0];
            }
        }
        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.getHeader("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

}

注意事项

第81行的HttpServletRequest request最好设置为局部变量,或ThreadLocal修饰的成员变量,而非普通成员变量,防止异步请求较多,导致有的request丢失参数的离奇问题。

定义返回值处理基类

package com.cjia.common.reflect;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

public abstract class BaseValueReturn {
    public static final String OPT = "operate";
    public static final String TARGET = "target";

    protected Integer moudleCode;
    protected HttpServletRequest request;

    public BaseValueReturn(Integer moudleCode, HttpServletRequest request) {
        super();
        this.moudleCode = moudleCode;
        this.request = request;
    }

    /**
     * 返回操作动作operate和操作目标target
     */
    public abstract Map<String, Object> getRequestInfo();
}

定义返回值处理子类

package com.cjia.common.reflect;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.cjia.common.Operate;

public class DataValueReturn extends BaseValueReturn {

    public DataValueReturn(Integer moudleCode, HttpServletRequest request) {
        super(moudleCode, request);
    }

    @Override
    public Map<String, Object> getRequestInfo() {
        Map<String, Object> info = new HashMap<>();
        info.put(OPT, Operate.FETCH_DATA);
        String menuId = request.getParameter("extension");
        info.put(TARGET, menuId);
        return info;
    }

}
package com.cjia.common.reflect;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.cjia.common.Operate;

public class LoginValueReturn extends BaseValueReturn {

    public LoginValueReturn(Integer moudleCode, HttpServletRequest request) {
        super(moudleCode, request);
    }

    @Override
    public Map<String, Object> getRequestInfo() {
        Map<String, Object> info = new HashMap<>();
        info.put(OPT, Operate.LOGIN);
        String email = request.getParameter("email");
        info.put(TARGET, email);
        return info;
    }

}

定义功能模块类

package com.cjia.common;

import java.util.HashMap;
import java.util.Map;

public class Moudle {

    public static final int LOGIN = 1;
    public static final int DATA = 2;
    public static final int USER = 3;
    // TODO more moudles

    private static final Map<Integer, String> moudleMap = new HashMap<>();
    static {
        moudleMap.put(Moudle.LOGIN, "登录");
        moudleMap.put(Moudle.DATA, "业务数据");
        moudleMap.put(Moudle.USER, "用户");
    }

    public static String getNameByCode(int code) {
        return moudleMap.get(code);
    }
}

定义操作类

package com.cjia.common;

import java.util.HashMap;
import java.util.Map;

public class Operate {

    public static final int LOGIN = 1;
    // 内部系统获取数据
    public static final int FETCH_DATA = 2;
    public static final int INSERT = 3;
    public static final int DELETE = 4;
    public static final int UPDATE = 5;
    public static final int SEARCH = 6;
    // TODO more opts

    private static final Map<Integer, String> optMap = new HashMap<>();
    static {
        optMap.put(Operate.LOGIN, "登录");
        optMap.put(Operate.FETCH_DATA, "获取业务数据");
        optMap.put(Operate.INSERT, "新增");
        optMap.put(Operate.DELETE, "删除");
        optMap.put(Operate.UPDATE, "修改");
        optMap.put(Operate.SEARCH, "查询");
    }

    public static String getNameByCode(int code) {
        return optMap.get(code);
    }
}

定义审计信息实体类

package com.cjia.model;

public class AuditMessage {

    private int id;
    private int userId;
    private int appId;
    private int moudleCode;
    private int operateType;
    private String ip;
    private String operateTime;
    private String target;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public int getAppId() {
        return appId;
    }

    public void setAppId(int appId) {
        this.appId = appId;
    }

    public int getMoudleCode() {
        return moudleCode;
    }

    public void setMoudleCode(int moudleCode) {
        this.moudleCode = moudleCode;
    }

    public int getOperateType() {
        return operateType;
    }

    public void setOperateType(int operateType) {
        this.operateType = operateType;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getOperateTime() {
        return operateTime;
    }

    public void setOperateTime(String operateTime) {
        this.operateTime = operateTime;
    }

    public String getTarget() {
        return target;
    }

    public void setTarget(String target) {
        this.target = target;
    }

    @Override
    public String toString() {
        return "AuditMessage [id=" + id + ", userId=" + userId + ", appId=" + appId + ", moudleCode=" + moudleCode
                + ", operateType=" + operateType + ", ip=" + ip + ", operateTime=" + operateTime + ", target=" + target
                + "]";
    }

}

书写mapper文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cjia.dao.AuditMessageMapper">

    <resultMap id="BaseResultMap" type="com.cjia.model.AuditMessage">
        <result column="id" jdbcType="INTEGER" property="id" />
        <result column="user_id" jdbcType="VARCHAR" property="userId" />
        <result column="app_id" jdbcType="VARCHAR" property="appId" />
        <result column="moudle_code" jdbcType="VARCHAR" property="moudleCode" />
        <result column="operate_type" jdbcType="INTEGER" property="operateType" />
        <result column="ip" jdbcType="VARCHAR" property="ip" />
        <result column="operate_time" jdbcType="INTEGER" property="operateTime" />
        <result column="target" jdbcType="VARCHAR" property="target" />
    </resultMap>

    <!-- 对应的插入字段的名字 -->
    <sql id="keys">
        <trim suffixOverrides=",">
            <if test="id!=null and id!=''">
                id,
            </if>
            <if test="userId!=null and userId!=''">
                user_id,
            </if>
            <if test="appId!=null and appId!=''">
                app_id,
            </if>
            <if test="moudleCode!=null and moudleCode!=''">
                moudle_code,
            </if>
            <if test="operateType!=null and operateType!=''">
                operate_type,
            </if>
            <if test="ip!=null and ip!=''">
                ip,
            </if>
            <if test="operateTime!=null and operateTime!=''">
                operate_time,
            </if>
            <if test="target!=null and target!=''">
                target,
            </if>
        </trim>
    </sql>

    <!-- 对应的插入字段的值 -->
    <sql id="values">
        <trim suffixOverrides=",">
            <if test="id!=null and id!=''">
                #{id},
            </if>
            <if test="userId!=null and userId!=''">
                #{userId},
            </if>
            <if test="appId!=null and appId!=''">
                #{appId},
            </if>
            <if test="moudleCode!=null and moudleCode!=''">
                #{moudleCode},
            </if>
            <if test="operateType!=null and operateType!=''">
                #{operateType},
            </if>
            <if test="ip!=null and ip!=''">
                #{ip},
            </if>
            <if test="operateTime!=null and operateTime!=''">
                #{operateTime},
            </if>
            <if test="target!=null and target!=''">
                #{target},
            </if>
        </trim>
    </sql>
    <insert id="insert" parameterType="com.cjia.model.AuditMessage">
        insert into audit_message(
        <include refid="keys" />
        )
        values(
        <include refid="values" />
        )
    </insert>

    <select id="selectAll" resultMap="BaseResultMap">
        select * from audit_message
    </select>

</mapper>

开启AOP拦截

package com.cjia;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@MapperScan("com.cjia.dao")
@EnableAspectJAutoProxy(exposeProxy=true)
public class DataserviceApplication {

    public static void main(String[] args) {
        SpringApplication.run(DataserviceApplication.class, args);
    }

}

或在配置文件中:

<!-- 开启AOP拦截 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<mvc:annotation-driven />
<!-- 定义Spring描述Bean的范围,使切面类AuditAop可以被扫描到  -->
<context:component-scan base-package="**.common" >
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

注解配置

package com.cjia.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSON;
import com.cjia.common.InnerApp;
import com.cjia.common.Moudle;
import com.cjia.common.ResConstance.UserGroup;
import com.cjia.common.annotation.Audit;
import com.cjia.common.reflect.LoginValueReturn;
import com.cjia.model.Menu;
import com.cjia.model.ResponseContent;
import com.cjia.model.User;
import com.cjia.service.MenuService;
import com.cjia.service.UserService;
import com.cjia.utils.DigestUtil;

/**
 * 处理内部系统登录信息验证
 */
@Controller
public class LoginController {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);

    @Autowired
    private UserService userService;

    @Autowired
    private MenuService menuService;

    /**
     * 处理登录页表单提交信息
     */
    @ResponseBody
    @PostMapping("/doLogin")
    @Audit(moudleCode = Moudle.LOGIN, extension = LoginValueReturn.class)
    public ResponseContent doLogin(HttpServletRequest req) throws Exception {
        String email = req.getParameter("email");
        String password = req.getParameter("password");
        int appId = Integer.valueOf(req.getParameter("appId"));
        // 获取到的这么多user(同一账户)只是role或门店不同,密码等其他都一致
        List<User> users = new ArrayList<>();
        // 对SRP报表系统不允许管家登录
        if (appId == InnerApp.CIS) {
            users = userService.selectByEmail4CIS(email);
        } else {
            users = userService.selectByEmail(email);
        }
        ResponseContent responseContent = new ResponseContent();
        if (users != null && !users.isEmpty()
                && users.get(0).getPassword().equals(DigestUtil.threeHash(password, users.get(0).getSecurityKey()))) {
            User user = users.get(0);
            LOGGER.debug("登陆验证成功{}", user);
            // 设置roleIdLst和branchMap
            List<Integer> roleIdLst = users.stream().map(User::getRoleId).collect(Collectors.toList());
            Map<String, String> branchMap = new HashMap<>();
            Map<Integer, List<Menu>> menuMap = getAllMenus(users);
            for (User u : users) {
                branchMap.put(u.getBranchId() + "", u.getBranchName());
            }
            user.setRoleIdLst(roleIdLst);
            user.setBranchMap(branchMap);
            // 给user设置菜单列表
            user.setMenuMap(menuMap);
            // 登陆成功后,需要看此用户的信息是否存在于redis,存在则刷新过期时间,不存在则插入记录
            String msgKey = userService.insertIntoRedis(user);
            responseContent.setRespCode(ResponseContent.RESPCODE_SUCCESS);
            responseContent.setMessage("登陆成功");
            responseContent.setData(msgKey);
        } else {
            LOGGER.debug("登陆验证失败");
            responseContent.setRespCode(ResponseContent.RESPCODE_FAILURE);
            responseContent.setMessage("登陆失败,请检查用户账号密码是否正确");
        }
        String jsonString = JSON.toJSONString(responseContent);
        LOGGER.debug("登录返回信息:{}", jsonString);
        return responseContent;
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring AOP与AspectJ的对比及应用详解

    目录 1 简介 2 Spring AOP vs AspectJ 2.1 织入方式 2.2 Joinpoints 2.3 性能 3 Spring Boot使用AspectJ 3.1 引入依赖 3.2 被AOP的对象 3.3 配置Aspect 3.4 maven插件 3.5 执行及测试 3.6 一些遇到的错误 4 总结 1 简介 AOP,即面向切面编程是很常用的技术,特别是在Java Web开发中.而最流行的AOP框架分别是Spring AOP和AspectJ. 2 Spring AOP vs As

  • Spring AOP实现声明式事务机制源码解析

    目录 一.声明式全局事务 二.源码 三.小结: 一.声明式全局事务 在Seata示例工程中,能看到@GlobalTransactional,如下方法示例: @GlobalTransactional public boolean purchase(long accountId, long stockId, long quantity) { String xid = RootContext.getXID(); LOGGER.info("New Transaction Begins: " +

  • Spring AOP统一功能处理示例代码

    目录 1. 什么是Spring AOP? 2. 为什要用 AOP? 3. Spring AOP 应该怎么学习呢? 3.1AOP组成 3.1.1 切面(Aspect) 3.1.2 连接点(Join Point) 3.1.3 切点(Pointcut) 3.1.4 通知(Advice) 3.2 Spring AOP实现 3.2.1 添加 AOP 框架支持 3.2.2 定义切面和切点. 3.2.3 定义相关通知 3.3 Spring AOP 实现原理 3.3.1 动态代理 3.3.2 JDK和CGLIB

  • Spring使用AOP完成统一结果封装实例demo

    目录 Spring使用AOP完成统一结果封装 Demo实现 Spring使用AOP完成统一结果封装 起因:自己写项目的时候忍受不了每个方法都要写一个retrun Result.success();和 retrun Result.error();,同时想到项目运行时异常的统一捕捉处理,于是我就在想有没有一种方法能够快捷有效的实现统一返回结果格式的方法.同时也能够比较方便的设置各种参数方便使用,于是我就想到AOP. Demo实现 引入依赖 <dependency> <groupId>o

  • SpringBoot使用AOP与注解实现请求参数自动填充流程详解

    首先定义一个加在方法上的注解 import java.lang.annotation.*; /** * 开启自动参数填充 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented @Inherited public @interface AutoParameterFill { /** * 要填充的字段名,不写的话默认下面类的子类中的字段都要填充 * * @see AutoParameterFi

  • Spring AOP的概念与实现过程详解

    目录 Aop 实现aop方式一 实现aop方式二 注解实现aop Aop 什么是Aop? AOP就是面向切面编程,通过预编译方式以及运行期间的动态代理技术来实现程序的统一维护功能. 什么是切面,我理解的切面就是两个方法之间,两个对象之间,两个模块之间就是一个切面.假设在两个模块之间需要共同执行一系列操作,并且最后将这一系列操作注入到两个模块之间的指定位置.此时这一系列操作就是切面,注入这些操作的位置称之为切点. 举例:公司员工上班 A员工上班需要在前台进行打卡,同样的B员工…其他员工都需要在前台

  • Spring AOP源码深入分析

    目录 1. 前言 2. 术语 3. 示例 4. @EnableAspectJAutoProxy 5. AbstractAutoProxyCreator 6. 构建Advisor 7. 创建代理对象 8. DynamicAdvisedInterceptor 9. CglibMethodInvocation 10. Advice子类 1. 前言 Spring除了IOC和DI,还有另一个杀手锏功能——Spring AOP.AOP是一种面向切面的编程思想,它的关注点是横向的,不同于OOP的纵向.面向对象

  • Spring AOP 实现自定义注解的示例

    自工作后,除了一些小项目配置事务使用过 AOP,真正自己写 AOP 机会很少,另一方面在工作后还没有写过自定义注解,一直很好奇注解是怎么实现他想要的功能的,刚好做项目的时候,经常有人日志打得不够全,经常出现问题了,查日志的才发现忘记打了,所以趁此机会,搜了一些资料,用 AOP + 自定义注解,实现请求拦截,自定义打日志,玩一下这两个东西,以下是自己完的一个小例子,也供需要的同学参考. 1. 注解如下: package cn.bridgeli.demo.annotation;   import j

  • 详解使用Spring AOP和自定义注解进行参数检查

    引言 使用SpringMVC作为Controller层进行Web开发时,经常会需要对Controller中的方法进行参数检查.本来SpringMVC自带@Valid和@Validated两个注解可用来检查参数,但只能检查参数是bean的情况,对于参数是String或者Long类型的就不适用了,而且有时候这两个注解又突然失效了(没有仔细去调查过原因),对此,可以利用Spring的AOP和自定义注解,自己写一个参数校验的功能. 代码示例 注意:本节代码只是一个演示,给出一个可行的思路,并非完整的解决

  • 使用spring boot通过自定义注解打印所需日志

    spring boot自定义注解打印日志 在实际项目中可能需要监控每个接口的请求时间以及请求参数等相关信息,那么此时我们想到的就是两种实现方式,一种是通过拦截器实现,另一种则通过AOP自定义注解实现. 本文介绍自定义注解实现方式 自定义注解,四个元注解这次就不解释了. @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface WebLog { /** * 日志信息描述 */ String d

  • Spring AOP使用@Aspect注解 面向切面实现日志横切的操作

    引言: AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型. 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率. 在Spring AOP中业务逻辑仅仅只关注业务本身,将日志记录,性能统计,安全控制,事务处理,异

  • 基于 Spring Aop 环绕通知实现 Redis 缓存双删功能(示例代码)

    基于 spring aop 常规应用场景多是用于日志记录以及实现 redis 分布式锁,在 github 中也有项目是把它拿来当作缓存的异常捕捉.从而避免影响实际业务的开发:在某天,笔者有个业务开发是给某个服务模块增加 redis 缓存.增加缓存就会涉及 redis 删除.所以笔者就在思考是不是可以用环绕通知的方式来进行实现 代码实现 结构示意图: 自定义注解 RedisDelByDbUpdate @Repeatable 表示允许在同一个地方上使用相同的注解,没有该注解时贴相同注解会报错 @Ta

  • Spring Boot中自定义注解结合AOP实现主备库切换问题

    摘要:本篇文章的场景是做调度中心和监控中心时的需求,后端使用TDDL实现分表分库,需求:实现关键业务的查询监控,当用Mybatis查询数据时需要从主库切换到备库或者直接连到备库上查询,从而减小主库的压力,在本篇文章中主要记录在Spring Boot中通过自定义注解结合AOP实现直接连接备库查询. 一.通过AOP 自定义注解实现主库到备库的切换 1.1 自定义注解 自定义注解如下代码所示 import java.lang.annotation.ElementType; import java.la

  • Spring Boot 通过AOP和自定义注解实现权限控制的方法

    本文介绍了Spring Boot 通过AOP和自定义注解实现权限控制,分享给大家,具体如下: 源码:https://github.com/yulc-coding/java-note/tree/master/aop 思路 自定义权限注解 在需要验证的接口上加上注解,并设置具体权限值 数据库权限表中加入对应接口需要的权限 用户登录时,获取当前用户的所有权限列表放入Redis缓存中 定义AOP,将切入点设置为自定义的权限 AOP中获取接口注解的权限值,和Redis中的数据校验用户是否存在该权限,如果R

  • Spring AOP如何实现注解式的Mybatis多数据源切换详解

    一.为什么要使用多数据源切换? 多数据源切换是为了满足什么业务场景?正常情况下,一个微服务或者说一个WEB项目,在使用Mybatis作为数据库链接和操作框架的情况下通常只需要构建一个系统库,在该系统库创建业务表来满足需求,当然也有分为测试库和正式库dev/prod,不过这俩库的切换是使用配置文件进行切分的,在项目启动时或者打成maven JAR包指定environment-dev.properties或者environment-prod.properties. 那么当程序运行过程中,比如一个co

  • Spring Aop之AspectJ注解配置实现日志管理的方法

    最近项目要做一个日志功能,我用Spring Aop的注解方式来实现. 创建日志注解 package com.wyj.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lan

  • Spring AOP实现打印HTTP接口出入参日志

    目录 前言 思路 1.编写一个HTTP接口 2.编写一个AOP日志配置 3.结果测试 前言 最近在维护一个运营端的系统,和前端联调的过程中,经常需要排查一些交互上的问题,每次都得看前端代码的传参和后端代码的出参,于是打算给HTTP接口加上出入参日志. 但看着目前的HTTP接口有点多,那么有什么快捷的方式呢?答案就是实用Spring的AOP功能,简单实用. 思路 定义个一个SpringAOP的配置类,里边获取请求的URL.请求的入参.相应的出参,通过日志打印出来. SpringBoot的aop依赖

随机推荐