SpringBoot AOP切面编程 使用案例

news2025/1/10 20:39:49

参考资料

  1. Springboot AOP实现指定敏感字段数据加密 (数据加密篇 二)
  2. 【SpringBoot-3】切面AOP实现权限校验:实例演示与注解全解
  3. 【小家Spring】Spring AOP中@Pointcut切入点表达式最全面使用介绍
  4. AOP编程过程中的Signature接口

本篇文章核心思想均摘自参考资料所示的博客,详情请参阅其博客。


目录

  • 一. 知识储备
    • 1.1 图示
    • 1.2 图示解释
  • 二. 前期准备1
    • 2.1 POM文件
    • 2.2 自定义注解
    • 2.3 form基类
    • 2.4 提供业务数据
    • 2.5 封装返回前台数据的实体类
  • 三. AOP实现页面国际化 + 共通属性值自动封装案例
    • 3.1 页面
    • 3.2 Controller层
    • 3.3 Service层
    • 3.4 `核心`的切面编程类
  • 四. 前期准备2
    • 4.1 自定义注解
      • 4.1.1 标记方法需要`加密`的注解
      • 4.1.2 标记方法需要`解密`的注解
      • 4.1.3 标记属性需要`加密或者解密`的注解
  • 五. 敏感数据加密和解密案例
    • 5.1 前台
    • 5.2 前台Form
    • 5.3 返回数据实体类
    • 5.4 Mapper层
    • 5.5 Controller层
    • 5.6 Service层
    • 5.7 Base64加密解密工具类
    • 5.8 `核心`的切面编程类


一. 知识储备

1.1 图示

在这里插入图片描述

在这里插入图片描述

1.2 图示解释

  • Pointcut:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。
    切点分为execution方式和annotation方式。
    • execution可以用路径表达式指定哪些类织入切面
    • annotation可以指定被哪些注解修饰的代码织入切面。
  • Advice:处理,包括处理时机和处理内容。
    • 处理内容就是要做什么事,比如校验权限和记录日志。
    • 处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
  • Aspect:切面,即Pointcut和Advice。
  • Joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
  • Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。

二. 前期准备1

2.1 POM文件

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.2 自定义注解

  • 该自定义注解作用于实体类的属性上
  • 自定义注解和接口类似,可以在接口中定义枚举类
  • 该注解用来标记属性值被自动添加为当前Date或者登录用户ID
import java.lang.annotation.*;

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldValueAnnotation {

    Category type();
	
	// 自定义注解中定义的枚举类
    enum Category {
        dateField,
        userIdField,
    }
}

2.3 form基类

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;

import java.util.Date;
import java.util.Map;

@Data
public class BaseForm {
	
	// 页面ID
    private String pageId;
	
	// 登录用户ID
    @FieldValueAnnotation(type = FieldValueAnnotation.Category.userIdField)
    private String loginUserId;
	
	// 创建日期
    // 返回前台json数据的时候,忽略此字段
    @JsonIgnore
    @FieldValueAnnotation(type = FieldValueAnnotation.Category.dateField)
    private Date createDate;
	
	// 更新日期
    @JsonIgnore
    @FieldValueAnnotation(type = FieldValueAnnotation.Category.dateField)
    private Date updateDate;
	
	// 语言code
    private String languageCode;
	
	// 画面项目Map
    private Map<String, String> itemMap;
}

2.4 提供业务数据

import lombok.Builder;
import lombok.Data;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class CommonService {

    // 支持的所有语言列表
    public List<LanguageEntity> getLanguageList() {

        return Arrays.asList(
                LanguageEntity.builder().code("jp").language("日语").build(),
                LanguageEntity.builder().code("zh").language("中文").build(),
                LanguageEntity.builder().code("en").language("英文").build()
        );
    }
    
    // 系统支持语言实体类
    @Data
    @Builder
    public static class LanguageEntity {

        private String code;

        private String language;
    }

    // 模拟数据库查询到的数据
    private Map<String, Map<String, Map<String, String>>> getItemMap() {

        // j002页面的国际化数据
        Map<String, String> zhMapJ002 = new HashMap<>() {{
            put("name", "姓名");
            put("age", "年龄");
            put("language", "语言");
            put("btnName", "提交");
        }};

        Map<String, String> jpMapJ002 = new HashMap<>() {{
            put("name", "名前");
            put("age", "年齢");
            put("language", "言語");
            put("btnName", "コミット");
        }};

        Map<String, String> enMapJ002 = new HashMap<>() {{
            put("name", "name");
            put("age", "age");
            put("language", "language");
            put("btnName", "commit");
        }};

        Map<String, Map<String, String>> j002Map = new HashMap<>() {{
            put("zh", zhMapJ002);
            put("jp", jpMapJ002);
            put("en", enMapJ002);
        }};

        // j003页面的国际化数据
        Map<String, String> zhMapJ003 = new HashMap<>() {{
            put("fruit", "苹果");
            put("drink", "可乐");
            put("language", "语言");
        }};

        Map<String, String> jpMapJ003 = new HashMap<>() {{
            put("fruit", "リンゴ");
            put("drink", "コーラ");
            put("language", "言語");
        }};

        Map<String, String> enMapJ003 = new HashMap<>() {{
            put("fruit", "apple");
            put("drink", "cola");
            put("language", "language");
        }};

        Map<String, Map<String, String>> j003Map = new HashMap<>() {{
            put("zh", zhMapJ003);
            put("jp", jpMapJ003);
            put("en", enMapJ003);
        }};

        // 最终的数据
        return new HashMap<>() {{
            put("j002", j002Map);
            put("j003", j003Map);
        }};
    }

    // 根据地区和画面id获取画面项目
    public Map<String, String> getItemByLocal(String local, String pageId) {
        return this.getItemMap().get(pageId).get(local);
    }
    
    // 模拟数据库获取下拉列表内容
    private Map<String, List<SelectItem>> getSelectList() {

        List<SelectItem> zhSelectItems = Arrays.asList(
                SelectItem.builder().code("c001").name("汽车").build(),
                SelectItem.builder().code("c002").name("电脑").build(),
                SelectItem.builder().code("c003").name("手机").build()
        );

        List<SelectItem> jpSelectItems = Arrays.asList(
                SelectItem.builder().code("c001").name("車").build(),
                SelectItem.builder().code("c002").name("パソコン").build(),
                SelectItem.builder().code("c003").name("スマホ").build()
        );

        List<SelectItem> enSelectItems = Arrays.asList(
                SelectItem.builder().code("c001").name("car").build(),
                SelectItem.builder().code("c002").name("computer").build(),
                SelectItem.builder().code("c003").name("phone").build()
        );

        return new HashMap<>(){{
            put("zh", zhSelectItems);
            put("jp", jpSelectItems);
            put("en", enSelectItems);
        }};
    }
    
    // 根据画面local地区,获取下列列表内容
    public List<SelectItem> getSelectListByLocal(String local) {
        if (ObjectUtils.isEmpty(local)) {
            local = "jp";
        }
        return this.getSelectList().get(local);
    }
    
    // 画面下拉列表实体类
    @Data
    @Builder
    public static class SelectItem {

        private String code;

        private String name;
    }
}

2.5 封装返回前台数据的实体类

import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;

@Data
@EqualsAndHashCode(callSuper = false)
public class ResultEntity implements Serializable {

    private Boolean result;

    private Object entity;

    private ResultEntity(Boolean result, Object entity) {
        this.result = result;
        this.entity = entity;
    }

    public static ResultEntity ok(Object entity) {
        return new ResultEntity(true, entity);
    }
}

三. AOP实现页面国际化 + 共通属性值自动封装案例

3.1 页面

⏹登录页面 ⇒ j001.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <h1>登录页面!!!</h1>
    <button id="btn">点击跳转到国际化页面</button>
</div>
</body>
<script type="text/javascript" th:src="@{/js/public/jquery-3.6.0.min.js}"></script>
<script>
    $("#btn").click(() => {
        window.location = "/j002/init";
    });
</script>
</html>

⏹登录之后显示的国际化页面 ⇒ j002.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>

    <label>
        <span id="language">[[${itemMap.language}]]</span>
        <select id="langList">
            <th:block th:each="content : ${langList}">
                <option th:value="${content.code}">[[${content.language}]]</option>
            </th:block>
        </select>
    </label>
    <hr>

    <span id="name">[[${itemMap["name"]}]]</span><br>
    <span id="age">[[${itemMap["age"]}]]</span>

    <hr>
    <select id="selectList" th:object="${entity}">
        <th:block th:each="selectEntity : *{selectList}">
            <option th:value="${selectEntity.code}">[[${selectEntity.name}]]</option>
        </th:block>
    </select>

    <button id="btnName">[[${itemMap["btnName"]}]]</button>
</div>
</body>
<script type="text/javascript" th:src="@{/js/public/jquery-3.6.0.min.js}"></script>
<script th:inline="javascript">
    // 打印当前页面的项目信息
    const itemInfo = [[${itemMap}]];
    console.log(itemInfo);

    // 打印当前画面的id
    const pageId = /*[[${pageId}]]*/ '';
    console.log(pageId);
</script>
<script>
    $(function() {
        eventBind();
    });

    function eventBind() {
		
		// 当语言下拉列表切换语言的时候,修改页面上的语言项目和下拉列表项目
        $("#langList").change(function({target: {value}}) {

            const data = {
                languageCode: value,
                pageId,
            };

            $.ajax({
                url: `/j002/languageChange`,
                type: 'POST',
                data: JSON.stringify(data),
                contentType: 'application/json;charset=utf-8',
                success: function (data, status, xhr) {
                    setViewItem(data);
                    setSelectList(data);
                }
            });
        });
		
		// 提交数据到后台
        $("#btnName").click(function (event) {
            $.ajax({
                url: `/j002/dataToDB`,
                type: 'POST',
                data: JSON.stringify({pageId}),
                contentType: 'application/json;charset=utf-8',
                success: function (data, status, xhr) {
                    console.log(data);
                }
            });
        });
    }

    // 选中画面上的所有项目
    function setViewItem({entity: {itemMap}}) {
        for (const [key, value] of Object.entries(itemMap)) {
            $(`#${key}`).text(value);
        }
    }

    // 设置下拉列表选纵横
    function setSelectList({entity: {selectList}}) {

        const selectObj = $("#selectList");
        const selectedCode = selectObj.val();
        selectObj.empty();

        for (const entity of selectList) {
            $("<option>", {
                value: entity.code,
                text: entity.name
            }).appendTo("#selectList");
        }
        selectObj.val(selectedCode);
    }
</script>
</html>

3.2 Controller层

⏹ 登录页面Controller

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpSession;
import java.util.List;

@Controller
@RequestMapping("/j001")
public class J001LoginController {

    @Autowired
    private HttpSession session;

    @Autowired
    private CommonService commonService;

    @GetMapping("/init")
    public ModelAndView init() {
		
		// 获取整个系统支持的语言下列列表放到session中
        List<CommonService.LanguageEntity> languageEntityList = commonService.getLanguageList();
        session.setAttribute("_langList", languageEntityList);

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("j001");
        return modelAndView;
    }
}

⏹ 国际化显示页面Controller

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@Controller
@RequestMapping("/j002")
public class J002BusinessController {

    @Autowired
    private CommonService commonService;

    @Autowired
    private J002BusinessService service;

    @GetMapping("/init")
    public ModelAndView init(J002Form form) {
		
		// 将下拉列表放到前台
        J002Entity entity = new J002Entity();
        entity.setSelectList(commonService.getSelectListByLocal(form.getLanguageCode()));

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("j002");
        modelAndView.addObject("testMsg", "测试消息");
        modelAndView.addObject("entity", entity);
        return modelAndView;
    }
	
	// 语言切换的时候,去数据库查询下拉列表的国际化数据,然后放到前台
    @PostMapping("/languageChange")
    @ResponseBody
    public ResultEntity languageChange(@RequestBody J002Form form) {

        List<CommonService.SelectItem> selectListByLocal = commonService.getSelectListByLocal(form.getLanguageCode());

        J002Entity entity = new J002Entity();
        entity.setSelectList(selectListByLocal);
        return ResultEntity.ok(entity);
    }
	
	// 插入数据到数据库
    @PostMapping("/dataToDB")
    @ResponseBody
    public void dataToDB(@RequestBody J002Form form) {
        service.insertDataToDB(form);
        service.updateDataToDB(form);
    }
}

3.3 Service层

import org.springframework.stereotype.Service;

@Service
public class J002BusinessService {

    public void insertDataToDB(J002Form form) {

        System.out.println(form);
    }

    public void updateDataToDB(J002Form form) {
        System.out.println(form);
    }
}

3.4 核心的切面编程类

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Path;
import java.nio.file.Paths;

import java.util.Date;
import java.util.Map;

@Aspect
@Component
public class ControllerAspect {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private HttpSession session;

    @Autowired
    private CommonService commonService;

    // 模拟当前的登录用户
    private final static String loginUserId = "jmw";

    // 只有一层包,所以是 controller.* ,如果有两层包的话,就是 controller.*.*
    @Before("execution(* com.example.jmw.aspect.controller.*.init(..)) && args(form)")
    public void beforeInit(JoinPoint join, BaseForm form) {

        // 获取当前画面的ID
        String uri = request.getRequestURI().substring(request.getContextPath().length());
        Path uriPath = Paths.get(uri);
        String pageId = uriPath.subpath(0, 1).toString();

        // 将当前的画面ID放到form中
        form.setPageId(pageId);
    }

    // 在ModelAndView返回到页面之前,向其中加入页面上的项目
    @AfterReturning(value = "execution(* com.example.jmw.aspect.controller.*.init(..)) && args(form)", argNames = "join,form,modelAndView", returning = "modelAndView")
    public void afterReturningInit(JoinPoint join, BaseForm form, ModelAndView modelAndView) {

        // 获取画面ID
        String pageId = form.getPageId();

        // 根据画面ID查询当前页面的国际化项目名称
        Map<String, String> itemMap = commonService.getItemByLocal("jp", pageId);
        modelAndView.addObject("itemMap", itemMap);

        // 将当前页面的语言list传到前台
        modelAndView.addObject("langList", session.getAttribute("_langList"));

        // 将当前页面的pageId传到前台
        modelAndView.addObject("pageId", pageId);
    }

    @AfterReturning(value = "execution(* com.example.jmw.aspect.controller.*.languageChange(..)) && args(form, ..)", argNames = "join,form,resultEntity", returning = "resultEntity")
    public void postLanguageChange(JoinPoint join, BaseForm form, ResultEntity resultEntity) {

        // 如果不是成功的响应就return
        if (!resultEntity.getResult()) {
            return;
        }

        // 因为每个画面用到的实体类都不相同,因此此处将每个画面的实体类获取出来之后,转换为父类的BaseForm,每个画面共通的字段都放到此处
        BaseForm baseForm = (BaseForm)resultEntity.getEntity();
        if (ObjectUtils.isEmpty(baseForm)) {
            baseForm = new BaseForm();
        }

        // 根据画面ID查询当前页面的国际化项目名称
        Map<String, String> itemMap = commonService.getItemByLocal(form.getLanguageCode(), form.getPageId());

        // 将国际化项目名称放到BaseForm中,也就相当于放到了每个画面自己的实体类中
        baseForm.setItemMap(itemMap);
    }

    // 切面service包下面所有的Service类里面所有以 insert 或者 update 开头的方法,并且该方法一定要有一个参数为 form
    @Before("execution(* com.example.jmw.aspect.service.*.insert*(..)) && args(form) " +
            // 可以单独定义一个类,类里面用来聚合所有的 切面表达式
            "|| com.example.jmw.common.aspect.ControllerAspect.PointcutClass.updateAspect(form)")
    public void beforeInsertOrUpdate(JoinPoint join, BaseForm form) throws Exception {

        // 获取出切面对象
        Signature signature = join.getSignature();
        this.getSignatureInfo(signature);

        // 获取出切面对象的所有参数对象
        Object[] args = join.getArgs();
        // 获取出第一个参数,并转换为父类(获取出共通的属性)
        Object objForm = args[0];
        // 通过反射来设置属性
        this.setFields(objForm);
		
		// 将子类强转换为共通的基类,基类中有我们要转换的共通的属性
        BaseForm argForm = (BaseForm)objForm;

        // 可以看到通过JoinPoint得到的参数对象和beforeInsertOrUpdate方法中的参数对象本质上是一个对象
        System.out.println(argForm == form);  // true

        // 通过反射获取出设置的loginUserId
        String loginUserId = (String)objForm.getClass().getField("loginUserId").get(objForm);

        // 给loginUserId添加后缀,通过这种方式设置form中的值更简洁,但是需要注意抽取基类和共通属性
        form.setLoginUserId(loginUserId + "~我是一个后缀~");
    }
	
	// 设置Object对象中的属性
    private void setFields(Object objectForm) throws IllegalAccessException {

        // 获取当前类的class对象
        Class<?> objClass = objectForm.getClass();
        // 获取当前类的父类的class对象
        Class<?> superclass = objClass.getSuperclass();
        // 获取父类上声明的所有类型的属性
        Field[] fields = superclass.getDeclaredFields();

        for (Field field : fields) {

            if (!field.isAnnotationPresent(FieldValueAnnotation.class)) {
                continue;
            }

            // 获取field属性上标记的注解
            FieldValueAnnotation fieldAnnotation = field.getAnnotation(FieldValueAnnotation.class);
            FieldValueAnnotation.Category fieldTypeEnum = fieldAnnotation.type();
            // 获取注解上标记的类型名称
            String name = fieldTypeEnum.name();

            // 因为属性是私有属性,通过setAccessible()将其设置为允许访问
            field.setAccessible(true);

            // 根据类型设置不同的值
            if (name.equals(FieldValueAnnotation.Category.userIdField.name())) {
                field.set(objectForm, loginUserId);
            } else if (name.equals(FieldValueAnnotation.Category.dateField.name())) {
                field.set(objectForm, new Date());
            }
        }
    }

    // 可以将切面表达式聚合到一个类里面集中管理
    public class PointcutClass {

        @Pointcut("execution(* com.example.jmw.aspect.service.*.update*(..)) && args(form, ..)")
        private void updateAspect(BaseForm form){}
    }
	
	// 获取切面Signature中的信息
    private void getSignatureInfo(Signature signature) {

        // 返回此签名的标识符部分。对于方法,这将返回方法名称。
        String name = signature.getName();
        System.out.println(name);  // insertDataToDB

        // 获取被切面的对象的短名称
        String shortString = signature.toShortString();
        System.out.println(shortString);  // J002BusinessService.insertDataToDB(..)

        // 获取被切面的对象的长名称
        String longString = signature.toLongString();
        System.out.println(longString);  // public void com.example.jmw.aspect.service.J002BusinessService.insertDataToDB(com.example.jmw.aspect.form.J002Form)

        // 获取被切面方法切到的方法的修饰符
        int modifiers = signature.getModifiers();
        String modifierName = Modifier.toString(modifiers);
        System.out.println(modifierName);  // public

        Class declaringType = signature.getDeclaringType();
        System.out.println(declaringType);  // class com.example.jmw.aspect.service.J002BusinessService

        String declaringTypeName = signature.getDeclaringTypeName();
        System.out.println(declaringTypeName);  // com.example.jmw.aspect.service.J002BusinessService
    }
}

四. 前期准备2

4.1 自定义注解

4.1.1 标记方法需要加密的注解

import java.lang.annotation.*;

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptInfoMethodAnnotation {

}

4.1.2 标记方法需要解密的注解

import java.lang.annotation.*;

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptInfoMethodAnnotation {

}

4.1.3 标记属性需要加密或者解密的注解

import java.lang.annotation.*;

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptFieldAnnotation {

}

五. 敏感数据加密和解密案例

5.1 前台

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>
        <button id="insert">插入数据</button>
        <button id="select">查询数据</button>
    </div>
</body>
<script type="text/javascript" th:src="@{/js/public/jquery-3.6.0.min.js}"></script>
<script>

    $("#insert").click(function() {
        const data = {
            id: "011",
            name: "贾飞天",
            password: "pass001",
            email: "123@qq.com"
        };

        $.ajax({
            url: `/j003/insert`,
            type: 'POST',
            data: JSON.stringify(data),
            contentType: 'application/json;charset=utf-8',
            success: function (data, status, xhr) {
                console.log(data);
            }
        });
    });

    $("#select").click(function() {

        $.ajax({
            // 注意,此处的查询参数少了一个0
            url: `/j003/select?id=11`,
            type: 'GET',
            success: function (data, status, xhr) {
                console.log(data);
            }
        });
    });
</script>
</html>

5.2 前台Form

import com.example.jmw.common.annotation.aspect.EncryptDecryptFieldAnnotation;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
public class J003Form extends BaseForm {

    private String id;

    private String name;

    // 需要加密的字段
    @EncryptDecryptFieldAnnotation
    private String password;

    // 需要加密的字段
    @EncryptDecryptFieldAnnotation
    private String email;
}

5.3 返回数据实体类

  • 父类中的J003Form已经有了passwordemail字段
  • 子类继承父类,并定义passwordemail字段来重写父类属性
import com.example.jmw.common.annotation.aspect.EncryptDecryptFieldAnnotation;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
public class J003Entity extends J003Form {

    // 需要解密的字段
    @EncryptDecryptFieldAnnotation
    private String password;

    // 需要解密的字段
    @EncryptDecryptFieldAnnotation
    private String email;
}

5.4 Mapper层

⏹接口

public interface J003Mapper {

    void insertUserInfo(J003Form form);

    J003Entity selectUserInfo(String id);
}

⏹SQL

<?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.example.jmw.mapper.J003Mapper">

    <select id="insertUserInfo" parameterType="com.example.jmw.aspect.form.J003Form">
       INSERT INTO
        aspect_user(
          id
          , name
          , password
          , email
        )
        VALUES (
          #{id}
          , #{name}
          , #{password}
          , #{email}
        )
    </select>

    <select id="selectUserInfo" parameterType="string" resultType="com.example.jmw.aspect.entity.J003Entity">
        SELECT
          id
          , name
          , password
          , email
        FROM
          aspect_user
        WHERE
          id = #{id}
    </select>
</mapper>

5.5 Controller层

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/j003")
public class J003BusinessController {

    @Autowired
    private J003BusinessService service;

    @GetMapping("/init")
    public ModelAndView init() {

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("j003");
        return modelAndView;
    }

    @PostMapping("/insert")
    @ResponseBody
    public void insertUserInfo(@RequestBody J003Form form) {
        service.insertUserInfo(form);
    }

    @GetMapping("/select")
    @ResponseBody
    public J003Entity selectUserInfo(@RequestParam String id) {

        J003Entity entity = service.selectUserInfoTest(id, "");
        System.out.println(entity);

        J003Entity j003Entity = service.selectUserInfo(id);
        return j003Entity;
    }
}

5.6 Service层

import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class J003BusinessService {

    @Resource
    private J003Mapper mapper;

    // 给信息加密的注解
    @EncryptInfoMethodAnnotation
    public void insertUserInfo(J003Form form) {
        mapper.insertUserInfo(form);
    }

    // 给信息解密的注解
    @DecryptInfoMethodAnnotation
    public J003Entity selectUserInfo(String id) {
        return mapper.selectUserInfo(id);
    }

    // 给信息解密的注解
    @DecryptInfoMethodAnnotation
    public J003Entity selectUserInfoTest(String id, String blank) {
        return mapper.selectUserInfo(id);
    }
}

5.7 Base64加密解密工具类

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public final class Base64Utils {

    private final static Base64.Encoder encoder = Base64.getEncoder();
    private final static Base64.Decoder decoder = Base64.getDecoder();

    // 加密
    public static String encode(String text) {
        return encoder.encodeToString(text.getBytes(StandardCharsets.UTF_8));
    }

    // 解密
    public static String decode(String encodedText) {
        return new String(decoder.decode(encodedText), StandardCharsets.UTF_8);
    }
}

5.8 核心的切面编程类

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

import static java.util.stream.Collectors.toList;

@Aspect
@Component
public class EncryptAndDecryptAspect {

    /*
        功能: 获取方法上的待加密对象
        切面内容: 方法上被标记了 EncryptInfoMethodAnnotation 注解
    */
    @Before("@annotation(com.example.jmw.common.annotation.aspect.EncryptInfoMethodAnnotation)")
    public void infoEncrypt(JoinPoint joinPoint) throws Exception {

        Object form = joinPoint.getArgs()[0];
        Class<?> formClass = form.getClass();

        Field[] declaredFields = formClass.getDeclaredFields();
        for (Field field : declaredFields) {

            if (!field.isAnnotationPresent(EncryptDecryptFieldAnnotation.class) || field.getType() != String.class) {
                continue;
            }

            // 强制private属性可访问
            field.setAccessible(true);

            // 属性值进行加密
            String encodeValue = Base64Utils.encode((String) field.get(form));
            // 加密后的属性值塞到原属性中
            field.set(form, encodeValue);
        }
    }

    /*
        功能: 获取方法上的待解密对象,这种写法还能获取方法上标记的注解
        切面内容: 方法上被标记了 DecryptInfoMethodAnnotation 注解,并且方法上需要有一个参数
        注意:如果方法上标记了 DecryptInfoMethodAnnotation 注解,并且方法没有参数或者多个参数的话,不会被切面到

        @Around可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值。

        @Around功能虽然强大,但通常需要在线程安全的环境下使用。
        因此,如果使用普通的Before、AfterReturning就能解决的问题,就没有必要使用Around了。
        此处使用 @Around 仅为测试其作用
    */
    @Around("@annotation(decryptInfoMethodAnnotation) && args(id)")
    public Object infoDecrypt(ProceedingJoinPoint joinPoint, DecryptInfoMethodAnnotation decryptInfoMethodAnnotation, String id) throws Throwable {

        System.out.println(decryptInfoMethodAnnotation);
        System.out.println(id);  // 11

        // 对参数进行处理,如果不满3位就补0
        String leftPadStr = StringUtils.leftPad(id, 3, "0");
        Object[] args = {
                leftPadStr
        };

        /*
            执行切面切到的方法,获取到方法执行的返回值
            如果使用的是 joinPoint.proceed() 的话,则相当于不改变目标方法的参数值
            我们使用的是 joinPoint.proceed(args) , 系统 args 是我们改变了参数值之后的参数数组

            如果传入的Object[]数组长度与目标方法所需要的参数个数不相等, 或者Object[]数组元素与目标方法所需参数的类型不匹配,程序就会出现异常。
        */
        Object resultObj = joinPoint.proceed(args);

        if (ObjectUtils.isEmpty(resultObj)) {
            return resultObj;
        }

        // 过滤出标记了 @EncryptDecryptFieldAnnotation 注解的属性
        Class<?> resultObjClass = resultObj.getClass();
        List<Field> fieldList = Arrays.stream(resultObjClass.getDeclaredFields()).filter(field -> field.isAnnotationPresent(EncryptDecryptFieldAnnotation.class)).collect(toList());
        if (ObjectUtils.isEmpty(fieldList)) {
            return resultObj;
        }

        for (Field field : fieldList) {
            // 强制private属性可访问
            field.setAccessible(true);

            // 对加密数据进行解密并设置到原属性中
            String encryptFieldValue = (String)field.get(resultObj);
            String decodeFieldValue = Base64Utils.decode(encryptFieldValue);
            field.set(resultObj, decodeFieldValue);
        }

        return resultObj;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/580751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(详解)vue中实现主题切换的三种方式

目录 一、背景 二、实现思路 方法1&#xff1a;定义全局的CSS变量 方法2&#xff1a;切换已定义好的css文件 方法3&#xff1a;切换顶级CSS类名 (需使用css处理器,如sass、less等) 一、背景 在我们开发中我们会遇到像是需要切换程序风格、主题切换啦这种应用场景。 参考大佬…

经典智能合约案例之发红包

经典智能合约案例&#xff1a;发红包 角色分析&#xff1a;发红包的人和抢红包的人 功能分析&#xff1a; 发红包&#xff1a;发红包的功能&#xff0c;可以借助构造函数实现&#xff0c;核心是将ether打入合约&#xff1b; 抢红包&#xff1a;抢红包的功能&#xff0c;抢成…

Flume系列:案例-Flume 聚合拓扑(常见的日志收集结构)

目录 Apache Hadoop生态-目录汇总-持续更新 1&#xff1a;案例需求-实现聚合拓扑结构 3&#xff1a;实现步骤&#xff1a; 2.1&#xff1a;实现flume1.conf - sink端口4141 2.2&#xff1a;实现flume2.conf- sink端口4141 2.3&#xff1a;实现flume3.conf - 监听端口4141 …

32 KVM管理系统资源-管理虚拟内存热插

文章目录 32 KVM管理系统资源-管理虚拟内存热插32.1 概述32.2 约束限制32.3 操作步骤32.3.1 配置虚拟机XML32.3.2 热插并上线内存 32 KVM管理系统资源-管理虚拟内存热插 32.1 概述 在虚拟化场景下&#xff0c;虚拟机的内存、CPU、外部设备都是软件模拟呈现的&#xff0c;因此…

深度学习进阶篇-国内预训练模型[5]:ERINE、ERNIE 3.0、ERNIE-的设计思路、模型结构、应用场景等详解

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

旧键盘打字 两数之和

&#x1f495;"不要因为别人的成功而感到沮丧&#xff0c;你的时机会来&#xff0c;只要你继续努力、坚持不懈。"&#x1f495; &#x1f43c;作者:不能再留遗憾了&#x1f43c; &#x1f386;专栏:Java学习&#x1f386; &#x1f697;本文章主要内容:使用哈希表的思…

2023年江苏省中职网络安全Web渗透测试解析(超详细)

一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 1.访问地址http://靶机IP/web1,分析页面内容,获取flag值,Flag格式为flag{xxx}; 2.访问地址http://靶机IP/web2,访问登录页面。用户user01的密码为1-1000以内的数,获取用户user01的密码,将密码作为Flag进行提交,Flag格式为…

Java数据结构之第十四章、泛型进阶

补充复杂示例&#xff1a; public class MyArray<E extends Comparable<E>> { ... } 表明E必须是实现了Comparable接口的 泛型基础内容 目录 一、通配符 二、通配符上界 三、通配符下界 一、通配符 ? 用于在泛型的使用&#xff0c;即为通配符 示例&#xf…

如何使用 Python Nornir 实现基于 CLI 的网络自动化?

在现代网络环境中&#xff0c;网络自动化已成为管理和配置网络设备的重要工具。Python Nornir 是一个强大的自动化框架&#xff0c;它提供了一个简单而灵活的方式来执行网络自动化任务。本文将详细介绍如何使用 Python Nornir 实现基于 CLI 的网络自动化。 1. Python Nornir 概…

jacoco增量覆盖率平台开发

先聊聊做这个平台的意义&#xff0c;从项目管理角度来说&#xff0c;测试说项目测试完成&#xff0c;该如何证明呢&#xff1f;一般情况下我们进行验收时没什么问题就算完成了&#xff0c;但是实际上测试很多情况并没有考虑到。所以该平台可以反哺测试的测试用例&#xff0c;让…

GO的服务

1.go的安装 1.1 确认版本go version go version go1.20.4 darwin/amd64 可以看到是macos10.14版本。如果是m1 需要安装对应的版本 1.2 用vscode 进行编写go的简单例子 先进入vscode的界面&#xff0c;新建一个目录为godemo&#xff0c;里面就是go的例子的工作目录&#xff0…

计算机的大小端存储模式(计算机小白必看!)

目录 1.什么是大端小端 2.为什么会有大小端模式之分呢&#xff1f; 3.如何判断当前机器为大端字节序还是小端字节序 本文将介绍计算机存储数据时的大小端问题 1.什么是大端小端 大端&#xff08;存储&#xff09;模式&#xff0c;是指数据的低位保存在内存的高地址中&…

可视化库seaborn常用操作介绍

目录 1.seaborn 概括2.Seaborn的调色板3.单变量绘图分析4.回归分析绘图5.分类图绘制6.FacetGrid使用7.Heatmap 1.seaborn 概括 seaborn库是一个用于数据可视化的Python库&#xff0c;它建立在matplotlib之上&#xff0c;可以让你轻松地创建各种美观的图表和图形。 在seaborn中…

致敬科技工作者:我们的世界因你们而美好

在我们的日常生活中&#xff0c;科技无处不在&#xff0c;而这一切离不开科技工作者的辛勤付出。作为一名科技从业者&#xff0c;我深深地理解并感悟到&#xff0c;科技工作者们的作用是不可替代的。 二十一世纪&#xff0c;科技的发展日新月异。我们见证了第一台计算机的发明…

RT1170启动详解:Boot配置、Bootable image头的组成

文章目录 1 基础知识2 BOOT配置2.1 BOOT_CFG配置2.2 BOOT_MODE 3 Bootable image3.1 文件格式3.2 Bootable image头的组成3.3 Bootable image的生成3.4 例&#xff1a;BootROM之non-XIP加载过程3.5 例&#xff1a;bin文件分析 1 基础知识 &#xff08;1&#xff09;BootROM Bo…

地面分割--Patchwork

文章目录 1问题定义2同心区域模型3按照区域划分的平面拟合4地面点似然估计&#xff08;GLE&#xff09;总结 patchwork是一种比较优秀的地面分割方法。其过程主要分为三个部分&#xff1a;同心圆环区域(CZM:concentric Zone Model)&#xff0c;按照区域划分的平面拟合(R-GPF:re…

OpenCV基础操作(5)图像平滑、形态学转换、图像梯度

import numpy as np import cv2 as cv from matplotlib import pyplot as plt一、图像平滑 1、2D卷积 我们可以对 2D 图像实施低通滤波&#xff08;LPF&#xff09;&#xff0c;高通滤波&#xff08;HPF&#xff09;等。 LPF 帮助我们去除噪音&#xff0c;模糊图像。HPF 帮助…

【数字信号处理】Goertzl算法详解推导及双音多频(DTMF)信号检测

Geortzel算法 【要点解析】 根据卷积公式 y ( n ) = ∑ m = − ∞ ∞ x ( m )

前端切图仔跑路真经

一、闭包 谈到闭包&#xff0c;我们首先要讨论的就是作用域。 1、作用域&#xff1a; 是指程序源代码中代码定义的范围。规定了如何设置变量&#xff0c;也就是确定了当前执行代码对变量的访问权限。 JavaScript采用词法作用域&#xff0c;也就是静态作用域&#xff0c;就是在…

直接带你使用 FreeRTOS 的 API 函数(基于 CubeMX 生成)(不断更新)

作者有话要说 对于这个越来约浮躁的社会&#xff0c;什么都要钱&#xff0c;特别是网上那些垃圾教程&#xff0c;越听越模糊&#xff0c;那行吧&#xff0c;我直接就从 FreeRTOS 的 API函数 学起&#xff0c;管你这么多底层内容的&#xff0c;以后再说吧&#xff01;&#xff0…