Spring——AOP代理机制详解

Spring AOP 是 Spring 框架的核心模块之一,它使用纯 Java 实现,因此不需要专门的编译过程和类加载器,可以在程序运行期通过代理方式向目标类织入增强代码。

目录

关于AOP相对专业的概念

用于理解AOP的小例子

用AOP实现操作日志 


关于AOP相对专业的概念

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

 概念一般都不太好理解,配上例子,就会好理解一些

用于理解AOP的小例子

要想理解什么是面向切面编程,我觉得首先要了解什么是切面,什么才能称作切面。AOP中有很多我不太理解的术语,我去查了一下,然后总结了我放到下面,希望可以对各位有帮助,如果有错误的话,希望各位uu指正

Spring AOP(通知、连接点、切点、切面) - sandea - 博客园

通知(Advice) 切面的工作被称为通知,通知定义了切面是什么以及何时使用,除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。

前置通知(Before)在目标方法被调用之前调用通知功能
后置通知(After)在目标方法完成之后调用通知,此时不会关心方法的输出是什么
返回通知(After-returning)在目标方法成功执行之后调用通知
异常通知(After-throwing)在目标方法抛出异常后调用通知
环绕通知(Around)通知包裹了被通知的方法,在被通知的方法调用之前和之后执行自定义的行为

后置通知和返回通知的区别是,后置通知是不管方法是否有异常,都会执行该通知;而返回通知是方法正常结束时才会执行。


连接点(JoinPoint)连接点是在应用执行过程中能够插入切面的一个点,就是spring允许你是通知(Advice)的地方,感觉是和方法有关的前后都是连接点。
切点(Pointcut)一个切面并不需要通知应用的所有连接点,切点有助于缩小切面所通知的连接点范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。因此,切点其实就是定义了需要执行在哪些连接点上执行通知。比如有一个类里,有20个方法,那就有20个连接点了,但并不是所有的连接点上都需要织入通知,只让其中几个织入通知,这时候切点就有用了,让切点来筛选连接点,选中那几个你想要的方法。
切面(Aspect)切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和在何处完成其功能。通知说明了干什么和什么时候干,切点说明了在哪干(指定到底是哪个方法)
引入(introduction) 引入允许我们向现有的类添加新方法或属性。
织入(weaving) 把切面应用到目标对象来创建新的代理对象的过程。

我在网上看了一个大佬的博客,解释的非常详细,还举了小例子,可以参考

细说Spring——AOP详解(AOP概览)_Jivan2233的博客-CSDN博客_spring aop

Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象):织入 Advice 的目标对象.。
Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
然后举一个容易理解的例子:
看完了上面的理论部分知识, 我相信还是会有不少朋友感觉到 AOP 的概念还是很模糊, 对 AOP 中的各种概念理解的还不是很透彻. 其实这很正常, 因为 AOP 中的概念是在是太多了, 我当时也是花了老大劲才梳理清楚的.
下面我以一个简单的例子来比喻一下 AOP 中 Aspect, Joint point, Pointcut 与 Advice之间的关系.
让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.

来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系.
首先我们知道, 在 Spring AOP 中 Joint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point 就相当于 爪哇的小县城里的百姓,pointcut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问.
为什么可以这样类比呢?

Joint point : 爪哇的小县城里的百姓: 因为根据定义, Joint point 是所有可能被织入 Advice 的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是 Joint point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.

Pointcut :男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 Advice, 但是我们并不希望在所有方法上都织入 Advice, 而 Pointcut 的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.

Advice :抓过来审问, Advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 Joint point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.

Aspect:Aspect 是 point cut 与 Advice 的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个 Aspect.

用AOP实现操作日志 

package com.gyj.signbackend.anno;

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

@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
public @interface OperLog {
    String operModul() default ""; // 操作模块
    int operType();  // 操作类型CRUD
    String operDesc() default "";  // 操作说明
}
package com.gyj.signbackend.aop;

import com.alibaba.fastjson.JSON;
import com.gyj.signbackend.anno.OperLog;
import com.gyj.signbackend.entity.SysOperLog;
import com.gyj.signbackend.service.SysOperLogService;
import com.gyj.signbackend.util.UserJwtUtil;
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 javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;


@Aspect
@Component
@Slf4j
public class OperLogAop {


    @Autowired
    private SysOperLogService sysOperLogService;

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Autowired
    private UserJwtUtil userJwtUtil;


    //切入点
    @Pointcut("@annotation(com.gyj.signbackend.anno.OperLog)")
    public void operLogPointCut() {
    }

    @AfterReturning(value = "operLogPointCut()", returning = "keys")
    public void saveOperLog(JoinPoint joinPoint, Object keys) {
        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入点所在的方法
        Method method = signature.getMethod();
        SysOperLog operlog = new SysOperLog();

        String token = httpServletRequest.getHeader("token");

        log.info("================"+token);
        //获取用户信息
        if (token!=null) {
            if (userJwtUtil.getUser(token) != null) {
                operlog.setUserId(userJwtUtil.getUser(token).getId());
            }
        }

        //获取注解信息
        OperLog logOper = method.getAnnotation(OperLog.class);
        String operModul=logOper.operModul();
        Integer operType=logOper.operType();
        String operDesc=logOper.operDesc();
        operlog.setDescription(operDesc);
        operlog.setModule(operModul);
        operlog.setType(operType);

        // 获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        // 获取请求的方法名
        String methodName = method.getName();
        methodName = className + "." + methodName;
                operlog.setMethod(methodName); // 请求方法

        //入参
        Object[] objects = joinPoint.getArgs();
        String[] paramNames =  signature.getParameterNames();
        Map<String, String> map = new HashMap<String, String>();
        for (int i=0;i<objects.length;i++){
            map.put(paramNames[i],objects[i].toString());
        }
        String params = JSON.toJSONString(map);
        operlog.setReqParam(params);

        //出参
        operlog.setResParam(JSON.toJSONString(keys));

        //时间
        operlog.setCreateTime(LocalDateTime.now());

        // 请求URL
        operlog.setUrl(httpServletRequest.getRequestURI());

        /**
         * 进行业务操作
         */
        sysOperLogService.save(operlog);
    }
}
package com.gyj.signbackend.util;

import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.gyj.signbackend.entity.SysUser;
import com.gyj.signbackend.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Calendar;
import java.util.Date;

@Slf4j
@Component
public class UserJwtUtil {
    private static final int EXPIRE_TIME=30;
    private static final String TOKEN_SECRET="privatekey";

    // 普通常量
    private static final String CLAIM_USER = "claimUser";

    @Resource
    private SysUserService sysUserService;

    /**
     *      签发对象:这个用户
     *      签发时间:现在
     *      有效时间:30分钟
     *      载荷内容:暂时设计为:这个人的名字
     *      加密密钥:这个人加上一串字符串
     * @return
     */
    public String createToken(SysUser sysUser) {
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.MINUTE, EXPIRE_TIME);
        Date expiresDate = nowTime.getTime();

        String userJson = JSON.toJSONString(sysUser);
        return JWT.create()
                .withAudience(sysUser.getId() + "")   //签发对象
                .withIssuedAt(new Date())    //发行时间
                .withExpiresAt(expiresDate)  //有效时间
                // 存储用户信息   =>   json转化 ,序列化
                .withClaim(CLAIM_USER, userJson)
                .sign(Algorithm.HMAC256(sysUser.getId() + TOKEN_SECRET));   //加密
    }


    /**
     * 检验合法性,其中secret参数就应该传入的是用户的id
     * @param token
     *
     */
    public static boolean verifyToken(String token, String secret){
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret+TOKEN_SECRET)).build();
            verifier.verify(token);
            return true;
        } catch (Exception e) {
            //效验失败
            //自定义的一个异常
            return false;

        }
    }
    /**
     * 获取签发对象
     */
    public static String getAudience(String token){
        String audience = null;
        try {
            audience = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException j) {
            //这里是token解析失败
            return null;
        }
        return audience;
    }

    /**
     * 获取载荷中用户信息
     *
     * @param token
     * @return
     */
    public SysUser getUser(String token) {
        SysUser user = null;
        try {
            String userJson = JWT.decode(token).getClaim(CLAIM_USER).asString();
            user = JSON.parseObject(userJson, SysUser.class);
        } catch (JWTDecodeException j) {
            log.error(j.getMessage(), j);
            return null;
        }
        SysUser user1=sysUserService.getById(user.getId());
        if (user1!=null) user1.setToken(token);
        return user1;
    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

赚钱去流浪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值