Spring-基础(上)

理解Spring框架

Spring框架作为Java企业级应用开发的核心组件,其核心特性涵盖了多个关键领域,为开发者提供了强大的工具和抽象层,以简化复杂的企业级应用开发。以下Spring框架几个核心特性:

1.控制反转(IoC)容器

Spring的核心之一是其控制反转(Inversion of Control, IoC)容器。IoC容器通过反转对象的创建和管理职责,使得开发者能够专注于业务逻辑的实现,而非对象的生命周期管理。具体来说,开发者只需定义bean及其依赖关系,Spring容器会自动负责这些对象的创建、组装和管理。

  • 依赖注入(Dependency Injection, DI):IoC容器通过依赖注入机制,将对象之间的依赖关系从代码中解耦,使得对象的创建和依赖关系的管理更加灵活和可配置。Spring支持多种依赖注入方式,包括构造器注入、Setter注入和字段注入。
  • Bean生命周期管理:Spring容器不仅负责bean的创建,还管理其生命周期,包括初始化和销毁阶段。开发者可以通过实现特定的接口或使用注解来定制bean的生命周期行为。

2.面向切面编程(AOP)

面向切面编程(Aspect-Oriented Programming, AOP)是Spring框架的另一大核心特性。AOP允许开发者将横切关注点(如事务管理、日志记录、安全性等)模块化,从而提高代码的模块化程度和可维护性。

  • 切面(Aspect):切面是对横切关注点的模块化封装,通常包括通知(Advice)和切点(Pointcut)。通知定义了在切点处执行的行为,而切点则定义了在何处应用这些行为。
  • 连接点(Join Point):连接点是程序执行过程中的特定点,如方法调用、异常抛出等。Spring AOP支持在方法调用和异常抛出处应用切面。
  • 代理机制:Spring AOP通过代理机制实现切面的应用。Spring支持两种代理方式:基于JDK动态代理和基于CGLIB的代理。前者适用于接口代理,后者适用于类代理。

3. 事务管理

Spring框架提供了一致且灵活的事务管理机制,使得开发者能够以声明式或编程式的方式管理事务。

  • 声明式事务管理:通过使用@Transactional注解或XML配置,开发者可以声明事务边界和事务属性,Spring会自动管理事务的开始、提交和回滚。
  • 编程式事务管理:对于需要更细粒度控制的事务场景,Spring提供了TransactionTemplatePlatformTransactionManager等API,允许开发者以编程方式管理事务。
  • 事务传播行为:Spring支持多种事务传播行为(如REQUIRED、REQUIRES_NEW、NESTED等),使得开发者能够灵活控制事务的传播和隔离级别。

4. Spring MVC框架

Spring MVC是基于Servlet API构建的Web框架,采用了模型-视图-控制器(Model-View-Controller, MVC)架构模式,为开发者提供了一种结构化的方式来构建Web应用。

  • 模型(Model):模型层负责处理业务逻辑和数据访问,通常由服务层和数据访问层组成。
  • 视图(View):视图层负责展示数据,通常由JSP、Thymeleaf、FreeMarker等模板引擎实现。
  • 控制器(Controller):控制器层负责处理用户请求,调用模型层处理业务逻辑,并将结果传递给视图层进行展示。Spring MVC通过@Controller@RequestMapping等注解,简化了控制器的开发。IoC与AOP

IoC

控制反转(IoC)

控制反转(Inversion of Control, IoC)是Spring框架的核心设计原则之一,它通过反转对象的创建和管理职责,使得开发者能够专注于业务逻辑的实现,而非对象的生命周期管理。依赖注入(Dependency Injection, DI)是IoC的一种具体实现方式。

在传统的开发模式中,对象的创建和管理通常由开发者手动完成,使用new关键字来实例化对象。这种方式会导致代码中各个对象之间的耦合度较高,难以维护和扩展。

1
2
3
4
public class UserService {
private UserRepository userRepository = new UserRepository();
// 业务逻辑
}

而在使用IoC思想的开发中,对象的创建和管理职责被反转给IoC容器,开发者只需定义bean及其依赖关系,Spring容器会自动负责这些对象的创建、组装和管理。

1
2
3
4
5
6
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 业务逻辑
}

IoC的核心概念

1. 控制反转(Inversion of Control)

所谓控制,就是对象的创建、初始化、销毁等生命周期管理。在传统的开发模式中,这些控制权由开发者掌握,而在IoC模式中,这些控制权被反转给IoC容器。

  • 创建对象:原来是new一个对象,现在是由Spring容器创建。
  • 初始化对象:原来是使用构造器或者setter方法给依赖赋值,现在由Spring自动注入。
  • 销毁对象:原来是通过给对象赋null值销毁,现在由Spring容器管理生命周期进行销毁。

所谓反转,其实就是反转的控制权,对象的生命周期已经由Spring容器管理来进行控制,开发者由对象的控制者,变成了IoC的被动控制者。

2. 依赖注入(Dependency Injection, DI)

依赖注入是IoC的一种实现方式,通过依赖注入,对象之间的依赖关系从代码中解耦,使得对象的创建和依赖关系的管理更加灵活和可配置。Spring支持多种依赖注入方式,包括构造器注入、Setter注入和字段注入。

  • 构造器注入:通过构造器参数注入依赖。
  • Setter注入:通过Setter方法注入依赖。
  • 字段注入:通过字段注解注入依赖。

IoC容器的优势

  • 降低耦合度:对象之间的依赖关系通过配置文件或注解进行管理,减少了代码中的硬编码依赖。
  • 提高可测试性:通过依赖注入,可以方便地使用Mock对象进行单元测试。
  • 增强可维护性:对象的创建和管理由容器负责,开发者只需关注业务逻辑的实现。

IoC容器的实现

Spring框架通过IoC容器来管理bean的生命周期和依赖关系。Spring IoC容器的主要实现包括:

1. BeanFactory

BeanFactory是Spring IoC容器的基本实现,提供了最基本的IoC功能。BeanFactory是一个接口,定义了获取bean、检查bean类型、检查bean作用域等方法。

1
2
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
UserService userService = (UserService) beanFactory.getBean("userService");

2. ApplicationContext

ApplicationContext是BeanFactory的子接口,提供了更高级的功能,如国际化支持、事件发布、资源加载等。ApplicationContext是Spring应用上下文的核心接口,通常在企业级应用中使用。

1
2
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean(UserService.class);

示例

以下是一个简单的IoC示例,展示了如何使用Spring IoC容器管理bean及其依赖关系。

  • 定义服务类
1
2
3
4
5
6
7
8
9
@Service
public class UserService {
@Autowired
private UserRepository userRepository;

public void saveUser(User user) {
userRepository.save(user);
}
}
  • 定义数据访问类
1
2
3
4
5
6
@Repository
public class UserRepository {
public void save(User user) {
// 数据保存逻辑
}
}
  • 配置Spring上下文
1
2
3
4
@Configuration
@ComponentScan("com.example")
public class AppConfig {
}
  • 使用IoC容器
1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.saveUser(new User());
}
}

控制反转(IoC)是Spring框架的核心设计原则之一,通过反转对象的创建和管理职责,使得开发者能够专注于业务逻辑的实现。依赖注入(DI)是IoC的一种具体实现方式,通过依赖注入,对象之间的依赖关系从代码中解耦,使得对象的创建和依赖关系的管理更加灵活和可配置。Spring IoC容器通过BeanFactory和ApplicationContext实现bean的生命周期管理,提供了强大的工具和抽象层,以简化复杂的企业级应用开发。深入理解IoC的原理和应用,能够帮助开发者更好地利用Spring框架,提升代码的质量和开发效率。

依赖倒置、依赖注入、控制反转

控制反转(Inversion of Control, IoC)

控制反转是一种设计原则,它将程序的执行流程控制权从开发者手中转移到了框架或容器中。传统上,开发者通过编写代码来控制程序的执行流程,而在使用框架后,框架接管了这一职责。这种“反转”意味着开发者不再直接控制程序的执行顺序,而是通过配置或声明的方式让框架来管理。

依赖注入(Dependency Injection, DI)

依赖注入是一种实现控制反转的具体技术。它通过将依赖对象的创建和管理从类内部转移到外部,从而实现类之间的解耦。依赖注入主要有三种方式:

  1. 构造函数注入:通过类的构造函数传递依赖对象。
  2. Setter方法注入:通过类的Setter方法传递依赖对象。
  3. 字段注入:通过字段注解直接注入依赖对象。

依赖注入的核心思想是:类不应该负责创建和管理自己的依赖,而是由外部容器或框架来负责。

依赖倒置原则(Dependency Inversion Principle, DIP)

依赖倒置原则是面向对象设计中的一个重要原则,它指导我们如何设计模块之间的依赖关系。具体来说,依赖倒置原则包含两个关键点:

  1. 高层模块不应该依赖于低层模块:传统的设计中,高层模块通常直接依赖于低层模块的具体实现。依赖倒置原则要求高层模块和低层模块都应该依赖于抽象接口或抽象类,而不是具体的实现。

  2. 抽象不应该依赖于细节,细节应该依赖于抽象:这意味着接口或抽象类的设计应该独立于具体的实现细节,而具体的实现类应该依赖于这些抽象。

依赖倒置原则通过引入抽象层,使得系统更加灵活、可扩展,并且降低了模块之间的耦合度。

Spring IoC 的设计与实现

在深入探讨如何自行实现 Spring IoC(Inversion of Control,控制反转)容器时,我们需要关注以下几个关键方面,以确保设计的科学性和专业性:

Bean 生命周期管理

Bean 的生命周期管理是 IoC 容器设计的核心。为了实现这一功能,可以采用以下策略:

  • 工厂模式:通过工厂模式来创建和管理 Bean 实例,确保 Bean 的创建过程与业务逻辑解耦。工厂模式可以进一步细分为简单工厂、工厂方法和抽象工厂,根据具体需求选择合适的模式。

  • 单例模式:对于需要全局共享的 Bean,可以采用单例模式来确保系统中只有一个实例存在。单例模式可以通过双重检查锁定(Double-Checked Locking)或静态内部类等方式实现,以保证线程安全。

  • 生命周期回调:除了基本的创建和销毁,还可以引入生命周期回调机制,如 @PostConstruct@PreDestroy 注解,以支持 Bean 在初始化和销毁时的自定义逻辑。

依赖注入(Dependency Injection)

依赖注入是 Spring IoC 的核心特性之一,其实现需要考虑以下几个方面:

  • 注入方式:支持多种注入方式,包括字段注入(Field Injection)、构造器注入(Constructor Injection)和 setter 注入(Setter Injection)。构造器注入通常被认为是最佳实践,因为它可以确保 Bean 在创建时所有依赖都已满足。

  • 反射机制:利用 Java 的反射机制来动态地解析和注入依赖。反射机制允许在运行时获取类的字段、方法和构造器信息,从而实现依赖的自动注入。

  • 配置元数据:依赖注入的实现依赖于配置元数据,这些元数据可以通过 XML 配置文件、注解(如 @Autowired@Inject)或 Java 配置类(如 @Configuration)来提供。

Bean 的作用域管理

Bean 的作用域决定了 Bean 实例的生命周期和可见性。常见的 Bean 作用域包括:

  • 单例(Singleton):在整个应用中只存在一个 Bean 实例。
  • 原型(Prototype):每次请求时都会创建一个新的 Bean 实例。
  • 会话(Session):在 Web 应用中,每个会话对应一个 Bean 实例。
  • 请求(Request):在 Web 应用中,每个请求对应一个 Bean 实例。

为了支持多种作用域,可以使用 Map 或其他数据结构来存储不同作用域的 Bean 实例。例如,可以使用 ConcurrentHashMap 来存储单例 Bean,使用 ThreadLocal 来存储线程作用域的 Bean。

异常处理

在 Bean 管理和依赖注入过程中,可能会遇到各种异常情况,如依赖注入失败、Bean 未找到等。为了确保系统的健壮性,需要定义特定的异常类型,并使用 try-catch 机制进行处理。

  • 自定义异常:定义如 BeanNotFoundExceptionDependencyInjectionException 等自定义异常,以便在发生错误时能够提供更详细的错误信息。

  • 异常传播:在异常处理过程中,考虑异常的传播机制,确保异常能够正确地传递到调用者,以便进行适当的处理。

面向切面编程(AOP)支持

AOP(Aspect-Oriented Programming)是 Spring 框架的另一个重要特性,它允许在不修改原有代码的情况下,通过切面(Aspect)来增强功能。

  • 动态代理:使用 Java 的动态代理机制来实现 AOP。动态代理可以在运行时生成代理类,从而在不修改原有类的情况下,插入额外的逻辑(如日志记录、事务管理等)。

  • 切面编程:定义切面(Aspect)、切点(Pointcut)和通知(Advice),通过这些元素来实现横切关注点的分离。常见的通知类型包括前置通知(Before Advice)、后置通知(After Advice)、环绕通知(Around Advice)等。

配置文件加载

配置文件的加载是 IoC 容器启动的关键步骤之一,它决定了容器如何初始化和管理 Bean。

  • 配置文件格式:支持多种配置文件格式,如 XML、注解(如 @ComponentScan@Bean)和 Java 配置类(如 @Configuration)。每种配置方式都有其优缺点,可以根据具体需求选择合适的配置方式。

  • 配置解析:配置文件的解析是加载过程中的关键步骤。可以使用 DOM 解析器(如 DocumentBuilder)来解析 XML 配置文件,使用反射机制来解析注解配置,使用 Java 反射来解析 Java 配置类。

  • 配置合并:在复杂的应用中,可能需要从多个配置文件中加载 Bean 信息。为了确保配置的一致性和完整性,可以实现配置的合并机制,将多个配置文件中的信息合并为一个统一的配置对象。

通过以上几个方面的深入设计和实现,可以构建一个功能完备、性能优越的 Spring IoC 容器,满足复杂应用场景的需求。

AOP

面向切面编程(AOP)

面向切面编程(Aspect-Oriented Programming, AOP)是Spring框架的另一大核心特性,它允许开发者将横切关注点(如事务管理、日志记录、安全性等)模块化,从而提高代码的模块化程度和可维护性。AOP的核心思想是将核心业务逻辑周边业务逻辑分离,并通过切面将它们编织在一起。

AOP的核心概念

来源小林coding

1. 切面(Aspect)

切面是对横切关注点的模块化封装,通常包括通知(Advice)和切点(Pointcut)。切面定义了在哪些连接点(Join Point)上应用哪些通知。

2. 连接点(Join Point)

连接点是程序执行过程中的一个特定点,例如方法调用、异常抛出等。在Spring AOP中,连接点仅支持方法级别的连接点。

3. 通知(Advice)

通知定义了在切点处执行的行为。Spring AOP支持多种类型的通知,包括:

  • 前置通知(Before Advice):在目标方法执行前执行。
  • 后置通知(After Advice):在目标方法执行后执行,无论方法是否成功完成。
  • 返回通知(After Returning Advice):在目标方法成功执行后执行。
  • 异常通知(After Throwing Advice):在目标方法抛出异常后执行。
  • 环绕通知(Around Advice):在目标方法执行前后执行,可以控制方法的执行流程。

4. 切点(Pointcut)

切点用于匹配连接点,定义了在哪些连接点上应用通知。切点通常通过表达式来匹配特定的连接点。

5. 引介(Introduction)

引介允许一个切面声明被通知对象未被实现的额外接口。通过引介,可以为一个对象代理多个目标类。

6. 织入(Weaving)

织入是将切面应用到目标对象的过程。在织入过程中,切面中的通知逻辑会被插入到目标方法上,使得通知逻辑在方法调用时得到执行。

7. AOP代理(AOP Proxy)

AOP代理是在AOP框架中实现切面协议的对象。Spring AOP支持两种代理方式:

  • JDK动态代理:适用于代理实现了接口的对象。Spring会使用JDK的Proxy类生成代理对象。
  • CGLIB动态代理:适用于代理没有实现接口的对象。Spring会使用CGLIB生成一个被代理对象的子类来进行代理。

8. 目标对象(Target Object)

目标对象是被代理的对象,即切面逻辑将要应用的对象。

AOP的应用场景

AOP能够将那些与业务无关、却为业务进行服务的通用公共功能封装起来,以便减少系统的代码量,使得系统更加模块化、降低系统耦合度,有利于未来系统的扩展与维护。常见的应用场景包括:

  • 事务管理:通过AOP实现声明式事务管理,简化事务控制代码。
  • 日志记录:通过AOP实现日志记录,避免在业务代码中嵌入日志逻辑。
  • 安全性检查:通过AOP实现权限验证,确保只有授权用户才能访问特定资源。
  • 性能监控:通过AOP实现性能监控,记录方法的执行时间和调用次数。

AOP的实现机制

Spring AOP通过代理机制实现切面的应用。具体来说,Spring AOP会在运行时生成代理对象,并将切面逻辑织入到目标方法中。

JDK动态代理

JDK动态代理适用于代理实现了接口的对象。Spring会使用JDK的Proxy类生成代理对象。JDK动态代理的优点是性能较高,但要求目标对象必须实现至少一个接口。

1
2
3
4
5
6
7
8
9
10
public interface UserService {
void saveUser(User user);
}

public class UserServiceImpl implements UserService {
@Override
public void saveUser(User user) {
// 业务逻辑
}
}

CGLIB动态代理

CGLIB动态代理适用于代理没有实现接口的对象。Spring会使用CGLIB生成一个被代理对象的子类来进行代理。CGLIB动态代理的优点是不要求目标对象实现接口,但性能略低于JDK动态代理。

1
2
3
4
5
public class UserService {
public void saveUser(User user) {
// 业务逻辑
}
}

示例

以下是一个简单的AOP示例,展示了如何使用Spring AOP实现日志记录功能。

1. 定义切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Aspect
@Component
public class LoggingAspect {

@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}

@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}

@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("After method: " + joinPoint.getSignature().getName() + ", result: " + result);
}
}

注释解释

  • **@Aspect**:将 LoggingAspect 类标记为一个切面类。
  • **@Component**:将 LoggingAspect 类标记为一个Spring组件,使其可以被Spring容器管理。

方法解释

  • **@Pointcut("execution(* com.example.service.*.*(..))")**:定义一个切入点 serviceMethods,匹配 com.example.service 包下所有类的所有方法。

    • execution(* com.example.service.*.*(..))
      • *:匹配任意返回类型。
      • com.example.service.*.*:匹配 com.example.service 包下的所有类和所有方法。
      • (..):匹配任意数量和类型的参数。
  • **@Before("serviceMethods()")**:定义一个前置通知 logBefore,在 serviceMethods 切入点匹配的方法执行前执行。

    • JoinPoint joinPointJoinPoint 对象包含被通知方法的详细信息,如方法签名、参数等。
    • joinPoint.getSignature().getName():获取被通知方法的名称。
  • **@AfterReturning(pointcut = "serviceMethods()", returning = "result")**:定义一个返回通知 logAfterReturning,在 serviceMethods 切入点匹配的方法成功返回后执行。

    • returning = "result":指定返回值的参数名称为 result
    • Object result:接收方法的返回值。

2. 定义服务类

1
2
3
4
5
6
7
8
@Service
public class UserService {

public String saveUser(User user) {
// 业务逻辑
return "User saved successfully";
}
}

注释解释

  • **@Service**:将 UserService 类标记为一个Spring服务组件,使其可以被Spring容器管理。

方法解释

  • **public String saveUser(User user)**:定义一个服务方法 saveUser,接收一个 User 对象作为参数,并返回一个字符串。

3. 配置Spring上下文

1
2
3
4
5
@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy
public class AppConfig {
}

注释解释

  • **@Configuration**:将 AppConfig 类标记为一个Spring配置类。
  • **@ComponentScan("com.example")**:启用组件扫描,扫描 com.example 包及其子包中的Spring组件。
  • **@EnableAspectJAutoProxy**:启用Spring AOP的自动代理功能,支持基于AspectJ的切面。

为何要有Spring AOP

Spring AOP(Aspect-Oriented Programming,面向切面编程)的设计初衷确实是为了补充和增强面向对象编程(OOP)的不足,而不是作为一种独立的编程范式来替代现有的开发模式。AOP 的核心思想是将横切关注点(Cross-Cutting Concerns)从业务逻辑中分离出来,从而提高代码的模块化、可维护性和可重用性。

Spring AOP 原理:动态代理技术的应用

Spring AOP(Aspect-Oriented Programming,面向切面编程)的核心原理是基于动态代理技术。动态代理允许在程序运行时生成代理对象,从而在不修改原有代码的情况下,插入额外的逻辑(如日志记录、事务管理等)。Spring AOP 主要使用了两种动态代理技术:JDK Proxy 和 CGLIB。

JDK Proxy

JDK Proxy 是 Java 标准库提供的一种基于接口的动态代理实现方式,适用于被代理对象实现了接口的情况。JDK Proxy 的工作原理如下:

  • 接口实现:JDK Proxy 要求被代理对象必须实现一个或多个接口。代理对象会实现相同的接口,并在运行时生成具体的实现类。

  • InvocationHandler:JDK Proxy 通过 InvocationHandler 接口来定义代理对象的行为。InvocationHandler 接口包含一个 invoke 方法,该方法会在代理对象的每个方法调用时被触发。

  • 生成代理对象:通过 Proxy.newProxyInstance 方法,可以在运行时生成代理对象。该方法需要传入类加载器、接口数组和 InvocationHandler 实例。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface MyInterface {
void doSomething();
}

class MyInterfaceImpl implements MyInterface {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}

class MyInvocationHandler implements InvocationHandler {
private final Object target;

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

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method call");
Object result = method.invoke(target, args);
System.out.println("After method call");
return result;
}
}

public class JDKProxyExample {
public static void main(String[] args) {
MyInterface target = new MyInterfaceImpl();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target)
);
proxy.doSomething();
}
}

CGLIB

CGLIB(Code Generation Library)是一种基于字节码生成的动态代理技术,适用于被代理对象没有实现接口的情况。CGLIB 的工作原理如下:

  • 字节码生成:CGLIB 通过生成被代理类的子类来实现代理。生成的子类会覆盖被代理类的所有非 final 方法,并在方法调用前后插入额外的逻辑。

  • MethodInterceptor:CGLIB 通过 MethodInterceptor 接口来定义代理对象的行为。MethodInterceptor 接口包含一个 intercept 方法,该方法会在代理对象的每个方法调用时被触发。

  • 生成代理对象:通过 Enhancer 类,可以在运行时生成代理对象。Enhancer 类需要传入被代理类的类型和 MethodInterceptor 实例。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

class MyClass {
public void doSomething() {
System.out.println("Doing something...");
}
}

class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method call");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method call");
return result;
}
}

public class CGLIBProxyExample {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyClass.class);
enhancer.setCallback(new MyMethodInterceptor());
MyClass proxy = (MyClass) enhancer.create();
proxy.doSomething();
}
}

Spring AOP 的选择策略

Spring AOP 在选择使用 JDK Proxy 还是 CGLIB 时,遵循以下策略:

  • JDK Proxy:如果被代理对象实现了接口,Spring AOP 默认使用 JDK Proxy。这是因为 JDK Proxy 是 Java 标准库的一部分,具有更好的兼容性和性能。

  • CGLIB:如果被代理对象没有实现接口,或者在配置中显式指定了使用 CGLIB,Spring AOP 会使用 CGLIB。CGLIB 适用于需要代理具体类的情况,但需要注意的是,CGLIB 生成的代理类是目标类的子类,因此目标类不能是 final 的。

动态代理与静态代理的区别

代理模式是一种常用的设计模式,其目的是为其他类提供一个代理来控制对某个对象的访问,从而将两个类的关系解耦。代理类和实现类通常需要实现相同的接口,最终调用的方法是委托类的方法。代理模式可以分为静态代理和动态代理两种类型,它们在实现方式和应用场景上存在显著差异。

静态代理

静态代理是指在代码编译时就已经确定好了被代理对象的代理方式。静态代理通常由程序员或特定工具手动创建,代理类和被代理类之间的关系在编译时就已经固定。

  • 编译时确定:静态代理在编译时就已经确定了代理类和被代理类的关系,代理类和被代理类都需要实现相同的接口。
  • 单一性:静态代理通常只代理一个具体的类,每个代理类都需要为每个被代理类单独编写。
  • 代码冗余:由于每个代理类都需要为每个被代理类单独编写,静态代理可能会导致代码冗余,尤其是在需要代理多个类时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
interface MyInterface {
void doSomething();
}

class MyInterfaceImpl implements MyInterface {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}

class MyStaticProxy implements MyInterface {
private final MyInterface target;

public MyStaticProxy(MyInterface target) {
this.target = target;
}

@Override
public void doSomething() {
System.out.println("Before method call");
target.doSomething();
System.out.println("After method call");
}
}

public class StaticProxyExample {
public static void main(String[] args) {
MyInterface target = new MyInterfaceImpl();
MyInterface proxy = new MyStaticProxy(target);
proxy.doSomething();
}
}

动态代理

动态代理是指在程序运行期间,通过反射机制动态生成的代理方式。动态代理的对象通常会有多个实现类,代理类和被代理类之间的关系在运行时才确定。

  • 运行时生成:动态代理在运行时通过反射机制动态生成代理类,代理类和被代理类之间的关系在运行时才确定。
  • 灵活性:动态代理可以代理多个类,甚至可以代理没有实现接口的类(如使用 CGLIB)。
  • 减少代码冗余:由于动态代理在运行时生成代理类,可以减少代码冗余,尤其是在需要代理多个类时。

示例代码(JDK Proxy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface MyInterface {
void doSomething();
}

class MyInterfaceImpl implements MyInterface {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}

class MyInvocationHandler implements InvocationHandler {
private final Object target;

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

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method call");
Object result = method.invoke(target, args);
System.out.println("After method call");
return result;
}
}

public class DynamicProxyExample {
public static void main(String[] args) {
MyInterface target = new MyInterfaceImpl();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target)
);
proxy.doSomething();
}
}

区别总结

特性 静态代理 动态代理
生成时机 编译时 运行时
代理对象数量 通常只代理一个类 可以代理多个类
代码冗余 较高,每个代理类都需要单独编写 较低,动态生成代理类
实现方式 手动编写代理类 通过反射机制动态生成代理类
适用场景 代理对象较少,且关系固定 代理对象较多,且关系灵活

AOP 实现常见注解

在 Spring AOP 中,注解是定义切面(Aspect)和通知(Advice)的重要方式。通过注解,可以方便地指定切点(Pointcut)、通知类型以及通知的执行时机。以下是 Spring AOP 中常见的注解及其作用:

1. @Aspect

@Aspect 注解用于定义一个切面类。切面类中包含了切点和通知的定义,Spring AOP 会根据这些定义在运行时生成代理对象。

1
2
3
4
5
6
7
8
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {
// 切点和通知定义
}
  1. @Pointcut

@Pointcut 注解用于定义切点,即指定哪些连接点(Join Point)会被应用通知。切点表达式可以使用通配符、逻辑运算符等来匹配方法。

1
2
3
4
import org.aspectj.lang.annotation.Pointcut;

@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
  1. @Before

@Before 注解用于定义前置通知,即在目标方法执行之前执行的通知。前置通知通常用于在方法执行前进行一些准备工作,如日志记录、参数校验等。

1
2
3
4
5
6
import org.aspectj.lang.annotation.Before;

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

@After 注解用于定义后置通知,即在目标方法执行之后(无论是否抛出异常)执行的通知。后置通知通常用于在方法执行后进行一些清理工作,如资源释放、日志记录等。

1
2
3
4
5
6
import org.aspectj.lang.annotation.After;

@After("serviceMethods()")
public void afterServiceMethod() {
System.out.println("After service method execution");
}
  1. @Around

@Around 注解用于定义环绕通知,即在目标方法执行前后都执行的通知。环绕通知可以完全控制目标方法的执行,包括是否执行目标方法、如何执行目标方法等。

1
2
3
4
5
6
7
8
9
10
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;

@Around("serviceMethods()")
public Object aroundServiceMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before service method execution");
Object result = joinPoint.proceed();
System.out.println("After service method execution");
return result;
}
  1. @AfterReturning

@AfterReturning 注解用于定义返回后通知,即在目标方法正常返回结果后执行的通知。返回后通知可以访问目标方法的返回值。

1
2
3
4
5
6
import org.aspectj.lang.annotation.AfterReturning;

@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void afterReturningServiceMethod(Object result) {
System.out.println("Service method returned: " + result);
}
  1. @AfterThrowing

@AfterThrowing 注解用于定义异常通知,即在目标方法抛出异常后执行的通知。异常通知可以访问抛出的异常对象。

1
2
3
4
5
6
import org.aspectj.lang.annotation.AfterThrowing;

@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
public void afterThrowingServiceMethod(Exception ex) {
System.out.println("Service method threw an exception: " + ex.getMessage());
}
  1. @Advice

@Advice 注解是一个通用的通知类型注解,用于定义通知的类型。通常情况下,Spring AOP 提供了更具体的通知类型注解(如 @Before@After 等),因此 @Advice 注解较少直接使用。

通过使用这些注解,可以方便地在 Spring AOP 中定义切面和通知,实现横切关注点的分离。以下是这些注解的简要总结:

注解 作用
@Aspect 定义切面类
@Pointcut 定义切点,指定连接点
@Before 前置通知,在目标方法执行前执行
@After 后置通知,在目标方法执行后(无论是否异常)执行
@Around 环绕通知,在目标方法执行前后都执行
@AfterReturning 返回后通知,在目标方法正常返回后执行
@AfterThrowing 异常通知,在目标方法抛出异常后执行
@Advice 通用通知类型,较少直接使用

反射的特性及其应用场景

特性

反射(Reflection)是 Java 语言提供的一种强大的机制,允许程序在运行时检查和操作类、方法、字段等结构信息。反射的主要特性包括:

  1. 运行时类信息访问:反射可以在运行时获取类的所有结构信息,包括类名、包名、父类、接口、属性、方法、构造器等。通过反射,可以动态地获取和操作这些信息。

  2. 动态对象创建:反射允许在运行时动态地创建对象实例,即使不知道具体的类名。通过 Class.forName() 方法可以加载类,并通过 newInstance() 方法创建对象实例。

  3. 方法调用:反射可以调用类的所有方法,包括私有方法。通过 Method 类的 invoke() 方法,可以在运行时调用任意方法。

  4. 访问和修改字段值:反射可以访问和修改类的字段值,包括私有字段。通过 Field 类的 get()set() 方法,可以在运行时获取和设置字段的值。

应用场景

反射在许多框架和库中得到了广泛应用,以下是一些常见的应用场景:

1. Spring 框架中的依赖注入(DI)和控制反转(IoC)

Spring 框架通过使用反射机制来实现其核心特性:依赖注入(Dependency Injection, DI)和控制反转(Inversion of Control, IoC)。

  • 依赖注入:在 Spring 中,开发者可以通过配置 XML 文件或基于注解的方式声明组件之间的依赖关系。当应用启动时,Spring 容器会扫描这些配置文件和注解,然后利用反射机制来实例化 Bean 对象,并根据配置文件自动装配到依赖中。

  • 控制反转:Spring 通过反射机制实现控制反转,将对象的创建和依赖关系的管理交给容器,而不是由开发者手动管理。

2. 动态代理

在需要对现有方法调用进行拦截、日志记录、权限控制或事务管理等应用场景中,反射结合动态代理技术被广泛应用。

  • 动态代理:动态代理允许在运行时生成代理对象,从而在不修改原有代码的情况下,插入额外的逻辑。动态代理通常结合反射机制来实现。

  • Spring AOP:Spring AOP 允许开发者定义切面(Aspect),将切面逻辑插入到原有的业务逻辑中进行增强,而不需要修改原有的代码。Spring AOP 通过动态代理和反射机制实现。