Spring——代理模式与AOP

代理模式

代理模式是一种设计模式,它允许一个对象控制对另一个对象的访问。在Spring中,代理模式主要用于实现AOP。代理对象可以在方法调用前后添加额外的逻辑,而不需要修改原始对象的代码。

核心思想:代理模式是给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

1.1 静态代理

静态代理是通过手动编写代理类实现的,代理类实现了与目标对象相同的接口,并在调用目标方法时添加额外的逻辑。

接下来对比静态代理和动态代理来感受一下代理

如想在目标类的方法实现前后加上日志等操作

静态代理示例
// 接口
public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

// 目标类
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        return i + j;
    }

    @Override
    public int sub(int i, int j) {
        return i - j;
    }

    @Override
    public int mul(int i, int j) {
        return i * j;
    }

    @Override
    public int div(int i, int j) {
        return i / j;
    }
}

// 静态代理类
public class CalculatorStaticProxy implements Calculator {
    private Calculator target;

    public CalculatorStaticProxy(Calculator target) {
        this.target = target;
    }

    @Override
    public int add(int i, int j) {
        System.out.println("参数是:" + i + "," + j);
        int result = target.add(i, j);
        System.out.println("方法内部 result = " + result);
        return result;
    }

    // 其他方法类似
    // ...
}

虽然静态代理实现了功能的解耦,但它缺乏灵活性。如果在多个地方需要附加日志功能,就需要声明多个静态代理类,这会导致大量重复代码,且日志功能分散,不便于统一管理。

  • 优点:
    • 代码结构清晰,易于理解。
    • 在编译期就能确定代理类和目标类之间的关系。
    • 性能略好于动态代理,因为不需要反射机制。
  • 缺点:
    • 每个目标类都需要一个代理类,代码冗余。
    • 扩展性差,如果需要添加新的功能,需要修改代理类代码。
    • 无法在运行时动态地添加代理逻辑。

1.2 动态代理

动态代理通过反射机制在运行时生成代理类,避免了手动编写代理类的繁琐。在Spring中,最常用的动态代理技术有JDK动态代理和CGLIB代理。

JDK动态代理

JDK动态代理需要目标类实现接口。代理对象和目标对象有相同的接口。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 代理工厂类
public class ProxyFactory {
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    System.out.println("[动态代理][日志] 方法:" + method.getName() + ",参数:" + Arrays.toString(args));
                    result = method.invoke(target, args);
                    System.out.println("[动态代理][日志] 方法:" + method.getName() + ",结果:" + result);
                } catch (Exception e) {
                    System.out.println("[动态代理][日志] 方法:" + method.getName() + ",异常:" + e.getMessage());
                    throw e;
                } finally {
                    System.out.println("[动态代理][日志] 方法:" + method.getName() + ",执行完毕");
                }
                return result;
            }
        };

        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}
测试代码
 
public class DynamicProxyTest {
    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new CalculatorImpl());
        Calculator proxy = (Calculator) factory.getProxy();
        proxy.add(1, 2);
        proxy.div(4, 2);
    }
}

 

  • 优点:
    • 减少代码冗余,不需要为每个目标类编写代理类。
    • 扩展性强,可以通过动态代理添加新的功能,无需修改目标类代码。
    • 可以动态地添加代理逻辑,方便灵活。
  • 缺点:
    • 性能略低于静态代理,因为使用了反射机制。
    • 代码复杂,理解难度较高

共同缺点:

  • 侵入性: 静态代理和动态代理都需要修改目标类代码,或者引入额外的代理类,这会增加代码复杂度,降低代码可维护性。

为何要使用AOP

为了解决静态代理和动态代理的缺点,引入了面向切面编程 (AOP)。AOP 是一种编程思想,它允许将横切关注点 (例如日志记录、事务管理、安全控制等) 从业务逻辑中分离出来,并以一种非侵入的方式进行管理。

AOP 的优势:

  • 非侵入性: AOP 不会修改目标类代码,而是通过配置的方式实现代理逻辑。
  • 模块化: AOP 将横切关注点模块化,提高代码可维护性和可扩展性。
  • 集中管理: AOP 可以集中管理横切关注点,例如日志记录、事务管理等,方便维护和修改。

AOP

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它通过“切面”将横切关注点(如日志记录、事务管理)与业务逻辑分离。在Spring中,AOP是通过代理模式实现的。

2.1 AOP的核心概念

  • 切面(Aspect):一个模块,包含横切关注点的逻辑。
  • 连接点(Join Point):程序执行的某个点,如方法调用或异常抛出。
  • 切入点(Pointcut):定义在哪些连接点上应用切面。
  • 通知(Advice):切面在连接点上执行的动作。
  • 目标对象(Target Object):被代理的对象。
  • 织入(Weaving):将切面应用到目标对象的过程。

连接点与切入点

连接点(Join Point)

连接点是程序执行的某个点,例如方法调用或异常抛出。在Spring AOP中,连接点总是表示方法的执行。

切入点(Pointcut)

切入点是一个表达式,定义在哪些连接点上应用通知。切入点表达式可以通过注解或XML配置定义。

通知分类

前置通知:使用@Before注解标识,在被代理的目标方法前执行

返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行---目标返回值后执行

异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行---相当于在catch中

后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行---- 相当于在finally中

环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的目标方法,包 括上面四种通知对应的所有位置

关注你要的目标对象(方法),然后在其方法上加入通知

切入点表达式

AOP的核心注解

@Aspect

@Aspect注解用于定义一个切面类。切面类包含了切入点和通知的定义。

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

@Aspect
@Component
public class LoggingAspect {
    // 切面类内容
}

@Pointcut

@Pointcut注解用于定义切入点表达式。切入点表达式指定了哪些连接点需要应用通知。

import org.aspectj.lang.annotation.Pointcut;

@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {
    // 切入点方法签名
}
 @Before

@Before注解表示前置通知,它在目标方法执行之前执行。

import org.aspectj.lang.annotation.Before;

@Before("serviceMethods()")
public void logBefore() {
    System.out.println("Before method execution...");
}
@After

@After注解表示后置通知,它在目标方法执行之后执行,无论方法是否抛出异常。

import org.aspectj.lang.annotation.After;

@After("serviceMethods()")
public void logAfter() {
    System.out.println("After method execution...");
}
@AfterReturning

@AfterReturning注解表示返回通知,它在目标方法成功执行并返回结果后执行。

import org.aspectj.lang.annotation.AfterReturning;

@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void logAfterReturning(Object result) {
    System.out.println("Method returned with result: " + result);
}
@AfterThrowing

@AfterThrowing注解表示异常通知,它在目标方法抛出异常后执行。

import org.aspectj.lang.annotation.AfterThrowing;

@AfterThrowing(pointcut = "serviceMethods()", throwing = "error")
public void logAfterThrowing(Throwable error) {
    System.out.println("Method threw an error: " + error);
}
@Around

@Around注解表示环绕通知,它围绕目标方法执行,可以在方法执行前后添加逻辑,并且可以决定是否执行目标方法。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;

@Around("serviceMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Before method execution...");
    Object result = joinPoint.proceed(); // 执行目标方法
    System.out.println("After method execution...");
    return result;
}

切入点重用

自定义一个空方法, 加上注解里面写上切入点表达式,就可以在后续切入点表达式上直接写自定义的方法名

1.3 通知注解

通知是在连接点上执行的动作。Spring AOP提供了以下几种通知注解:

2.2 使用Spring AOP

Spring AOP主要有两种实现方式:基于注解和基于XML配置。

2.2.1 基于注解的AOP

Spring提供了几个常用的注解来定义切面、切入点和通知。

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

// 定义切面
@Aspect
@Component
public class LoggingAspect {

    // 定义切入点和通知
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore() {
        System.out.println("Before method execution...");
    }
}

// 目标类
@Component
public class MyService {
    public void perform() {
        System.out.println("Performing service...");
    }
}

// 配置类
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    // 配置类内容
}
2.2.2 基于XML的AOP

除了注解,Spring还支持通过XML配置AOP。

<!-- 定义切面 -->
<aop:config>
    <aop:aspect ref="loggingAspect">
        <!-- 定义切入点和通知 -->
        <aop:pointcut id="serviceMethods" expression="execution(* com.example.service.*.*(..))"/>
        <aop:before pointcut-ref="serviceMethods" method="logBefore"/>
    </aop:aspect>
</aop:config>

<!-- 切面类 -->
<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>

 

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值