java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring question

Spring question问题小结

作者:奈川直子

在AppConfig配置类中,通过@Bean注解创建了Service和Controller的实例,Spring会自动将这些实例纳入容器的管理,并处理它们之间的依赖关系,本文给大家介绍Spring question问题小结,感兴趣的朋友跟随小编一起看看吧

Spring

一. 控制反转(IoC)

1.手动

使用了Spring的 @Configuration @Bean 注解来明确指定了哪些类需要被纳入容器的管理。在 AppConfig 配置类中,通过 @Bean 注解创建了 Service Controller 的实例,Spring会自动将这些实例纳入容器的管理,并处理它们之间的依赖关系。

// 定义一个Service接口
public interface Service {
    void doSomething();
}
// 实现Service接口的具体类
public class ServiceImpl implements Service {
    @Override
    public void doSomething() {
        System.out.println("Service is doing something.");
    }
}
// 定义一个Controller类,它依赖于Service接口
public class Controller {d
    private Service service;
    // 通过构造函数注入依赖
    public Controller(Service service) {
        this.service = service;
    }
    public void doAction() {
        service.doSomething();
    }
}
// 在应用的入口处,使用Spring容器创建实例并进行依赖关系的管理
public class Main {
    public static void main(String[] args) {
        // 创建Spring容器
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        // 从容器中获取Controller实例
        Controller controller = context.getBean(Controller.class);
        // 调用Controller的方法,会自动调用Service的方法
        controller.doAction();
    }
}
// 定义一个配置类,用于告诉Spring容器如何创建和管理Bean
@Configuration
public class AppConfig {
    @Bean
    public Service service() {
        return new ServiceImpl();
    }
    @Bean
    public Controller controller(Service service) {
        return new Controller(service);
    }
}

2.自动

SpringApplication.run(CommunityApplication.class, args) 会扫描应用中的所有组件(包括被 @Component @Service @Repository 等注解标记的类),并将它们纳入Spring容器的管理。通过调用 SpringApplication.run 方法启动应用,会自动创建一个Spring应用上下文(ApplicationContext),并初始化整个应用的配置和组件。

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

@SpringBootApplication 实际上是一个组合注解,包含了 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan 三个注解。其中, @SpringBootConfiguration 注解表示这是一个Spring Boot的配置类, @EnableAutoConfiguration 注解启用自动配置, @ComponentScan 注解指定要扫描的包路径。使用Spring Boot的自动化机制可以方便快捷地扫描和管理所有的Bean,而手动进行依赖注入可以更加精确地控制容器中的Bean。

Q1:使用@Repository注解时,怎么在Spring的配置文件中进行相关的配置?

1、Spring

在使用@Repository注解时,可以在Spring的配置文件中进行相关的配置。以下是配置步骤:

1.在Spring的配置文件中添加 context:component-scan 标签,用于开启组件扫描功能,扫描带有 @Repository 注解的类。示例如下:

<context:component-scan base-package="com.example.repository" />

其中, base-package 属性指定了要扫描的包路径。

2.如果需要配置数据源和事务管理器,可以在Spring的配置文件中进行相关的配置。示例如下:

<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
    <!-- 数据源相关配置 -->
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

其中, dataSource 是数据源的配置, transactionManager 是事务管理器的配置。

3.如果需要使用 @Repository 注解对应的数据访问异常转化功能,可以配置相关的异常转化器。示例如下:

<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

这个配置会自动为带有 @Repository 注解的类添加异常转化功能。通过以上配置,就可以在Spring的配置文件中配置 @Repository 注解相关的配置。注意,具体的配置内容根据实际需求和使用的技术选择可能会有所不同。

2、Spring Boot

在使用Spring Boot时,可以省略繁琐的配置过程,因为Spring Boot提供了自动配置的功能。对于@Repository注解的配置,可以按照以下步骤进行:

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

这样,Spring Boot会根据配置文件的内容自动配置数据源。

4.如果需要事务管理器,可以在需要使用事务的类或方法上添加 @Transactional 注解,Spring Boot会自动配置事务管理器。 通过以上步骤,就可以在Spring Boot中使用@Repository注解,无需进行繁琐的配置,Spring Boot会根据约定和自动配置的机制自动完成相关的配置。 Q2:@Primary有什么用

@Primary注解是Spring框架中的一个注解,用于标识主要的Bean实例。当存在多个同类型的Bean实例时,通过使用@Primary注解,可以指定其中一个Bean实例为首选的主要实例。

Q3:@Repository有什么用

@Repository注解是Spring框架中的一个注解,用于标识持久层组件(如DAO类)。

@Repository的作用主要有以下几点:

Q4:谈谈对IoC的理解

控制:创建、实例化对象的权力反转:将这些权力交给IoC容器和Spring框架

将对象之间的依赖关系交给IoC容器管理,由IoC容器完成对象的注入,简化开发,IoC容器像是一个工厂,创建一个对象时只配置好,不需要考虑怎么被创建出来的。Spring中用Xml文件配置bean,在Spring Boot中用注解配置。

Q5:@Component和@Bean的区别

Q6:注入Bean的注解

1.@Autowired 属于 Spring 内置的注解,默认的注入方式是 byType(根据类型进行匹配) ,首先根据接口类型去匹配并注入 Bean 。如果一个接口有多个实现类,注入方式就会变为 byName(根据名称匹配)

举个例子, SmsService 接口有两个实现类: SmsServiceImpl1 SmsServiceImpl2 ,且它们都已经被 Spring 容器所管理。

// 报错,byName 和 byType 都无法匹配到 bean
@Autowired
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Autowired
private SmsService smsServiceImpl1;
// 正确注入  SmsServiceImpl1 对象对应的 bean
// smsServiceImpl1 就是我们上面所说的名称
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;

2.@Resource 属于 JDK 提供的注解,默认注入方式为 byName 。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为 byType @Resource 有两个比较重要且日常开发常用的属性: name (名称)、 type (类型)。

public @interface Resource {
    String name() default "";
    Class<?> type() default Object.class;
}

如果仅指定 name 属性则注入方式为 byName ,如果仅指定 type 属性则注入方式为 byType ,如果同时指定 name type 属性(不建议这么做)则注入方式为 byType + byName

// 报错,byName 和 byType 都无法匹配到 bean
@Resource
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Resource
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;

总结一下:

Q8:Bean的作用域

Q9:为什么在Spring boot没有配置 bean 的作用域

在Spring Boot中,为了简化配置和提高开发效率,通常不需要显式地配置Bean的作用域。Spring Boot默认使用单例模式来管理Bean,即每个Bean在容器中只会存在一个实例。 这是因为Spring Boot遵循"约定优于配置"的原则,通过自动配置和默认配置来简化开发过程。在大多数情况下,单例模式已经能够满足开发需求,因此默认使用单例模式可以减少不必要的配置。 如果需要使用其他作用域,如原型(prototype)、会话(session)、请求(request)等,可以在需要的地方使用特定的注解来标记,而不需要在配置文件中显式配置。例如,可以使用** @Scope("prototype") **注解来将特定的Bean定义为原型模式。

二. 面向切面编程(AOP)

术语含义
目标(Target)目标对象/被通知的对象
代理(Proxy)向目标对象应用通知之后创建的代理对象
连接点(JoinPoint)目标对象的所属类中,定义的所有方法均为连接点
切入点(Pointcut)被切面拦截/增强的连接点(切入点一定是连接点,连接点不一定是织入点)
通知(Advice)增强的逻辑/代码,也是拦截到目标对象的连接点后应该做的事
织入(Weaving)将通知 应用到目标对象,产生代理对象的过程动作
方面主键(Aspect)Pointcut+Advice

Q1:谈谈对AOP的理解

能够将与业务无关,却为业务模块所共同调用的逻辑和责任所封装起来,例如:事务处理、权限控制,日志管理,减少重复代码,比较好维护。

Spring AOP 基于动态代理的,如果代理的对象实现了某个接口,那么 Spring AOP 会使用 JDK Proxy ,去创建代理对象;而对于没有实现接口的对象, Spring AOP 使用 Cglib 生成一个被代理对象的子类作为代理。

Spring AOP 的工作原理:自动为目标对象生成代理,并在方法调用时织入切面逻辑。

代理对象是通过 Spring AOP 自动生成的,并且已经被注入到 ApplicationContext 中。具体来说,代理对象是在以下代码中获取的:

UserService userService = context.getBean(UserService.class);
// 主类
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class, args);
        UserService userService = context.getBean(UserService.class);
        userService.addUser("john", "123456");
    }
}

在这个示例中,通过 ApplicationContext getBean 方法来获取代理对象。根据 Spring AOP 的配置,如果 UserService 接口被代理,那么获取到的 userService 对象就是代理对象;如果 UserServiceImpl 类被代理,那么获取到的 userService 对象也是代理对象。

Q2:为什么要对Spring AOP进行代理,不代理会怎样?

Spring AOP 中,代理是实现切面功能的关键。如果不进行代理,切面逻辑将无法被织入到目标对象的方法调用中,失去了 AOP 的作用。 具体来说,代理对象在目标对象和调用方之间充当了一个中间层。当调用方调用代理对象的方法时,代理对象会在方法执行前后执行切面逻辑。这样,我们可以在切面中添加一些额外的逻辑,比如日志记录、事务管理、性能监控等。代理对象将切面逻辑织入到目标对象的方法调用中,从而实现了横切关注点的模块化。 如果不进行代理,切面逻辑将无法被自动应用到目标对象的方法调用中。这意味着我们需要在每个目标对象方法的调用处手动添加切面逻辑,这样会导致代码的重复和冗余,不利于代码的维护和扩展。而代理机制可以自动为目标对象生成代理,并在方法调用时织入切面逻辑,使得切面的应用更加便捷和灵活。 除了将切面逻辑织入到方法调用中,代理还可以实现其他功能,比如延迟加载、事务管理、缓存等。代理对象可以拦截方法调用,根据需要进行一些额外的处理,从而提供更多的功能。

Q3:那在以下代码中,谁是目标对象,谁是代理对象,谁是调用方?

public class AlphaAspect {    
@Pointcut("execution(* com.newcoder.community.service.*.*(..))")//所有类、所有方法   
public void pointcut(){}
@Before("pointcut()")//连接点开始记日志
public void before(){
    System.out.println("before");
}
@After("pointcut()")//连接点后记日志
public void after(){
    System.out.println("after");
}
@AfterReturning("pointcut()")//有返回值后记日志
public void afterReturn(){
    System.out.println("afterReturn");
}
@AfterThrowing("pointcut()")//抛异常后记日志
public void afterThrowing(){
    System.out.println("afterThrowing");
}
@Around("pointcut()")//前后都织入  执行代理对象,织入代理对象,用来代替原始对象
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{//ProceedingJoinPoint为连接点,织入部位
    System.out.println("around before");
    Object obj = joinPoint.proceed();//proceed调目标主键 调用原始对象方法
    System.out.println("around after");
    return obj;
}

在这个例子中,目标对象是com.newcoder.community.service包下的所有类的所有方法。 AlphaAspect类本身是一个切面类,其中包含了各种注解标记的通知方法。这些通知方法会在目标对象的方法调用前后执行相应的逻辑。因此,AlphaAspect类是代理对象。 调用方是在业务代码中调用目标对象的方法的地方,这个例子中没有给出具体的业务代码,所以无法确定调用方是什么。

所以,代理对象会在目标对象的方法调用前后执行相应的逻辑。

Q4:那为什么说向目标对象应用通知之后创建代理对象?

在AOP中,通知(Advice)是切面的核心逻辑,用于在目标对象的方法调用前后执行特定的操作。为了将通知应用到目标对象的方法调用中,需要创建代理对象来包装目标对象。代理对象负责拦截目标对象的方法调用,并在适当的时机执行通知。 代理对象的创建过程可以分为两种方式:静态代理和动态代理。

Q5:目标对象所有的方法就是连接点吗?

是的,目标对象的所有方法都可以作为连接点。连接点是程序执行过程中的特定点,包括方法的调用、异常的抛出、字段的访问等。在AOP中,最常见的连接点是方法的调用,因为我们通常希望在方法的执行前、执行后或发生异常时插入切面逻辑。 在AOP中,可以选择在目标对象的所有方法上织入切面逻辑,也可以选择只在特定的方法上织入切面逻辑,这取决于所定义的切点(Pointcut)。切点是定义在哪些连接点上织入切面逻辑的规则,可以使用表达式或注解来定义切点。

Q6:切入点是类路径吗?

不,切入点(Pointcut)并不是类路径。切入点是在 AOP 用于定义哪些连接点(JoinPoint)应该被织入切面逻辑的规则。 切入点可以使用表达式或注解来定义。使用表达式定义切入点时,可以使用 AspectJ 风格的切入点表达式,如 execution、within、args 等关键字,以及类、方法、参数等信息来描述需要织入切面逻辑的连接点。使用注解来定义切入点时,可以通过在目标对象的方法上添加特定的注解,然后通过切入点表达式来匹配这些注解来确定要织入切面逻辑的连接点。 切入点可以非常灵活地定义,可以选择在目标对象的所有方法上织入切面逻辑,也可以选择只在特定的方法或类上织入切面逻辑。切入点的定义是根据业务需求和设计目标来确定的,通常会根据实际情况来选择需要织入切面逻辑的连接点。

Q7:织入的过程是不是就相当于动态代理和静态代理过程?

? 是的,织入的过程可以类比为动态代理和静态代理的过程。? 在静态代理中,代理对象和目标对象实现同一个接口,代理对象在调用目标对象方法的前后插入额外的逻辑。这种方式需要在编译时期就确定代理关系,并在代码中显式地指定代理对象。? 在动态代理中,代理对象是在运行时动态生成的,无需事先编写代理类。通过Java的反射机制,动态代理可以在运行时拦截并处理目标对象的方法调用。在动态代理中,我们可以在目标对象方法的调用前后插入切面逻辑。? 类似地, AOP 的织入过程也是在运行时动态生成的。 AOP 框架会根据切入点的定义,对目标对象的方法调用进行拦截,并根据切面逻辑对其进行增强。这个过程可以看作是在运行时动态代理的过程。 不同的是,动态代理和静态代理通常是针对单个类或对象进行的,而** AOP 的织入可以同时作用于多个类和对象**,根据切入点的定义对满足条件的连接点进行增强。

三、Spring MVC

Q1:说说自己对Spring MVC的了解

MVC是模型(Model)、视图(View)、控制器(Controller)的简写,它是通过将业务逻辑、数据、显示分离组织代码。

Q2:Spring MVC的核心组件有哪些?

Q3:Spring MVC的工作原理

Q4:Handler 是什么?

Handler 是指在 Spring MVC 框架中用于处理用户请求的组件。Handler 可以是一个 Controller 类中的方法、一个 Servlet、一个 WebSocket 处理器或其他可处理请求的组件。

Q5:DispatcherServlet 为什么调用 HandlerAdapter适配器执行 Handler?

不同的 Handler 可能有不同的处理方式,例如一个 Controller 方法、一个 Servlet、一个 WebSocket 处理器等。为了统一处理不同类型的 Handler,需要使用适配器模式将不同类型的 Handler 适配为统一的处理方式。 HandlerAdapter 适配器的作用就是根据不同类型的 Handler,将请求信息进行适配,使其能够被统一调用并执行。它根据 Handler 的类型,调用相应的适配方法,将请求信息传递给 Handler 进行处理,并获取处理结果返回给 DispatcherServlet。 通过使用 HandlerAdapter 适配器,DispatcherServlet 不需要关心具体的 Handler 类型,只需要调用适配器的方法即可,实现了对不同类型 Handler 的统一调用和处理。

Q5:为什么会将请求涉及到的拦截器和 Handler 一起封装?

将请求涉及到的拦截器和Handler一起封装的目的是为了在请求处理过程中能够方便地对请求进行拦截和处理。 拦截器是用于对请求进行预处理和后处理的组件,可以在请求到达Controller之前和之后执行额外的逻辑。通过将拦截器和Handler一起封装,可以实现以下几个方面的功能:

四、设计模式

Q1:Spring框架中用到了哪些设计模式?

五、Spring事务

Q1:事务的特性(ACID)了解吗?

只有保证了原子性、隔离性、持久性以后,一致性才能得到保障:AID->C

Q2:什么时候才用事务的隔离性?

事务的隔离性是指多个事务并发执行时,各个事务之间的数据应该是相互隔离的,一个事务的操作不应该对其他事务的操作产生影响。在并发环境下,如果多个事务同时访问和修改共享数据,可能会导致数据不一致的问题。 举个例子来说明事务的隔离性的使用场景: 假设有一个电商平台,用户可以在平台上购买商品,而商品的库存是需要管理的。当用户购买商品时,会进行库存的扣减操作。在这个场景中,如果不考虑事务的隔离性,可能会出现以下问题:

非隔离性导致的库存错误:假设有两个用户 A 和 B 同时购买同一件商品,且库存只有一件。如果不使用事务的隔离性,A 和 B 同时发起购买请求时,在没有互斥控制的情况下,可能会导致库存扣减错误。例如,A 和 B 同时读取库存数量为 1,然后都进行库存扣减,最终库存可能会变成 -1。 为了解决这个问题,可以使用事务的隔离性。通过将库存扣减操作放在一个事务中,并使用合适的隔离级别,可以避免并发访问时的数据不一致问题。例如,使用数据库的隔离级别 SERIALIZABLE,可以确保同一时间只有一个事务能够访问和修改库存数据,从而避免库存错误问题。

Q3:MySQL怎么保证原子性?

保证原子性,就要在异常发生的时候对已经执行的操作进行回滚,在MySQL中,恢复机制是通过回滚日志实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中出现异常,就利用回滚日志中的信息将数据回滚到修改之前的样子。并且,回滚日志会先把数据持久化到硬盘上,这样就能保证数据库在事务执行过程中宕机,未完成的事务会自动回滚。当再次打开数据库时,数据库会通过回滚日志来查找未完成的事务,并对其进行回滚操作,以保证数据的一致性。

到此这篇关于Spring question问题小结的文章就介绍到这了,更多相关Spring question内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

阅读全文