AOP和代理模式

代理模式

分类

静态代理

  • 优点:代码结构简单,较容易实现
  • 缺点:无法适配所有代理场景,如果有新的需求,需要修改代理类,不符合软件工程的开闭原则

动态代理

  • 优点:能够动态适配特定的代理场景,扩展性较好,符合软件工程的开闭原则
  • 缺点:动态代理需要利用到反射机制和动态生成字节码,导致其性能会比静态代理稍差一些,但是相比于优点,这些劣势几乎可以忽略不计

代理模式的定义:给目标对象提供一个代理对象,代理对象包含该目标对象,并控制对该目标对象的访问。

代理模式的目的:

  • 通过代理对象的隔离,可以在对目标对象访问前后增加额外的业务逻辑,实现功能增强。
  • 通过代理对象访问目标对象,可以防止系统大量地直接对目标对象进行不正确地访问,出现不可预测的后果
  • 动态代理产生代理对象的时机是运行时动态生成,它没有 Java 源文件,直接生成字节码文件实例化代理对象;而静态代理的代理对象,在程序编译时已经写好 Java 文件了,直接 new 一个代理对象即可。
  • 动态代理比静态代理更加稳健,对程序的可维护性和可扩展性更加友好

什么是AOP

简介


AOP:全称是Aspect Oriented Programming即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。 

 

AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP 是 OOP(面向对象编程) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP 的作用及其优势

作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强

优势:减少重复代码,提高开发效率,并且便于维护

AOP 的底层实现

实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

AOP 的动态代理技术

常用的动态代理技术

JDK 代理 : 基于接口的动态代理技术 (Proxy.newProxyInstance

cglib 代理:基于父类的动态代理技术(Enhancer.create)

8.1 aop的相关术语

  • Target(目标对象):代理的目标对象(原对象)

  • Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类

  • Joinpoint(连接点):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等。在SpringAOP中,理解为方法的执行,因为spring只支持方法类型的连接点, 具体的某一个方法

  • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint(方法) 进行拦截的定义

  • Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知

  • Aspect(切面):是切入点和通知(引介)的结合

  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

 

//常用的表达式 * com.bw.controller.*.*(..) //表示controller包下所有的类,所有的方法,所有的方法入参

编程式事务, 声明式事务

编程式事务和声明式事务都是用于管理数据库事务的方式,它们在事务管理的实现上有一些区别。

  1. 编程式事务: 编程式事务是通过编写代码显式地开始、提交或回滚事务。在编程式事务中,开发人员需要手动在适当的位置添加事务的开始、提交和回滚代码,以确保事务的正确控制。通常会使用事务管理器提供的API来实现这些操作,如在Java中可以使用JDBC的Connection对象来控制事务。

编程式事务的优点是灵活性高,可以根据具体需求对事务进行细粒度的控制。但缺点是代码中会包含大量的事务控制代码,导致代码冗余和可读性下降。

  1. 声明式事务: 声明式事务通过在方法或类级别上使用特定的注解或配置,由框架自动处理事务的开始、提交和回滚。开发人员只需在方法上添加注解或在配置文件中进行相应的配置,而不需要编写事务控制的具体代码。

声明式事务通过AOP(面向切面编程)来实现,框架会在运行时动态地为被注解或配置的方法添加事务管理的逻辑。在Spring框架中,可以使用@Transactional注解来声明事务的属性,如隔离级别、传播行为等。

声明式事务的优点是将事务控制与业务逻辑分离,代码更加简洁、清晰,并且可维护性较好。但缺点是灵活性相对较低,无法对事务进行细粒度的控制。

总结来说,编程式事务适合于需要对事务进行动态控制的场景,而声明式事务适合于对事务控制要求相对简单的场景,可以减少开发人员的工作量。

AOP原理代码


/**
 *配置类
 */
//@Component // 把这个对象放入容器
//@Aspect // 织面
public class MyAop {


    /**
     * 前置通知     原方法运行之前执行
     *
     * execution 切入点表达式  用来匹配方法的,如果匹配到方法,这个方法就会被增强
     * @param joinPoint
     */
    @Before("execution( * com.bw.service.*.*(..))")
    public void  before(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println("方法:" + name + "入参是:" + JSON.toJSONString(args));
    }

    @Before("execution(* com.bw.service.*.*(..))")
    public void befors(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println("方法:" + name + "入参是:" + JSON.toJSONString(args));


    }


    /**
     * 后置通知  在原方法运行之后 执行
     * @param joinPoint
     * @param result
     */
    @AfterReturning(returning = "result", pointcut = "execution( * com.bw.service.*.*(..))")
    public void  afterRetuning(JoinPoint joinPoint,Object result){

        String name = joinPoint.getSignature().getName();
        System.out.println("方法:" + name + "返参是:" + JSON.toJSONString(result));
    }


    //异常抛出增强
    @AfterThrowing("execution( * com.bw.service.*.*(..))")
    public void afterThrow() {
        System.out.println("----@AfterThrowing 异常时执行------");
    }
    //final增强,不管是抛出异常或者正常退出都会执行
    @After("execution( * com.bw.service.*.*(..))")
    public void afterFinal() {
        System.out.println("----@After 最后一定会执行------");
    }

}

扩展

aop的拦截存入数据库源码


@Slf4j
@Aspect
@Component
public class MyLog {

    @Autowired
    HttpServletRequest request;
    @Autowired
    SysUserMapper sysUserMapper;
    @Autowired
    SysLogService sysLogService;
//6.拦截内容包含:访问用户,ip,url,请求参数,访问时间(10分)
    @Around(value = "@annotation(Log)")
    public Object aop(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String ip = request.getRemoteAddr();
        String url = request.getRequestURI();
        String args = JSONUtil.toJsonStr(proceedingJoinPoint.getArgs());
        String username="未知用户";
        String userid = JWTUtils.getUsername();
        if(userid!=null){
            SysUser user = sysUserMapper.selectById(userid);
            username=user.getUsername();
        }
        SysLog sysLog = new SysLog(null, ip, username,url, args,  new Date());

        boolean save = sysLogService.save(sysLog);
        if(save){
            log.info(JSONUtil.toJsonStr(sysLog));
        }
        return proceedingJoinPoint.proceed();
    }

}

Aop中打印出方法请求的路径

切点表达式具体代码

Aop中的通知与注解

1.通知

在面向切面编程(AOP)中,通知(Advice)是应用于特定切点(Join Point)的行为。它们定义了在切点处执行的代码,并且可以根据需要将其织入到应用程序中的不同位置。

以下是常见的 AOP 中的各种通知类型:

  1. 前置通知(Before Advice):在切点方法执行之前执行的通知。它可以用于执行预处理操作或验证输入参数等任务。

  2. 后置通知(After Advice):在切点方法执行之后执行的通知。它无论切点方法是否成功完成,都会被执行。它可以用于执行清理操作或记录日志等任务。

  3. 返回通知(After Returning Advice):在切点方法成功完成并返回结果后执行的通知。它可以访问切点方法的返回值,并对其进行处理。

  4. 异常通知(After Throwing Advice):在切点方法抛出异常后执行的通知。它可以捕获并处理切点方法抛出的异常,并采取相应的措施。

  5. 环绕通知(Around Advice):环绕通知围绕着切点方法的执行。它可以在切点方法之前和之后执行自定义的逻辑。环绕通知具有最大的灵活性,可以完全控制切点方法的执行过程。

这些通知类型可以通过使用 AOP 框架(如Spring AOP)来实现。通过在切面(Aspect)中定义这些通知,可以将它们与切点关联起来,并在运行时自动地将通知织入到目标方法中。

需要注意的是,不同的 AOP 框架可能会提供更多的通知类型或具有不同的命名约定,但上述列举的通知类型是 AOP 中最常见和基本的通知类型。

2.注解

在Spring框架中,通常使用注解来声明AOP中的各种通知。下面是各种通知对应的注解:

  1. 前置通知(Before Advice):

    • 注解:@Before
    @Before("execution(* com.example.service.*.*(..))") public void beforeAdvice() { // 执行前置通知的逻辑 }
  2. 后置通知(After Advice):

    • 注解:@After
    @After("execution(* com.example.service.*.*(..))") public void afterAdvice() { // 执行后置通知的逻辑 }
  3. 返回通知(After Returning Advice):

    • 注解:@AfterReturning
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result") public void afterReturningAdvice(Object result) { // 执行返回通知的逻辑,可以访问方法的返回值result }
  4. 异常通知(After Throwing Advice):

    • 注解:@AfterThrowing
    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex") public void afterThrowingAdvice(Exception ex) { // 执行异常通知的逻辑,可以访问方法抛出的异常ex }
  5. 环绕通知(Around Advice):

    • 注解:@Around
    @Around("execution(* com.example.service.*.*(..))") public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { // 执行环绕通知的逻辑,可以在目标方法执行前后加入自定义逻辑 Object result = joinPoint.proceed(); // 手动调用目标方法 return result; }

这些注解可以与切点表达式结合使用,通过在切面类的方法上添加对应的注解,来声明各种通知类型。

需要注意的是,以上示例中的切点表达式是用于指定通知作用的连接点(Join Point),具体的切点表达式根据实际情况进行定义。

自定义注解流程与代码

1.创建自定义注解

2.给注解标记元注解

参考@Aroud()

3.创建日志表

4..反向生成实体类+mapper+service

5.. 创建切面类和方法

6.方法上标记自定义注解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值