Spring Framework面试题

Spring 与Spring Framework 以及 Spring Boot 之间的是什么关系。

Spring 是一个广泛应用于 Java 开发的企业级开源框架。它的设计初衷是通过依赖注入(Dependency Injection,DI)和面向切面编程(Aspect Oriented Programming,AOP)等技术,简化 Java 应用程序的开发、测试和部署。Spring 提供了许多工具和库,使得 Java 开发者能够更快速、更有效地构建复杂的企业应用程序。

Spring Framework 是 Spring 家族中最核心的一个框架,它主要包括以下几个部分:

  1. 核心容器(Core Container):提供了依赖注入(DI)和 IoC(Inversion of Control)等基础功能。
  2. AOP(面向切面编程)和 Aspects:提供了面向切面编程的支持,使得开发者能够将一些通用功能(如日志记录、安全性检查等)抽离出来,降低代码的耦合性。
  3. 数据访问与集成(Data Access/Integration):提供了对 JDBC、ORM、事务管理等数据库操作的支持。
  4. Web:提供了对 Web 应用开发的支持,包括 MVC 框架和 RESTful Web 服务。
  5. 附加模块(Miscellaneous):包含了邮件发送、任务调度、缓存管理等其他功能。

Spring Boot 是基于 Spring Framework 的一个子项目,它的目标是简化 Spring 应用程序的初始化、配置和部署。通过使用 Spring Boot,开发者可以快速地创建一个独立的、生产级别的 Spring 应用程序,而无需关注底层的配置和依赖管理。Spring Boot 提供了以下特性:

  1. 自动配置:根据项目中的依赖关系,自动配置合适的 Spring 应用程序。
  2. 起步依赖(Starter Dependencies):简化了项目依赖的管理,开发者只需引入一个起步依赖,就可以包含所需的所有库。
  3. 嵌入式 Web 服务器:无需部署到外部 Web 服务器,直接运行 Spring Boot 应用程序即可启动内置的 Web 服务器(如 Tomcat、Jetty 等)。
  4. 生产级别的监控和管理:提供了基于 HTTP、JMX 或 SSH 的监控和管理功能,方便运维人员对应用程序进行管理。
  5. 外部化配置:支持多种配置方式,包括属性文件、YAML、环境变量和命令行参数等。

总结一下,Spring 是一个广泛的 Java 开发框架,Spring Framework 是其中的核心框架,提供了一系列的基础功能。而 Spring Boot 则是基于 Spring Framework 的一个子项目,旨在简化 Spring 应用程序的开发、配置和部署。

Spring Framework中的IOC的思想及实现原理是什么?

IoC(Inversion of Control,控制反转)是一种编程思想,其核心概念是将对象之间的依赖关系从代码中抽离出来,由外部容器(如 Spring 容器)来负责创建和管理对象及其依赖。这样,对象只需要关注自身的业务逻辑,而不需要关心如何创建和获取依赖对象。IoC 提高了代码的模块化程度,降低了模块之间的耦合性,使得应用程序更加灵活、可扩展和易于测试。

Spring Framework 中实现 IoC 的主要技术是依赖注入(Dependency Injection,简称 DI)。依赖注入有以下几种实现方式:

  1. 构造器注入(Constructor Injection):通过构造函数将依赖对象传递给需要它的对象。当对象被创建时,Spring 容器会自动调用适当的构造函数,并传入所需的依赖对象。
  2. Setter 注入(Setter Injection):通过对象的 setter 方法将依赖对象传递给需要它的对象。在对象创建之后,Spring 容器会自动调用相应的 setter 方法,将依赖对象传入。
  3. 字段注入(Field Injection):直接将依赖对象注入到需要它的对象的字段中。这种方式不需要编写额外的构造函数或 setter 方法,但会破坏封装性,因此使用相对较少。

Spring Framework 中 IoC 的实现原理主要包括以下几个步骤:

  1. 配置:首先,开发者需要在 XML 文件或 Java 配置类中定义应用程序中的对象(称为 Bean)及其依赖关系。这些配置信息将被 Spring 容器读取并解析。
  2. 初始化:当应用程序启动时,Spring 容器会根据配置信息创建 Bean 实例,并根据配置的依赖关系,将依赖对象注入到需要它们的 Bean 中。
  3. 使用:在应用程序的代码中,开发者可以通过注解(如 @Autowired)或接口(如 ApplicationContextAware)等方式,从 Spring 容器中获取已经创建并初始化好的 Bean 实例。
  4. 销毁:当应用程序关闭时,Spring 容器会负责销毁已经创建的 Bean 实例,并回收资源。

通过以上步骤,Spring Framework 实现了 IoC 思想,有效地解耦了对象之间的依赖关系,提高了代码的可维护性和可测试性。

请使用简单的java代码示例,展示上述IOC的实现过程。

以下是一个简单的 Java 示例,展示了使用 Spring Framework 实现 IoC 的过程。

首先,我们创建两个简单的 Java 类,分别表示服务接口(MessageService)和其具体实现(MessageServiceImpl):

public interface MessageService {
    String getMessage();
}

public class MessageServiceImpl implements MessageService {
    @Override
    public String getMessage() {
        return "Hello, Spring IoC!";
    }
}

接着,我们创建一个使用 MessageService 的类(MessagePrinter):

public class MessagePrinter {
    private MessageService messageService;

    public void setMessageService(MessageService messageService) {
        this.messageService = messageService;
    }

    public void printMessage() {
        System.out.println(messageService.getMessage());
    }
}

然后,我们使用 Java 配置类来定义 Spring 容器的配置信息:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public MessageService messageService() {
        return new MessageServiceImpl();
    }

    @Bean
    public MessagePrinter messagePrinter() {
        MessagePrinter printer = new MessagePrinter();
        printer.setMessageService(messageService());
        return printer;
    }
}

最后,我们编写一个简单的主类,启动 Spring 容器,并从容器中获取 MessagePrinter 实例:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Application {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MessagePrinter printer = context.getBean(MessagePrinter.class);
        printer.printMessage();
    }
}

在这个示例中,我们使用了 Java 配置类(AppConfig)来定义 Bean 及其依赖关系。MessagePrinter 依赖于 MessageService,因此在 AppConfig 中,我们将 MessageService 实例注入到 MessagePrinter 实例中。

当我们运行 Application 类的 main 方法时,Spring 容器会根据配置信息创建和初始化 Bean 实例,然后将 MessageService 实例注入到 MessagePrinter 实例中。这样,我们就实现了 IoC,使得 MessagePrinter 可以专注于自身的业务逻辑,而无需关心如何创建和获取 MessageService 实例。

请问SpringBoot如何扫描SpringBeans并注册到IOC容器中的

Spring Boot 在启动过程中,会自动扫描启动类所在的包及其子包下的所有类,并将带有 @Component、@Service、@Repository、@Controller 以及 @Configuration 等注解的类注册到 IoC 容器中。这个过程是通过 Spring 的组件扫描(Component Scanning)机制实现的。

以下是 Spring Boot 启动过程中自动注入组件的简要步骤:

  1. 创建启动类:首先,我们需要创建一个 Spring Boot 启动类,该类应包含 @SpringBootApplication 注解以及 main 方法。@SpringBootApplication 是一个复合注解,其中包括了 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan 等注解。

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class Application {
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }
    }

  2. 组件扫描:当 Spring Boot 应用程序启动时,@ComponentScan 注解会告诉 Spring 容器扫描启动类所在的包及其子包下的所有类。在扫描过程中,Spring 容器会识别出带有 @Component、@Service、@Repository、@Controller 以及 @Configuration 等注解的类,并将这些类注册为 Spring Bean。

  3. Bean 创建与依赖注入:在组件扫描完成后,Spring 容器会创建这些注册的 Bean 实例,并根据它们之间的依赖关系进行依赖注入。这样,我们就可以在应用程序的其他部分通过注解(如 @Autowired)或其他方式从 Spring 容器中获取这些已经创建并初始化好的 Bean 实例。

  4. 自动配置:此外,@EnableAutoConfiguration 注解会启用 Spring Boot 的自动配置功能,根据项目中的依赖关系自动配置合适的 Spring 应用程序。这使得开发者可以专注于业务逻辑,而无需过多关注底层的配置和依赖管理。

总之,Spring Boot 通过 @ComponentScan 注解在启动过程中自动扫描并注册组件。这个过程通常发生在应用程序启动阶段,在创建 ApplicationContext 之前。组件扫描完成后,Spring 容器会创建 Bean 实例并进行依赖注入。这样,我们就可以在应用程序中轻松地使用这些自动注册并初始化好的组件。

请问启动SpringBoot启动时,如何跟踪代码,找到@ComponentScan扫描到的@Component、@Service、@Repository、@Controller 以及 @Configuration 等注解的类,并将这些类注册为 Spring Bean,最终跟踪到已经注册为Spring Bean的容器呢?

要跟踪 Spring Boot 启动过程中 @ComponentScan 的执行过程,我们需要先了解 Spring Boot 的启动流程。以下是一个简化的 Spring Boot 启动流程:

  1. 在启动类的 main 方法中,调用 SpringApplication.run() 方法。
  2. SpringApplication.run() 方法创建并配置一个新的 SpringApplication 实例。
  3. SpringApplication 实例调用 run() 方法。
  4. 在 run() 方法中,创建一个新的 AnnotationConfigApplicationContext 实例。
  5. 使用 AnnotationConfigApplicationContext 的 register() 方法注册启动类。
  6. 调用 AnnotationConfigApplicationContext 的 refresh() 方法,触发组件扫描、Bean 创建和依赖注入等操作。

下面是一个简化的例子,展示了如何使用调试器跟踪 Spring Boot 启动过程中的组件扫描:

  1. 在启动类中,为 SpringApplication.run() 方法设置一个断点。

    @SpringBootApplication
    public class Application {
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args); // 设置断点
    }
    }

  2. 使用调试模式运行应用程序。当断点触发时,使用调试器的“步进”功能,跟踪 SpringApplication.run() 方法的执行。

  3. 当执行到 AnnotationConfigApplicationContext 的 refresh() 方法时,设置一个断点。

    // 在 org.springframework.context.support.AbstractApplicationContext 类中
    public void refresh() throws BeansException, IllegalStateException {
    // ...
    try {
    // ...
    prepareBeanFactory(beanFactory);
    // ...
    postProcessBeanFactory(beanFactory);

         invokeBeanFactoryPostProcessors(beanFactory); // 设置断点
         // ...
         registerBeanPostProcessors(beanFactory);
         // ...
         initMessageSource();
         initApplicationEventMulticaster();
         onRefresh();
         registerListeners();
         finishBeanFactoryInitialization(beanFactory);
         finishRefresh();
     } catch (BeansException ex) {
         // ...
     }
    

    }

  4. 使用调试器的“步进”功能,跟踪 invokeBeanFactoryPostProcessors() 方法的执行。在该方法中,ConfigurationClassPostProcessor 类负责处理带有 @ComponentScan、@Component、@Service、@Repository、@Controller 和 @Configuration 等注解的类。

  5. 在 ConfigurationClassPostProcessor 类中的 processConfigBeanDefinitions() 方法设置一个断点。在该方法中,ConfigurationClassParser 类负责解析配置类以及进行组件扫描。

    // org.springframework.context.annotation.ConfigurationClassPostProcessor 类中
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List configCandidates = new ArrayList<>();
    // ...
    ConfigurationClassParser parser = new ConfigurationClassParser(
    metadataReaderFactory, problemReporter, environment, resourceLoader, componentScanBeanNameGenerator, registry);

     parser.parse(candidateComponents.toArray(new BeanDefinitionHolder[0])); // 设置断点
     // ...
    

    }

  6. 使用调试器的“步进”功能,跟踪 ConfigurationClassParser 类的 parse() 方法。在该方法中,doProcessConfigurationClass() 方法负责处理带有 @ComponentScan、@Component、@Service、@Repository、@Controller 和 @Configuration 等注解的类。

  7. 在 ConfigurationClassParser 类中的 doProcessConfigurationClass() 方法设置一个断点。

    // 在 org.springframework.context.annotation.ConfigurationClassParser 类中
    private void doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
    throws IOException {
    // ...
    if (configClass.getMetadata().isAnnotated(ComponentScan.class.getName())) {
    // ...
    componentScanParser.parse(componentScan, configClass); // 设置断点
    }
    // ...
    }

  8. 使用调试器的“步进”功能,跟踪 componentScanParser.parse() 方法。在该方法中,ComponentScanAnnotationParser 类负责执行实际的组件扫描。

  9. 在 ComponentScanAnnotationParser 类中的 parse() 方法设置一个断点。在该方法中,ClassPathBeanDefinitionScanner 类负责注册扫描到的组件为 Spring Bean。

    // 在 org.springframework.context.annotation.ComponentScanAnnotationParser 类中
    public Set parse(AnnotationAttributes componentScan, final String declaringClass) {
    // ...
    scanner.scan(basePackages); // 设置断点
    return scanner.getRegistry().getBeanDefinitionHolders();
    }

  10. 使用调试器的“步进”功能,跟踪 ClassPathBeanDefinitionScanner 类的 scan() 方法。在该方法中,doScan() 方法负责扫描指定的包及其子包下的类。

    // 在org.springframework.context.annotation.ClassPathBeanDefinitionScanner 类中
    @Override
    public Set scan(String... basePackages) {
    Set beanDefinitions = new LinkedHashSet<>();
    // ...
    for (String basePackage : basePackages) {
    Set candidates = findCandidateComponents(basePackage);
    for (BeanDefinition candidate : candidates) {
    // ...
    beanDefinitions.add(holder);
    }
    }
    return beanDefinitions; // 已注册的 Bean 定义
    }

通过以上步骤,你可以使用调试器跟踪 Spring Boot 启动过程中的组件扫描。在 ClassPathBeanDefinitionScanner 类的 scan() 方法返回的 beanDefinitions 集合中,你可以找到已经注册为 Spring Bean 的组件。

Spring Framework 中的AOP是什么? 有什么用? 使用场景是什么?

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在解决软件开发中的横切关注点(Crosscutting Concerns)问题。横切关注点是指在软件系统中多个组件和功能之间共享的部分,例如日志记录、事务管理、安全性和缓存等。AOP 通过将这些横切关注点与业务逻辑分离,提高了代码的模块化和可维护性。

在 Spring Framework 中,AOP 是通过代理(Proxy)实现的。代理是一个包装了目标对象的对象,代理可以拦截目标对象上的方法调用,并在调用目标方法之前或之后执行额外的操作。Spring AOP 支持 JDK 动态代理和 CGLIB 代理两种实现方式。

AOP 的主要概念包括:

  1. 切面(Aspect):切面是一个关注点的模块化表示。切面通常包含多个通知(Advice)。
  2. 通知(Advice):通知是切面在特定的连接点(Join Point)执行的操作。主要有以下类型:前置通知(Before)、后置通知(After)、返回通知(After Returning)、异常通知(After Throwing)和环绕通知(Around)。
  3. 连接点(Join Point):连接点是程序执行过程中的某个特定点,如方法调用或异常抛出。
  4. 切入点(Pointcut):切入点是一组连接点的集合,定义了通知应该在哪些连接点执行。
  5. 引入(Introduction):引入允许我们在现有类中添加新方法或属性。
  6. 目标对象(Target Object):目标对象是被代理的对象。
  7. 代理(Proxy):代理是一个包装了目标对象的对象,代理可以拦截目标对象上的方法调用,并在调用目标方法之前或之后执行额外的操作。

AOP 的使用场景:

  1. 日志记录:记录方法调用的开始、结束、异常等信息。
  2. 事务管理:在需要进行事务控制的方法上添加事务的开始、提交、回滚等操作。
  3. 访问控制和权限验证:在方法调用之前进行权限验证,确保调用者具有足够的权限。
  4. 性能监控:测量方法执行时间,用于性能分析和优化。
  5. 缓存处理:在数据访问方法上添加缓存逻辑,以减轻数据库压力并提高查询性能。
  6. 异常处理:在方法执行过程中捕获异常,并进行统一的异常处理。

通过使用 Spring AOP,你可以将这些横切关注点与业务逻辑分离,从而提高代码的可读性、可维护性。

请根据上述使用场景,分别给出相应的java代码示例加以说明。

以下是根据上述使用场景提供的 Java 代码示例。我们将通过 Spring AOP 实现日志记录、事务管理和权限验证等功能:

日志记录:

创建一个切面类 LoggingAspect 用于记录方法调用的开始、结束和异常信息:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
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(JoinPoint joinPoint) {
        System.out.println("开始执行方法: " + joinPoint.getSignature().getName());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("方法执行结束: " + joinPoint.getSignature().getName());
    }

    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
        System.out.println("方法抛出异常: " + joinPoint.getSignature().getName());
        System.out.println("异常信息: " + error.getMessage());
    }
}

事务管理:

创建一个切面类 TransactionAspect 用于管理事务。为简化示例,我们只在方法上使用 System.out.println() 模拟事务操作。

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 TransactionAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("开始事务");
        Object result;
        try {
            result = joinPoint.proceed();
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            throw e;
        }
        return result;
    }
}

权限验证:

创建一个切面类 SecurityAspect 用于权限验证。在本示例中,我们仅检查一个简单的权限标志,实际应用中可能需要更复杂的权限验证逻辑。

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

@Aspect
@Component
public class SecurityAspect {

    private boolean hasPermission = true; // 模拟权限标志

    @Before("execution(* com.example.service.*.*(..))")
    public void checkPermission(JoinPoint joinPoint) {
        if (!hasPermission) {
            throw new RuntimeException("没有权限执行方法: " + joinPoint.getSignature().getName());
        }
    }
}

在这些示例中,我们使用 @Aspect 注解定义切面类,并使用 @Component 注解将其注册为 Spring Bean。通过使用不同类型的通知(如 @Before、@After、@AfterThrowing 和 @Around),我们可以在方法调用的不同阶段执行特定的操作。这些操作可以包括日志记录、事务管理和权限验证等功能。在这些示例中,我们使用 execution() 表达式定义切入点,以匹配特定包(如 com.example.service)中的所有类和方法。这使得我们可以在多个组件和功能之间共享横切关注点。

接下来,我们创建一个简单的服务类 UserService,以演示如何将上述切面应用于实际代码:

package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public void addUser() {
        System.out.println("添加用户");
    }

    public void deleteUser() {
        System.out.println("删除用户");
    }
}

现在,当我们在应用程序中调用 UserService 的 addUser() 或 deleteUser() 方法时,我们的 AOP 切面将自动执行日志记录、事务管理和权限验证操作。例如,当我们调用 addUser() 方法时,输出可能如下所示:

makefileCopy code
开始执行方法: addUser
开始事务
添加用户
提交事务
方法执行结束: addUser

通过使用 Spring AOP,我们可以将横切关注点与业务逻辑分离,从而提高代码的可读性和可维护性。请注意,这些示例仅用于演示目的,在实际项目中,可能需要根据具体需求调整切面的实现和配置。

性能监控:

创建一个切面类 PerformanceAspect 用于测量方法执行时间。

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 PerformanceAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long elapsedTime = System.currentTimeMillis() - startTime;
        System.out.println("方法 " + joinPoint.getSignature().getName() + " 执行时间: " + elapsedTime + " ms");
        return result;
    }
}

这个切面使用 @Around 通知,在方法执行前后记录当前时间,并计算方法执行所需的时间。

缓存处理:

创建一个切面类 CacheAspect 用于处理数据访问方法的缓存逻辑。这里我们使用一个简单的 HashMap 作为缓存,实际项目中可能需要使用更高级的缓存实现,如 Redis。

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

import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
public class CacheAspect {

    private Map<String, Object> cache = new HashMap<>();

    @Around("execution(* com.example.service.*.*(..))")
    public Object handleCache(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        if (cache.containsKey(methodName)) {
            System.out.println("从缓存获取数据: " + methodName);
            return cache.get(methodName);
        } else {
            Object result = joinPoint.proceed();
            cache.put(methodName, result);
            System.out.println("将数据添加到缓存: " + methodName);
            return result;
        }
    }
}

这个切面在方法执行前检查缓存中是否已经有结果,如果有,则直接从缓存中获取结果;如果没有,则执行方法并将结果添加到缓存。

异常处理:

创建一个切面类 ExceptionHandlingAspect 用于统一处理方法执行过程中抛出的异常。

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

@Aspect
@Component
public class ExceptionHandlingAspect {

    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "exception")
    public void handleException(JoinPoint joinPoint, Exception exception) {
        System.out.println("方法 " + joinPoint.getSignature().getName() + " 发生异常: " + exception.getMessage());
        // 在这里进行异常处理,如记录日志、发送通知等
    }
}

这个切面使用 @AfterThrowing 通知在方法抛出异常后执行异常处理逻辑。在这个示例中,我们仅输出异常信息,但实际项目中可能需要执行更复杂的异常处理操作,如记录日志、发送通知或者执行回滚操作等。

现在,我们已经为每个使用场景创建了相应的切面类。当我们在应用程序中调用服务类的方法时,这些切面将自动应用到方法调用上,实现性能监控、缓存处理和异常处理等功能。

为了演示这些切面的工作方式,我们将创建一个简单的服务类 ProductService:

package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class ProductService {

    public String getProductInfo(String productId) {
        System.out.println("获取产品信息: " + productId);
        // 模拟从数据库或其他数据源获取数据
        return "Product Info for " + productId;
    }

    public void updateProductInfo(String productId) {
        System.out.println("更新产品信息: " + productId);
        // 模拟更新数据库或其他数据源中的数据
    }
}

当我们在应用程序中调用 ProductService 的 getProductInfo() 或 updateProductInfo() 方法时,我们的 AOP 切面将自动执行性能监控、缓存处理和异常处理操作。例如,当我们调用 getProductInfo("123") 方法时,输出可能如下所示:

makefileCopy code
开始执行方法: getProductInfo
开始事务
获取产品信息: 123
提交事务
方法执行结束: getProductInfo
方法 getProductInfo 执行时间: 5 ms
将数据添加到缓存: getProductInfo

当我们再次调用 getProductInfo("123") 方法时,输出可能如下所示:

makefileCopy code
开始执行方法: getProductInfo
开始事务
从缓存获取数据: getProductInfo
提交事务
方法执行结束: getProductInfo
方法 getProductInfo 执行时间: 2 ms

通过使用 Spring AOP,我们可以将横切关注点与业务逻辑分离,从而提高代码的可读性和可维护性。请注意,这些示例仅用于演示目的,在实际项目中,可能需要根据具体需求调整切面的实现和配置。

JDK动态代理与Cglib代理的区别是什么? 共同点是什么?

JDK 动态代理与 Cglib 代理的区别主要在于代理方式和使用条件。它们的共同点是都能在运行时创建代理对象,从而实现对目标对象的方法拦截。

JDK 动态代理:

  • 只能代理实现了接口的类;
  • 使用 Java 反射 API,具体为 java.lang.reflect.Proxy 类;
  • 性能相对较低,因为它使用反射调用方法。

示例代码:

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

interface UserService {
    void addUser();
}

class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("添加用户");
    }
}

class JDKProxy implements InvocationHandler {
    private Object target;

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

    public Object getProxyInstance() {
        // 使用 Proxy.newProxyInstance() 方法创建代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用方法前,添加自定义逻辑
        System.out.println("开始执行方法: " + method.getName());

        // 调用目标对象的方法
        Object result = method.invoke(target, args);

        // 在调用方法后,添加自定义逻辑
        System.out.println("结束执行方法: " + method.getName());

        return result;
    }
}

public class JDKProxyExample {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        JDKProxy jdkProxy = new JDKProxy(userService);
        UserService proxyInstance = (UserService) jdkProxy.getProxyInstance();
        proxyInstance.addUser();
    }
}

Cglib 代理:

  • 可以代理没有实现接口的类;
  • 使用第三方库 Cglib(Code Generation Library),需要引入 Cglib 依赖;
  • 性能相对较高,因为它使用字节码技术生成子类。

示例代码(需要引入 Cglib 依赖):

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

class UserServiceCglib {
    public void addUser() {
        System.out.println("添加用户");
    }
}

class CglibProxy implements MethodInterceptor {
    private Object target;

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

    public Object getProxyInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        // 使用 Enhancer.create() 方法创建代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 在调用方法前,添加自定义逻辑
        System.out.println("开始执行方法: " + method.getName());

        // 调用目标对象的方法
        Object result = proxy.invokeSuper(obj, args);

        // 在调用方法后,添加自定义逻辑
        System.out.println("结束执行方法: " + method.getName());
      
      return result;
    }
}


public class CglibProxyExample {
    public static void main(String[] args) {
      UserServiceCglib userService = new UserServiceCglib();
      CglibProxy cglibProxy = new CglibProxy(userService);
      UserServiceCglib proxyInstance = (UserServiceCglib) cglibProxy.getProxyInstance();
      proxyInstance.addUser();
    }
}

总结:

  • JDK 动态代理:只能代理实现了接口的类,使用 Java 反射 API,性能较低;
  • Cglib 代理:可以代理没有实现接口的类,使用第三方库 Cglib,性能较高;
  • 共同点:都能在运行时创建代理对象,实现对目标对象的方法拦截。

Spring Framework中的数据访问与集成(Data Access/Integration),比如提供了对 JDBC、ORM、事务管理等数据库操作的支持。

Spring Framework 的数据访问与集成模块为数据库操作提供了一套抽象层,包括对 JDBC、ORM、事务管理等的支持。这使得开发者可以更轻松地处理数据库操作,并减少因底层技术变更带来的影响。

JDBC 支持:

Spring Framework 提供了 JdbcTemplate 类来简化 JDBC 操作,使得开发者不需要关心底层的资源管理和错误处理。下面是一个使用 JdbcTemplate 查询数据库的示例:

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcExample {
    public static void main(String[] args) {
        // 配置数据源
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("root");

        // 创建 JdbcTemplate 实例
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

        // 定义查询 SQL
        String sql = "SELECT * FROM users WHERE id = ?";

        // 定义 RowMapper,用于将结果集映射到 Java 对象
        RowMapper<User> rowMapper = new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int rowNum) throws SQLException {
                User user = new User();
                user.setId(resultSet.getLong("id"));
                user.setName(resultSet.getString("name"));
                user.setEmail(resultSet.getString("email"));
                return user;
            }
        };

        // 执行查询
        User user = jdbcTemplate.queryForObject(sql, rowMapper, 1L);
        System.out.println(user);
    }
}

ORM 支持(使用 MyBatis):

Spring Framework 提供了对多种 ORM 框架的支持,如 Hibernate、MyBatis 等。下面是一个使用 MyBatis 查询数据库的示例:

首先,需要在 pom.xml 文件中添加 MyBatis 和 MySQL 驱动的依赖:

<dependencies>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.22</version>
    </dependency>
</dependencies>

然后,创建一个 User 实体类:

public class User {
    private Long id;
    private String name;
    private String email;

    // getter 和 setter 省略
}

接下来,创建一个 UserMapper 接口,并使用 MyBatis 注解定义 SQL:

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User getUserById(Long id);
}

接着,在 application.properties 文件中配置 MyBatis 和数据库相关的设置:

propertiesCopy code
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

mybatis.type-aliases-package=com.example.model mybatis.mapper-locations=classpath:mapper/*.xml

接下来,创建一个 UserService 类,用于调用 UserMapper 的方法:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User getUserById(Long id) {
        return userMapper.getUserById(id);
    }
}

最后,在 Spring Boot 的启动类中注入 UserService,并调用其方法查询用户信息:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyBatisExampleApplication {
    @Autowired
    private UserService userService;

    public static void main(String[] args) {
        SpringApplication.run(MyBatisExampleApplication.class, args);
    }

    @PostConstruct
    public void run() {
        User user = userService.getUserById(1L);
        System.out.println(user);
    }
}

事务管理:

Spring Framework 提供了一致的事务管理抽象,支持编程式和声明式事务。下面是一个使用声明式事务的示例:

首先,在 application.properties 文件中启用事务管理:

propertiesCopy code
spring.tx.enable=true

然后,在 UserService 类中添加一个更新用户信息的方法,并使用 @Transactional 注解开启事务:

import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {
    // 省略其他代码

    @Transactional
    public void updateUser(Long id, String name, String email) {
        // 更新用户信息,如 userMapper.updateUser(id, name, email);
        // 如果出现异常,事务将自动回滚
    }
}

Spring Framework 的数据访问与集成模块提供了丰富的功能,使得开发者可以更轻松地处理数据库操作。上述示例仅展示了其中的一部分功能,实际使用时可能还需要根据具体需求进行调整和优化。

posted @ 2023-04-09 21:43  BlakeYa  阅读(11)  评论(0编辑  收藏  举报