java 面向切面编程

面向切面编程,指扩展功能不修改源代码,将功能代码从业务逻辑代码中分离出来。

一个Bean是Spring容器中的一个对象实例,这个对象实例通常是通过Java类(或接口)的实例化得到的。

Bean作为对象,具有对象的所有基本特性,包括状态(即对象的属性)和行为(即对象的方法)。但是,与普通对象不同的是,Bean的生命周期由Spring容器控制,包括创建、初始化、使用和销毁等阶段。此外,Bean还支持依赖注入(DI)和面向切面编程(AOP)等Spring框架的高级特性。

在Spring框架中,Bean的声明通常是通过在类上添加特定的注解(如@Component@Service@Repository@Controller等)或在配置类中使用@Bean注解的方法来完成的。这些声明告诉Spring容器哪些类需要被实例化并注册为Bean。然后,Spring容器会在启动时读取这些声明,并根据配置信息来创建Bean的实例,并将它们存储在容器中以便后续使用。

而AOP则是通过在不修改源代码的情况下,为Bean提供额外的行为,如日志记录、事务管理、安全控制等,从而实现了横切关注点(cross-cutting concerns)的模块化。

1.代理对象

AOP的实现通常依赖于动态代理技术。当Spring容器创建Bean时,如果检测到该Bean需要进行AOP处理(例如,通过注解或XML配置指定了切面),Spring会为该Bean生成一个代理对象。这个代理对象在方法调用时会执行额外的操作(即切面的逻辑),然后调用原始Bean的方法。因此,从外部看来,就像是直接对原始Bean进行了操作,但实际上是通过代理对象来完成的。

这里对代理对象进行说明

public class UserService {
    private String username;

    public UserService(String username) {
        this.username = username;
    }

    public void addUser() {
        System.out.println("User " + username + " added.");
    }
}

// 代理类
public class UserServiceProxy {
    private UserService userService;

    public UserServiceProxy(UserService userService) {
        this.userService = userService; // 持有原对象的引用
    }

    public void addUser() {
        System.out.println("Before adding user."); // 增强逻辑
        userService.addUser(); // 调用原对象的方法
    }
}

// 主程序
public class Main {
    public static void main(String[] args) {
        UserService userService = new UserService("Alice");
        UserServiceProxy proxy = new UserServiceProxy(userService);
        
        proxy.addUser(); // 通过代理调用
    }
}
  • AOP 创建一个代理对象,代理对象实现了原对象的接口(对于 JDK 动态代理)或是原对象的子类(对于 CGLIB 代理)。
  • 当外部调用代理对象的方法时,实际上是通过代理来执行目标方法。

所以AOP实际上是操作的代理对象(Proxy),而不是原对象(目标对象Target),代理对象是在原对象的基础上进行了扩展(增强),从另一个角度看,AOP是在对(bean对象)对象进行处理,就是 f(x)。

2.切入点

切入点指定了哪些Bean对象的方法调用应该被拦截。

在AOP(面向切面编程)的上下文中,通俗点理解方法调用可以被视为对象的行为,而AOP的主要功能之一就是检测这些对象的行为(即方法调用),并在特定条件下触发额外的逻辑(即切面的通知)。

具体来说,AOP框架会监视应用程序中的方法调用,以识别出与切面切入点表达式相匹配的连接点。一旦检测到这样的连接点(即特定的方法调用),AOP框架就会根据切面的定义在该连接点之前、之后或周围执行相应的通知。

切入点可以通过表达式定义,这些表达式描述了要拦截的方法或类。以下是定义切入点的常见方式。

1. 使用 AspectJ 表达式

在 Spring AOP 中,切入点通常使用 AspectJ 表达式来定义,常见的表达式包括:

方法名匹配

@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {
    // 切入点定义
}

这个切入点匹配 com.example.service 包下的所有类的所有方法。

特定方法匹配

@Pointcut("execution(* com.example.service.UserService.addUser(..))")
public void addUserMethod() {
    // 切入点定义
}

这个切入点仅匹配 UserService 类中的 addUser 方法。

类匹配

@Pointcut("within(com.example.service.*)")
public void allServiceMethods() {
    // 切入点定义
}

这个切入点匹配 com.example.service 包下的所有类的所有方法。

注解匹配

@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
public void postMappingMethods() {
    // 切入点定义
}

这个切入点匹配所有带有 @PostMapping 注解的方法。

2. 组合切入点

切入点可以通过逻辑运算符组合,以实现更复杂的匹配条件:

并且

@Pointcut("execution(* com.example.service.*.*(..)) && within(com.example.service.UserService)")
public void specificServiceMethods() {
    // 切入点定义
}

@Pointcut("execution(* com.example.service.UserService.*(..)) || execution(* com.example.service.AdminService.*(..))")
public void userAndAdminServiceMethods() {
    // 切入点定义
}

@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.UserService.addUser(..))")
public void allServiceMethodsExceptAddUser() {
    // 切入点定义
}

切入点方法的主要目的是提供一个可重用的切入点定义,不需要在方法体中写任何逻辑。这样可以使得切入点的管理更加集中和清晰。

3.连接点

程序执行中的一个特定点,在这个点上可以插入额外的行为(即通知)。连接点可以是方法调用、异常处理、对象创建等,具体取决于 AOP 的实现和配置。在 Spring AOP 中,连接点主要是方法执行。在 AspectJ 中,连接点的范围更广,包括方法执行、构造函数调用、字段访问等。切入点定义了哪些连接点应该被通知。也就是说,连接点是实际的执行点,而切入点是匹配连接点的条件。常指的是目标对象中执行的方法。

package com.example.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    // 定义切入点,匹配 UserService 中的所有方法
    @Around("execution(* com.example.service.UserService.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        // 前置逻辑
        System.out.println("Before executing method: " + joinPoint.getSignature().getName());

        // 调用目标方法
        Object result = joinPoint.proceed();

        // 后置逻辑
        System.out.println("After executing method: " + joinPoint.getSignature().getName());

        // 可以对结果进行修改
        return "Extended: " + result;
    }
}
1. JoinPoint
  • 定义:表示程序执行中的一个特定点,通常用于获取有关连接点的信息,如方法名、参数、目标对象等。
  • 用途:一般用于 @Before@After 通知中,只能用于访问连接点的信息,但不能用于控制目标方法的执行。
2. ProceedingJoinPoint
  • 定义:是 JoinPoint 的子接口,主要用于 @Around 通知。
  • 用途:不仅可以获取连接点的信息,还允许你控制目标方法的执行。通过调用 proceed() 方法,可以在切面中执行目标方法,并可选择修改其返回值。

4.通知/增强

指的是在特定连接点执行的额外行为或逻辑。通知可以在目标方法执行的不同阶段插入特定的操作,以实现对目标方法的增强和扩展。

直接在通知中使用切入点表达式是可以的,适合简单场景。也就是说,可以不定义切入点。

@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    // 环绕通知逻辑
}




@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {
    // 切入点定义
}

@Around("serviceMethods()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    // 环绕通知逻辑
}

上述两个都是可以的。 

前置通知(Before Advice)

  • 在目标方法执行之前执行。
  • 用于进行准备工作或记录日志。
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
    System.out.println("Before executing method: " + joinPoint.getSignature().getName());
}

后置通知(After Advice)

  • 在目标方法执行之后执行,不管目标方法是否正常完成。
  • 用于清理资源或记录结束日志。
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
    System.out.println("After executing method: " + joinPoint.getSignature().getName());
}

返回通知(After Returning Advice)

  • 在目标方法正常返回后执行。
  • 可用于处理返回值。
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
    System.out.println("Method returned: " + result);
}

异常通知(After Throwing Advice)

  • 在目标方法抛出异常后执行。
  • 可用于处理异常和记录错误信息。
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
    System.out.println("Method threw: " + error);
}

环绕通知(Around Advice)

  • 包裹了目标方法的执行,可以在执行前后插入逻辑。
  • 允许控制目标方法的执行。
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Before method: " + joinPoint.getSignature().getName());
    Object result = joinPoint.proceed(); // 调用目标方法
    System.out.println("After method: " + joinPoint.getSignature().getName());
    return result;
}

5.引介

是 AOP(面向切面编程)中的一种特殊类型的通知,它允许向现有的类中添加新的方法或属性,而无需修改原有类的代码。引介通常用于增强类的功能,使其实现额外的接口。

  • 增强现有类:引介可以给目标类添加新的功能,比如新的方法或属性。

  • 实现接口:通过引介,可以让目标类实现一个或多个新的接口,从而增加其行为。

  • 不修改原有代码:通过引介,可以在不修改原始类的情况下添加新功能,这有助于保持代码的可维护性。

//定义接口
package com.example;

public interface Monitorable {
    void monitor();
}

//实现用户服务
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void addUser(String username) {
        System.out.println("User " + username + " added.");
    }
}

//创建引介
package com.example.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareMixin;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MonitoringAspect {

    @DeclareMixin("com.example.service.UserService")
    public Monitorable createMonitorable(UserService userService) {
        return new Monitorable() {
            @Override
            public void monitor() {
                System.out.println("Monitoring UserService actions.");
            }
        };
    }
}


//使用引介
package com.example;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.service.UserService;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean(UserService.class);
        
        // 使用引介提供的新方法
        if (userService instanceof Monitorable) {
            ((Monitorable) userService).monitor();
        }
        
        userService.addUser("Alice");
    }
}


//结果
Monitoring UserService actions.
User Alice added.

一个HTTP请求在Web应用中的经过顺序通常是:首先通过过滤器,然后经过拦截器的预处理(preHandle),接着可能触发AOP切面进行额外的处理,之后到达控制器执行具体的业务逻辑,控制器处理完毕后可能再次触发AOP切面进行后处理,最后经过拦截器的后处理(postHandleafterCompletion)和过滤器的后处理,最终将响应返回给客户端。这个顺序可能因具体的配置和使用的技术而有所不同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值