Spring MVC项目开发中,Java注解和AOP联合使用,会大有妙用

前言

为什么今天想发这篇文章呢?其实我先前公司使用的技术栈很落后,我一直想在项目中使用注解,也多次尝试使用,但都没有真正成功过!

在项目中使用自己定义的注解,时常成了我心中一个坎。由于各种借口,但都不了了之了。当初对Java注解非常感兴趣,那是因为使用Java类库时,发现Java注解有很多好处,也想在自己项目中使用自己定义的注解,表现自己牛批的一面,但就是不明白其自运行的原理,在网上搜索相关资料也没有尝试成功
在这里插入图片描述

这次项目中需要增加一个数据权限的需求,项目模块有19个,都要进行添加业务逻辑代码感觉非常的繁琐,要写大量的代码,因此进修者又想到了Java注解。但是这次与往常不同,这次是用的 Spring MVC 项目,而且也有了AOP面向切面编程的一些思想。于是就想同时使用Java注解和AOP来完成这次的业务需求。经过了大量尝试和学习,我心中的这道坎总算跨过了!

下面给大家分享一下,也希望可以帮到需要的伙伴们!

正文

在进入今天的内容分享之前,我先说明一些需要掌握的技术点。需要了解Spring MVC开发模式,熟悉Java反射机制注解的知识,同时还要掌握Spring AOP面向切面的思想。下面进修者也会给与一些这方面的知识。

辅助知识

Java反射机制

反射是框架设计的灵魂,我们开发中使用的任何框架都会用到Java反射的知识。Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

Java注解

Java注解(Java标注)是在 JDK5 时引入的新特性,有些地方称它为元数据。Java注解提供了一种安全的类似注释的机制,用来将任何的信息或元数据与程序元素(类、方法、成员变量等)进行关联。一般会附加在代码中的一些元信息上,通常用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。

Java注解的一些应用如下:

  • 生成文档:它是最常见也是最早提供的Java注解;
  • 格式检查:在编译时进行格式检查,如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;
  • 替代配置:跟踪代码依赖性,实现替代配置文件功能,比较常见的是spring 2.5 开始的基于注解配置,作用就是减少配置;
  • 反射注解:在反射的 Class、Method和Field 等函数中,有许多于 Annotation 相关的接口,可以在反射中解析并使用 Annotation。

Spring AOP原理

Spring AOP实现原理简单点说是通过动态代理实现的。如果我们为Spring的某一个Bean配置了切面,那么Spring在创建这个Bean的时候,实际上创建了这个Bean的一个代理对象,我们后续对Bean中方法的调用,其实调用的是代理类重写的代理方法。Spring AOP使用了2种动态代理方式实现,分别是 JDK动态代理和Cglib动态代理。这里不再继续说明,不明白的可以在网上搜索相关资料。
在这里插入图片描述

Java注解应用

结合业务需求,自定义一个Java注解,该注解的功能主要进行方法标注。方法上面标注了该注解的都要进行统一的数据权限处理。如下面的Java代码:

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

/**
 * 数据权限配置注解类
 * @author trainer
 * @date 2023/8/20
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataRoleConfig {
    Class<?> value() default Object.class;
}

Spring AOP应用

创建切面类

在项目中定义和业务相关的切面类,负责处理相关的业务需求。如:获取业务数据并统一给参数赋值并传递到SQL中。部分代码如下:

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
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.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.HttpSession;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

/**
 * 数据权限配置切面类
 * @author trainer
 * @date 2023/8/20
 */
@Slf4j
@Aspect
@Component
public class DataConfigAspect {

    @Autowired
    private DictionaryService dictionaryService;

    /**
     * 将切点切到指定注解上
     */
    @Pointcut("@annotation(com.trainer.aop.annotation.DataRoleConfig)")
    public void dataConfigPointCut() { }

    /**
     * 前置通知
     * @param joinPoint
     */
    @Before("dataConfigPointCut()")
    public void dataConfigBefore(JoinPoint joinPoint) throws Exception {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpSession session = attr.getRequest().getSession(true);
        // 获取内容
        String mpIds = ObjectUtil.defaultIfNull(session.getAttribute("mpIds")
                , Constant.DATA_CONFIG_DEFAULT_MONITOR_POINTS).toString();

        // 注解内容,暂时不处理
        DataRoleConfig dataRoleConfig = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(DataRoleConfig.class);
        Class<?> bean = dataRoleConfig.value();
        boolean dataConfigSwitch = dataConfigSwitch();

        // 处理方法或属性
        // dealMethod(joinPoint);
        Object[] args = joinPoint.getArgs();
        for(Object o : args){
            if (o instanceof List) {
                if(dataConfigSwitch) {
                    ((List) o).addAll(Arrays.asList(mpIds.split(",")));
                }
                continue;
            }

            if (ObjectUtil.isNull(o) || o instanceof JSONObject || o instanceof Long
                    || o instanceof Integer || o instanceof String) {
                continue;
            }

            if (dataConfigSwitch) {
                // 对象属性赋值
                ((PageBean)o).setDataConfigMpIds(Arrays.asList(mpIds.split(",")));
            } else {
                ((PageBean)o).setDataConfigMpIds(null);
            }
        }
    }

    /**
     * 前置通知
     * @param joinPoint
     */
    // @Around("dataConfigPointCut()")
    public void dataConfigAround(ProceedingJoinPoint joinPoint) {
        // 获取方法名称
        String methodName = joinPoint.getSignature().getName();
        // 获取入参
        Object[] param = joinPoint.getArgs();

        StringBuilder sb = new StringBuilder();
        for(Object o : param){
            sb.append(o + "; ");
        }
        log.debug("进入【{}】方法,参数为: {}", methodName, sb.toString());

        // 继续执行方法
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    /**
     * 后置处理逻辑
     * @param joinPoint
     */
    @After("dataConfigPointCut()")
    public void dataConfigAfter (JoinPoint joinPoint) { }

    /**
     * 数据权限配置开关,数据从字典中获取
     * @return
     */
    private boolean dataConfigSwitch() throws Exception {
        // 获取开关状态
        List<Dictionary> dictionaryList = dictionaryService.queryDictionaryByCode(Constant.DATA_CONFIG_SWITCH_CATEGORY
                , Constant.DATA_CONFIG_SWITCH_ITEM_CATEGORY);

        // 没有配置,默认关闭
        if (CollectionUtil.isEmpty(dictionaryEntityList)) {
            throw new Exception("数据权限配置开关没有配置,请在字典中进行配置!");
        }

        Dictionary dictionaryEntity = dictionaryList.get(0);
        String itemValue = dictionaryEntity.getValue1();
        if (!"true".equalsIgnoreCase(itemValue) && !"false".equalsIgnoreCase(itemValue)) {
            throw new Exception("数据权限配置开关,字典配置有误,请检查配置!");
        }

        return Boolean.valueOf(itemValue);
    }

    /**
     * 处理方法或属性(暂时未用)
     * @param joinPoint
     */
    private void dealMethod (JoinPoint joinPoint) {
        // 获取方法名称
        String methodName = joinPoint.getSignature().getName();
        log.debug("开始执行方法:{} ", methodName);
        // 加载类
        String clazzPathName = joinPoint.getSignature().getDeclaringTypeName();
        // Class clazz = joinPoint.getSignature().getDeclaringType();
        try {
            Class clazz = Class.forName(clazzPathName);
            // clazz.getSuperclass().getDeclaredField("mpIdList");
            Field field = clazz.getField("mpIdList");
            Object obj = clazz.newInstance();
            field.set(obj, Arrays.asList(DataConfigTarget.mpIds.split(",")));
            System.out.println(field.get(obj));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

自定义注解使用

在项目sevice层中具体查询逻辑的方法上使用,如下面的部分代码所示:

   /**
     * 列表查询
     * @param monitorPoint
     * @return
     */
    @DataRoleConfig
    public List<MonitorPoint> queryList(MonitorPoint monitorPoint){
        return monitorPointMapper.queryPointList(monitorPoint);
    }
    

部分Mybatis SQL配置代码如下所示:

	
	<!-- 统一调用的SQL拼接串 -->
    <sql id="dataRoleConfigSql">
        <!-- 数据权限过滤 -->
        <if test="dataConfigMpIds != null and dataConfigMpIds.size() > 0">
            AND mp_id in
            <foreach collection="dataConfigMpIds" open="(" close=")" item="dcMpid" separator=",">
                #{dcMpid}
            </foreach>
        </if>
    </sql>
    

通过该种方式,可以在项目中针对需要修改的19个功能模块进行注解标注,这样可以简化代码结构、避免重复编码、松散耦合

小结

通过学习上面的内容,进修者相信读者已经学会了Java注解和AOP在项目开发过程中的联合使用,也知道了它们各自的好处。

这里再阐述一下,Spring Boot项目只是在Spring MVC项目上对原有开发框架进行了一系列的整合;做到开发简单化,同时解决了包引入冲突等原生开发过程中的一些问题,其特点也不仅于此,希望同学们进行深入学习和挖掘!

温情提示:学习不是一蹴而就的,希望大家不忘初心,继续前行!

感谢您读完了进修者的内容分享,欢迎留言区一起聊聊天,聊聊关于您对“Spring MVC项目开发中,注解和AOP联合的使用”有什么更好的想法,都可以随时叨扰!

我是进修者,期待与您肩并肩,一起进化成长!
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进修者之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值