1、简述一下Spring IOC和AOP
(1)Spring IOC
IOC是指将对象的创建和依赖关系的管理交给Spring容器来处理。
IOC控制反转通常通过依赖注入来实现,这可以通过XML配置或者注解来完成。
IOC可以帮助开发者减少代码的复杂性,提高模块之间的解耦,使得代码更加灵活和可维护。
(2)Spring AOP
AOP允许开发者将横切关注点(如日志、事务管理等)与业务逻辑分离,从而提供更好的模块化。
在Spring中,AOP可以通过动态代理或者字节码操作来实现,常用的是动态代理。
AOP可以提高代码的重用性,使得横切关注点的修改更加集中和方便。
2、简述一下Spring IOC的工作流程
- 加载配置文件:Spring容器启动时,会加载xml配置文件,并解析其中的bean定义;
- 实例化bean:根据bean定义,spring会创建bean的实例;
- 依赖注入:Spring容器会根据bean定义中的依赖关系,将依赖的bean注入到当前bean中。
- 调用初始化方法:如果bean实现了InitializingBean接口或者在bean定义中配置了init-method属性,Spring容器会调用相应的初始化方法。
- 发布事件:如果bean实现了ApplicationListener接口或者在bean定义中配置了监听的事件,Spring容器会发布相应的事件。
- 调用销毁方法:如果bean实现了DisposableBean接口或者在bean定义中配置了destroy-method属性,Spring容器会在容器关闭时调用相应的销毁方法。
3、简述一下Spring AOP的工作流程?
Spring AOP(Aspect Oriented Programming)的工作流程主要涉及到面向切面编程的核心概念,如切面、连接点和通知等。以下是Spring AOP的基本工作流程:
- 定义切面:切面是织入到目标类中的功能代码,通常包括前置通知、后置通知、环绕通知、异常通知和最终通知等。这些通知可以根据业务需求进行自定义实现。
- 确定连接点:连接点表示切面将会被织入到目标对象的哪个位置。在Java中,这通常是某个方法的执行点或者某个构造器的创建点。
- 配置切入点:切入点是连接点的子集,用于更精确地定义哪些连接点需要被织入切面代码。
- 读取切面配置:在Spring容器启动时,会读取所有的切面配置,包括切入点的配置。
- 初始化bean:在bean初始化过程中,Spring会检查bean对应的类中的方法是否匹配到任意切入点。如果匹配成功,则会在该方法上织入相应的切面代码。
- 代理对象创建:Spring AOP通过动态代理技术(如JDK动态代理或CGLIB代理)为目标对象创建代理对象。代理对象会拦截对目标方法的调用,并根据需要执行相应的通知代码。
- 方法调用:当应用程序调用代理对象的方法时,实际上会触发AOP框架的执行流程。首先,会执行前置通知(如果有的话),然后调用目标方法,接着执行后置通知(如果有的话)。在方法执行过程中,如果出现异常,会执行异常通知。最后,执行最终通知(如果有的话)。
- 通过这个工作流程,Spring AOP能够在不修改原有业务代码的前提下,对多个目标对象进行统一管理,并增加额外的功能,如日志记录、事务管理、权限验证等。这使得代码更加模块化、可维护,并提高了系统的灵活性和可扩展性。
4、Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 是 Spring Framework 提供的一种 AOP 实现方式,是基于动态代理实现的。它允许在方法调用前、方法调用后、抛出异常时等切点进行切入通知,并通过动态代理技术织入切面。Spring AOP 只能在方法级别上生效。
AspectJ AOP 是一个独立的 AOP 框架, 是基于字节码操作实现的。它提供了比 Spring AOP 更为强大和灵活的 AOP 功能。AspectJ 可以在方法调用前、方法调用后、抛出异常时等切点进行切入通知,并且还支持构造器、字段、对象初始化和异常处理等更多切点。AspectJ 可以通过编译器织入(AspectJ编译器),也可以使用代理织入(在运行时为目标对象创建代理,称为 Spring AOP 的 AspectJ 代理模式)。
简而言之,如果需要更强大的功能和更好的性能,可以选择使用 AspectJ AOP;如果只需要简单的切面编程并且希望保持代码的简单性,可以选择使用 Spring AOP。
5、Spring AOP有哪些应用场景?
- 日志记录:可以使用AOP在方法执行前、执行后或者抛出异常时记录方法的执行日志,包括方法的入参、返回值、执行时间等信息。
- 事务管理:通过AOP实现声明式事务管理,对事务的开启、提交、回滚等进行统一管理,提高事务控制的简洁性和可维护性。
- 安全检查:AOP可以用于在方法调用前进行安全性检查,例如对用户权限的验证或者身份认证。
- 性能监控:利用AOP可以在方法执行前后统计方法的执行时间、调用频率等信息,用于性能监控和分析。
- 异常处理:AOP可以用于捕获方法执行过程中抛出的异常,并进行统一的处理、记录或通知。
- 缓存控制:通过AOP可以实现对方法的返回值进行缓存,从而提高系统的性能。
- 参数校验和转换:Spring AOP可以在方法调用前对方法的参数进行校验和转换,以确保参数的有效性和符合业务要求。这有助于减少因参数错误导致的异常和错误。
- 事件驱动编程:AOP可以用于实现事件发布和订阅机制,实现事件驱动编程。
6、Spring AOP的两种代理方式
Spring AOP 提供了两种代理方式:JDK 动态代理和 CGLIB 动态代理。
(1)JDK 动态代理
基于接口的代理,要求目标类实现一个接口,通过接口生成代理对象。这种方式的优点是性能较好,但缺点是只能代理接口,不能代理类。
(2)CGLIB 动态代理
基于类的代理,不要求目标类实现接口,直接对类进行代理。这种方式的优点是可以代理类和接口,但缺点是性能相对较差。
7、JDK动态代理有什么优点和缺点?
(1)优点:
相对于 CGLIB 动态代理,JDK 动态代理的性能较好,因为它是基于接口的代理,生成的代理类较少,使用起来比较简单,运行时开销较小。
(2)缺点:
- 只能代理接口:JDK 动态代理要求目标类必须实现一个接口,不能直接代理类。这限制了它的使用范围,对于没有实现接口的类,无法使用 JDK 动态代理。
- 代理类有限:JDK 动态代理生成的代理类数量有限,当目标类实现多个接口时,会为每个接口生成一个代理类,可能导致生成大量的代理类。
- 不支持类成员代理:JDK 动态代理无法对类的成员变量进行代理,只能在方法调用时进行拦截处理。
8、CGLIB 动态代理是如何工作的?
- 创建子类:CGLIB 动态代理通过创建目标类的子类来实现代理。当目标类没有实现接口或者无法使用 JDK 动态代理时,CGLIB 会创建一个目标类的子类,并覆写其中的方法。
- 覆写方法:在子类中覆写目标类的方法,这使得在目标方法被调用时,可以先执行相应的增强逻辑。
- 代理对象的创建:CGLIB 动态代理通过创建目标类的子类对象来实现代理。这个生成的子类对象就是代理对象,当调用代理对象的方法时,代理对象会委派给增强逻辑。
与 JDK 动态代理相比,CGLIB 动态代理不要求目标类必须实现接口,因此更加灵活,但代理对象的创建过程相对更为耗时。
9、Spring依赖注入是如何实现的?
Spring的依赖注入(Dependency Injection,简称DI)是通过Java的反射机制实现的。在Spring中,你可以使用XML配置文件或注解的方式,定义Bean之间的依赖关系,然后由Spring容器在运行时将这些依赖关系注入到相应的Bean中。
- 构造器注入:通过在类的构造函数中传入依赖对象,实现依赖注入。
- Setter 方法注入:通过调用类的 Setter 方法设置依赖对象,实现依赖注入。
- 注解注入:Spring 3.0以后引入了注解(Annotation),通过@Autowired,@Resource等注解可以实现自动装配,Spring可以智能地自动装配可以匹配的Bean。
在所有这些方式中,Spring都使用了Java的反射机制来动态地创建Bean的实例,并设置其属性或调用其构造函数。反射机制允许Spring在运行时获取类的信息,包括类的构造函数、方法、属性等,然后根据配置信息动态地创建和配置Bean。
10、Spring有哪些重要的模块?
- Spring Core Container:Spring核心容器,包括BeanFactory和ApplicationContext。它们提供了依赖注入和面向切面编程(AOP)功能,以及对不同应用层(如Web应用或基于数据的应用)的支持。
- Spring AOP:Spring的面向切面编程(AOP)模块,提供了将横切关注点(如日志、性能监控、事务管理等)从业务逻辑中分离出来的功能。
- Spring ORM:Spring对各种ORM(对象关系映射)框架的支持,包括JDBC、Hibernate、JPA、MyBatis等。这个模块提供了在Spring应用中集成持久化框架的功能。
- Spring Web:Spring Web模块包括Spring MVC和其他与Web开发相关的工具和类。Spring MVC是Spring框架的Web应用程序开发框架,用于构建灵活的Web应用程序。
- 数据访问与集成:提供与数据库交互的工具和库,包括JDBC、ORM、OXM、JMS等。
- Spring Test:提供了对单元测试和集成测试的支持,包括对Junit和TestNG的集成。
- Transaction Management事务管理:提供了事务管理的解决方案,可以方便地实现声明式事务管理。
- Security安全:提供了一套完整的安全框架,包括认证、授权、攻击防护等。
11、Spring 有几种配置方式?
(1)XML配置
通过编写 XML 文件来配置 Spring 应用程序的组件、依赖项和行为。
(2)注解配置
使用注解(如 @Component, @Autowired, @Configuration 等)来配置应用程序的部分或全部组件,以及它们之间的依赖关系
(3)Java 配置
使用纯 Java 代码配置 Spring 应用程序的组件、依赖项和行为,不需要 XML 文件。通常使用 @Configuration 和 @Bean 注解来实现。
12、请解释 Spring Bean 的生命周期?
- 加载配置文件:Spring容器加载配置文件,解析配置信息。
- 实例化Bean:根据配置文件中定义的Bean,Spring容器实例化Bean。
- 设置Bean属性:Spring容器为实例化的Bean注入属性值。
- BeanPostProcessor的前置处理(postProcessBeforeInitialization):如果Bean实现了BeanPostProcessor接口,Spring容器将在初始化方法调用之前调用postProcessBeforeInitialization方法。
- 初始化Bean:如果Bean实现了InitializingBean接口,Spring容器将调用其afterPropertiesSet()方法进行初始化。如果在配置文件中通过init-method指定了初始化方法,Spring容器将调用指定的初始化方法。
- BeanPostProcessor的后置处理(postProcessAfterInitialization):如果Bean实现了BeanPostProcessor接口,Spring容器将在初始化方法调用之后调用postProcessAfterInitialization方法。
- 使用Bean:Bean可以被容器管理组件使用。
- 销毁Bean:如果Bean实现了DisposableBean接口,Spring容器将调用其destroy()方法进行销毁。如果在配置文件中通过destroy-method指定了销毁方法,Spring容器将调用指定的销毁方法。
13、@Autowired和Spring的生命周期有什么关系?
- 实例化Bean:当 Spring 容器创建 Bean 的实例时,会检查该 Bean 是否使用了 @Autowired 注解。如果使用了该注解,Spring 会自动解析并注入所依赖的 Bean 到该 Bean 中。
- 设置Bean属性:在 Bean 实例化之后,Spring 会根据配置文件或者注解信息,将依赖的属性值注入到 Bean 中。如果使用了 @Autowired 注解,Spring 会自动解析并注入所依赖的 Bean 到该 Bean 中。
- 初始化Bean:在属性注入完成后,Spring 会调用 Bean 的初始化方法。这包括实现 InitializingBean 接口的 afterPropertiesSet 方法或者配置的 init-method 方法。
- 销毁Bean:当容器关闭或者 Bean 不再需要时,Spring 会调用 Bean 的销毁方法。这包括实现 DisposableBean 接口的 destroy 方法或者配置的 destroy-method 方法。在这个阶段,可以进行资源的释放等清理工作。
14、在Spring框架中,我使用了FileSystemXmlApplicationContext来注入bean,但是当我尝试使用@Autowired注解来获取这个bean时,却无法成功。请问这是什么原因?
当您使用FileSystemXmlApplicationContext来加载XML配置文件时,虽然您可以成功地创建和管理Bean,但是这些Bean不会被自动注入到Spring容器中进行托管。因此,当您尝试使用@Autowired注解来获取这些Bean时,Spring容器无法找到这些Bean并将它们注入到目标对象中。
解决方法是将通过FileSystemXmlApplicationContext加载的Bean手动注册到Spring容器中。
具体步骤如下:
- 在Spring配置文件中使用FileSystemXmlApplicationContext加载XML配置文件,并创建Bean实例。
- 将创建的Bean实例手动注册到Spring容器中,例如使用ConfigurableBeanFactory的registerSingleton方法。
- 在使用Autowired注解的地方,Spring容器就能够正确地找到并注入这些Bean。
15、什么是FileSystemXmlApplicationContext,它是如何工作的,以及在什么情况下我们会使用它?
FileSystemXmlApplicationContext是Spring框架中的一个类,它用于从文件系统加载XML配置文件并创建应用程序上下文。它是AbstractApplicationContext的一个具体实现,专门用于处理XML配置文件。
工作原理:
- 初始化:当创建一个FileSystemXmlApplicationContext实例时,你需要提供一个XML配置文件的路径。这个路径可以是相对于当前工作目录的相对路径,也可以是绝对路径。
- 解析XML:FileSystemXmlApplicationContext会读取和解析指定的XML配置文件,从中提取bean定义和其他配置信息。
- 创建Bean:根据XML中的bean定义,FileSystemXmlApplicationContext会创建和管理这些bean的生命周期,包括依赖注入、作用域等。
- 提供服务:一旦所有的bean都被创建和管理,你就可以通过getBean()方法来获取和使用这些bean。
使用场景:
当你有一个XML配置文件,并且希望在启动应用程序时立即加载它时,可以使用FileSystemXmlApplicationContext。
如果你的应用程序需要从文件系统中加载多个XML配置文件,你可以使用FileSystemXmlApplicationContext来加载它们。
public static void main(String[] args) {
// 创建一个FileSystemXmlApplicationContext实例,加载XML配置文件
ApplicationContext context = new FileSystemXmlApplicationContext("conf/config.xml");
// 从ApplicationContext中获取bean
UserService userService = (UserService) context.getBean("userService");
// 调用bean的方法
userService.execute();
}
在这个例子中,我们首先创建了一个FileSystemXmlApplicationContext实例,并指定了XML配置文件的路径。然后,我们从ApplicationContext中获取了一个名为"userService"的bean,并调用了它的execute()方法。
16、FileSystemXmlApplicationContext是如何从文件系统中加载XML配置文件的?
- 实例化:通过传入一个或多个XML配置文件的路径来创建一个FileSystemXmlApplicationContext实例。
- 刷新容器:调用refresh()方法来启动Spring容器。
- 解析XML:FileSystemXmlApplicationContext使用内部的XML解析器来读取和解析XML文件。
- 注册BeanDefinitions:解析完成后,FileSystemXmlApplicationContext会将解析出的bean定义注册到内部的数据结构中,这样Spring容器就能够管理这些bean的生命周期。
- 处理其他配置:除了bean定义之外,FileSystemXmlApplicationContext还会处理XML中的其他配置,如AOP设置、事务管理等。
17、FileSystemXmlApplicationContext与ClassPathXmlApplicationContext有何区别?
FileSystemXmlApplicationContext和ClassPathXmlApplicationContext都是Spring容器在加载XML配置文件时使用的接口实现类,它们之间的主要区别在于加载配置文件的方式和路径。
FileSystemXmlApplicationContext是通过文件系统加载XML配置文件的方式来初始化Spring容器。
ClassPathXmlApplicationContext是通过类路径(class path)加载XML配置文件的方式来初始化Spring容器。
18、什么是ApplicationContextAware接口,它的功能和用途是什么?
ApplicationContextAware 是 Spring 框架中的一个接口,它允许实现该接口的类能够访问到当前的 ApplicationContext。换句话说,任何实现了 ApplicationContextAware 接口的类都可以获得Spring容器中的bean引用。
public class MyService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
19、Spring中@Autowired和@Resource的区别?
- @Autowired是Spring的注解,@Resource是Java的注解;
- @Autowired默认通过byType方式注入,@Resource默认通过byName方式注入;
- @Autowired可以用于字段、构造方法和方法上,@Resource可以用于字段和set方法上;
- @Autowired在有多个相同类型的bean时,需要结合@Qualifier注解来精确注入,@Resource在有多个相同类型的bean时,会根据名称进行匹配,如果找不到对应的bean则会报错。
20、@Autowired和@Resource在实际项目中如何选择使用?
如果需要精确注入某个特定名称的bean,那么可以选择@Resource,因为它会根据名称进行匹配。
如果需要在多个相同类型的bean中选择一个进行注入,那么可以选择@Autowired,并结合@Qualifier注解来指定具体的bean。
21、Spring Bean 的作用域之间有什么区别?
在Spring框架中,Bean的作用域定义了Bean实例的生命周期和可见范围。Spring框架提供了以下五种主要的Bean作用域:
- Singleton(单例):在整个应用程序中,只有一个Bean实例被创建并被共享。每次请求该Bean时,都会返回相同的实例。
- Prototype(原型):每次请求该Bean时,Spring容器都会创建一个新的Bean实例。因此,每次获取Bean时都会得到不同的实例。
- Request(请求):每个HTTP请求都会创建一个新的Bean实例,该Bean实例仅在该HTTP请求范围内可见。
- Session(会话):每个HTTP会话期间只存在一个Bean实例,该Bean实例仅在该HTTP会话期间可见。
- Global Session(全局会话):在基于Portlet的Web应用中,全局会话作用域类似于HTTP会话作用域,但仅适用于Portlet上下文。
这些作用域之间的主要区别在于Bean实例的生命周期、创建方式以及可见范围。选择合适的Bean作用域取决于应用程序的需求和设计。
22、为什么Spring中每个bean都要有作用域?
作用域规定了bean实例的生命周期和在容器中的存储方式。作用域确定了bean实例的创建、初始化和销毁方式,以及在应用程序中使用这些bean的方式。
- 通过定义作用域,可以控制bean实例的生命周期;
- 作用域也决定了bean实例之间的状态共享方式;
- 通过定义作用域,可以更好地管理内存和其他资源。
23、什么是 Spring inner beans?
Spring内部bean是指在另一个bean的内部定义的bean。这些内部bean的作用域受限于包含它们的外部bean,因此它们不能被应用程序的其他部分所访问。内部bean适合于那些只在外部bean中使用的小型、私有的bean。在XML配置文件中,内部bean通常作为外部bean的属性进行定义。
package com;
public class Customer {
private Person person;
}
class Person{
private int id;
private String name;
private int age;
}
在这个例子中,"person"是一个内部bean,它被嵌套在"CustomerBean"的属性中。内部bean的生命周期受外部bean控制,并且外部bean销毁时,内部bean也会被销毁。 inner beans的使用可以帮助简化配置文件,并在外部bean范围内限制bean的可见性。
24、Spring 框架中的单例 Beans 是线程安全的么?
Spring 框架本身并不保证单例Beans的线程安全性,因为线程安全性通常取决于Bean的实现方式,而不是容器本身。因此,开发者需要自行确保他们的单例Beans是线程安全的。
有几种常见的策略可以帮助确保单例Beans的线程安全性:
- 无状态Bean:如果Bean本身是无状态的,即它不包含任何可变的实例变量,那么它自然就是线程安全的。这种Bean只包含方法,这些方法通常只依赖于传入的参数,并且不修改任何内部状态。
- 同步方法:如果Bean的某些方法需要访问和修改共享资源,可以使用synchronized关键字来同步这些方法。这样可以确保在任意时刻只有一个线程能够执行这些方法。
- 使用并发集合:如果Bean包含集合类型的属性,并且这些集合需要在多个线程之间共享,那么应该使用Java提供的并发集合类(如ConcurrentHashMap、CopyOnWriteArrayList等),而不是普通的集合类。
- 使用ThreadLocal:对于某些需要在多个线程之间保持独立状态的场景,可以使用ThreadLocal来存储线程特定的数据。这样,每个线程都会有自己独立的数据副本,从而避免了线程间的数据竞争。
- 依赖注入:避免在Bean中直接创建和共享其他Bean的实例。相反,应该使用Spring的依赖注入功能来注入所需的依赖项。这样,Spring可以管理这些依赖项的生命周期和线程安全性。
总之,虽然Spring框架本身不保证单例Beans的线程安全性,但开发者可以通过上述策略来确保他们的Bean在多线程环境中能够正常工作。
25、请解释 Spring Bean 的自动装配?
Spring Bean的自动装配是指Spring容器根据预先定义的规则,自动在Spring应用程序上下文中将Bean与其他Bean进行关联。这样可以避免手动配置Bean之间的依赖关系,从而简化了应用程序的配置。
Spring提供了以下几种自动装配的模式:
- no:默认模式,不自动装配,需要手动指定依赖注入。
- byName:根据Bean的名称自动装配,Spring容器会自动将与属性名相同的Bean注入到属性中。
- byType:根据Bean的类型自动装配,Spring容器会自动将与属性类型相同的Bean注入到属性中。如果存在多个匹配的Bean,则会抛出异常。
- constructor:类似byType,但适用于构造函数参数的自动装配。
使用自动装配可以减少配置工作,并且更易于维护。然而,过度依赖自动装配也可能导致代码不够清晰,因此需要根据具体情况进行合理的选择。
26、如何开启基于注解的自动装配?
- 导入context依赖:确保您的项目中包含了Spring的context依赖,这是使用注解进行自动装配的基础。
- 启用注解驱动:在Spring的配置文件中,需要开启注解的支持。这通常是通过在XML配置文件中添加context:annotation-config/标签来实现的。
- 使用注解标识:使用注解@Autowired 来自动装配Bean,@Component 用于标识自动扫描的Spring组件(如Bean),@Service、@Repository、@Controller等用于分别标识服务、存储库、控制器等特定类型的组件。
- 配置组件扫描:为了让Spring能够扫描到使用了注解的类,需要在配置文件中开启组件扫描,可以通过来指定扫描的包路径。
- 使用@SpringBootApplication注解:如果您使用的是Spring Boot,那么可以使用@SpringBootApplication注解,它是一个组合注解,包含了@Configuration、@EnableAutoConfiguration和@ComponentScan三个注解,分别用于声明配置类、开启自动装配和开启组件扫描。
27、FileSystemResource 和 ClassPathResource 有何区别?
(1)加载方式不同
FileSystemResource 是从文件系统路径加载文件,而 ClassPathResource 是从类路径(classpath)中加载文件。
(2)适用场景不同
FileSystemResource 适用于加载本地文件系统中的文件,而 ClassPathResource 适用于加载应用程序内部的资源文件,如配置文件、模板等。
(3)使用方式不同
FileSystemResource 需要提供文件的绝对路径或相对路径,而 ClassPathResource 只需要提供资源文件的相对路径即可。
28、如何向 Spring Bean 中注入一个 Java.util.Properties?
(1)使用标签
在XML配置文件中定义一个util:properties元素来创建一个Properties对象,然后将其注入到Bean中。
(2)使用@Value注解
如果你正在使用Java配置或者希望直接在代码中使用注解,那么可以使用@Value注解来注入Properties。但请注意,@Value注解主要用于注入单个属性值,而不是整个Properties对象。然而,你可以通过Spring的@ConfigurationProperties注解或者@PropertySource和Environment类来注入整个Properties对象。
29、Spring中BeanFactory和FactoryBean的区别?
BeanFactory是Spring框架中的一个核心接口,它主要用于管理和提供应用程序中的Bean实例。BeanFactory接口定义了Spring容器的基本规范和行为,它提供了一种机制来将配置文件中定义的Bean实例化、配置和管理起来。它负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory的主要作用是提供Bean的创建、配置、初始化和销毁等基本操作,它可以根据配置文件或注解来创建并管理Bean实例,并提供了各种方法来获取和操作Bean实例。
FactoryBean接口定义了一种创建Bean的方式,它允许开发人员在Bean的创建过程中进行更多的自定义操作。通过实现FactoryBean接口,开发人员可以创建复杂的Bean实例,或者在Bean实例化之前进行一些额外的逻辑处理。
30、BeanFactory 和 ApplicationContext 有什么区别?
ApplicationContext则是BeanFactory的子接口,它提供了比BeanFactory更完整的功能。除了继承BeanFactory的所有功能外,ApplicationContext还提供了国际化、资源文件访问、监听器注册等功能。
(1)初始化方式
BeanFactory 是延迟初始化,即在真正需要使用到某个 Bean 时才会创建该 Bean。ApplicationContext 则是在容器启动时就会预先加载所有的 Bean,所以它是预初始化。
(2)注册方式
BeanFactory 的注册必须要在配置文件中指定,而 ApplicationContext 可以通过注解的方式自动扫描并注册 Bean。
(3)生命周期管理
ApplicationContext 会管理 Bean 的完整生命周期,包括创建、初始化、销毁等过程。而 BeanFactory 则只负责创建和管理 Bean 实例,不会对 Bean 进行生命周期管理。
31、Spring有几级缓存,都有什么特点?
Spring框架中有三级缓存。
(1)一级缓存
Spring中最基本的缓存,用于存放完全初始化并确定类型的Bean实例。当一个Bean被创建并完成所有的初始化过程后,它会被转移到这个缓存中。这个缓存是对外提供服务的主要缓存,当我们通过Spring获取一个Bean时,首先会从这个缓存中查找是否有现成的对象。
(2)二级缓存
存放的是已经实例化但未初始化的bean。
保证了在多次循环依赖时,同一个类只会被构建一次,从而确保了单例性质。
(3)三级缓存
用于存储用于创建单例bean的ObjectFactory。
当依赖的bean实例创建完成后,Spring会使用这个ObjectFactory来创建bean实例,并从三级缓存中移除。
三级缓存的主要作用是解决循环依赖问题,特别是当涉及到AOP代理时。通过将代理的bean或普通bean提前暴露,使得依赖注入成为可能。
32、Spring为何需要三级缓存解决循环依赖,而不是二级缓存?
先了解一下什么是循环依赖?
当两个或多个bean相互依赖,形成一个闭环时,就发生了循环依赖。
二级缓存存储了尚未完成初始化的bean实例。当Spring检测到循环依赖时,它可以将正在创建的bean放入二级缓存中,以便其他bean可以引用它。然而,二级缓存并不能解决所有循环依赖问题,特别是当涉及到AOP代理时。
AOP(面向切面编程)是Spring框架的一个重要特性,它允许开发者在不修改现有代码的情况下,为应用程序添加新的行为。Spring AOP通常通过创建代理对象来实现这一点,这些代理对象在运行时增强目标对象的功能。
在循环依赖的场景中,如果涉及的bean需要被AOP代理,那么仅仅使用二级缓存是不够的。因为二级缓存中的bean可能还没有被AOP框架处理过,也就是说,它们可能还不是最终的代理对象。如果其他bean引用了这些未处理的bean,就会导致错误。
三级缓存就是为了解决这个问题而引入的。它存储的不是实际的bean实例,而是创建这些bean的工厂对象(ObjectFactory)。当Spring检测到循环依赖时,它会将ObjectFactory放入三级缓存中。这个工厂对象知道如何创建和(如果需要的话)代理目标bean。一旦循环依赖被解决,Spring就可以使用这个工厂对象来创建和返回最终的bean实例。
通过这种方式,三级缓存不仅解决了普通的循环依赖问题,还解决了涉及AOP代理的复杂循环依赖问题。它允许Spring在bean完全初始化(包括AOP代理)之前暴露引用,从而打破了循环依赖的限制。
因此,虽然二级缓存可以解决一些循环依赖问题,但三级缓存提供了更强大和灵活的解决方案,特别是当涉及到AOP代理时。
33、Spring中的事务传播行为有哪些?
Spring中的事务传播行为定义了七种类型,分别是:
PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常见的选择。
PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行操作。
PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则与PROPAGATION_REQUIRED类似。
这些传播行为可以通过@Transactional注解的propagation属性来设置,用于控制业务方法的事务行为。
34、导致Spring事务失效的原因有哪些?
(1)对@Transactional理解不深入或使用不当
开发者如果对@Transactional注解的工作原理和使用方式理解不深入,或者在使用时存在误解,也可能导致事务失效。
(2)方法没有被public修饰
在Spring中,如果@Transactional注解添加在不是public修饰的方法上,事务就会失效。因为Spring的事务是通过AOP代理实现的,而AOP代理需要目标方法能够被外部访问,所以只有public方法才能被代理。
(3)类没有被Spring托管
如果事务方法所在的类没有被加载到Spring IoC容器中,即该类没有被Spring管理,那么Spring就无法实现代理,从而导致事务失效。
(4)不正确的异常捕获
如果事务方法抛出的异常被catch处理,那么@Transactional注解无法感知到异常,因此无法回滚事务,导致事务失效。
(5)传播行为配置错误
如果内部方法的事务传播类型被配置为不支持事务的传播类型,那么该方法的事务在Spring中就会失效。
(6)异步
如果在事务方法中调用了异步方法,那么异步方法中的事务可能会失效。
35、Spring中实现异步的方式有哪些?
- 使用@Async注解:通过在方法上添加@Async注解,可以将该方法声明为异步方法。Spring会自动创建一个代理对象,当调用该方法时,会在一个单独的线程中执行。
- 使用CompletableFuture:CompletableFuture是Java 8引入的一个类,用于表示异步计算的结果。在Spring中,可以通过CompletableFuture来创建异步任务。
- 使用ThreadPoolTaskExecutor:ThreadPoolTaskExecutor是Spring提供的一个线程池任务执行器,可以用来执行异步任务。
- 使用@Scheduled注解:@Scheduled注解可以用于定时任务,通过设置固定的时间间隔或者使用Cron表达式,可以实现定时执行异步任务。
- 使用WebAsyncTask:在Spring Web应用中,可以使用WebAsyncTask来实现异步处理。WebAsyncTask会在一个新的线程中执行,并且支持回调函数。
- 使用消息队列,消息队列是实现异步操作的一种常见方式。你可以将任务发布到消息队列,然后由后台消费者异步处理这些任务。
- 使用Spring WebFlux,Spring WebFlux基于Reactive Streams规范,允许你以非阻塞的方式处理请求和响应。虽然这主要用于Web应用程序,但它也可以用于其他需要异步处理的场景。
36、什么是 Spring Batch?
Spring Batch是一个轻量级、全功能且可扩展的开源批处理框架,用于处理大量数据操作的需求。它允许开发人员定义并运行大规模的批处理作业,涵盖了各种需求,包括数据迁移、ETL操作(抽取、转换、加载)、报表生成等。
Spring Batch提供了丰富的功能,包括错误处理、事务管理、并发处理、监控和跟踪等,使得开发人员能够轻松构建可靠、高性能的批处理作业。通过使用配置简单和易于扩展的Spring Batch框架,可以减少开发成本和时间,并且易于维护。
Spring Batch框架由多个重要组件组成,包括Job、Step、ItemReader、ItemProcessor和ItemWriter等。开发人员可以使用这些组件来定义和配置批处理作业,以满足特定的需求。同时,Spring Batch提供了丰富的支持,能够与其他Spring框架(如Spring Boot、Spring Integration)及其他企业级技术(如JDBC、JMS、Hadoop、NoSQL数据库)集成,进一步提升了批处理作业的灵活性和适用性。
37、如何在Spring中实现定时任务?
在Spring中实现定时任务,可以使用@Scheduled注解。以下是一个简单的示例:
首先,在Spring配置文件中开启定时任务支持,添加task:annotation-driven/标签:
创建一个定时任务类,并在需要执行定时任务的方法上添加@Scheduled注解:
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyTask {
// 每隔5秒执行一次
@Scheduled(fixedRate = 5000)
public void doTask() {
System.out.println("执行定时任务");
}
}
38、说说@Required注解的作用?
@Required注解是Spring框架中的注解之一,用于标记Bean的属性在配置时必须进行注入的。当在Bean配置中使用了@Required注解标记的属性时,Spring在初始化Bean时会检查这些属性是否被正确注入,如果未被注入,则会抛出BeanInitializationException异常。
在Spring 5.x及更高版本中,@Required注解已经被废弃,因为它依赖于一些不推荐使用的特性。
推荐使用Java配置或XML配置中的required="true"属性来指定必需的属性。
推荐使用JSR-330中的@Inject或者Spring的@Autowired注解来代替@Required。这些注解在实现依赖注入时更加灵活,并且更容易用于各种场合。
39、Spring常用注解有哪些?应用场景都有什么?
以下是Spring框架中常用的一些注解及其应用场景:
@Component:用于声明一个通用的组件,是其他注解的基础。 @Controller:用于标记API层,即Web控制器。 @Service:用于标记业务逻辑层。 @Repository:用于标记数据访问层,通常用于DAO实现类。 @Autowired:用于自动装配Bean,可以按类型自动注入依赖。 @Resource:按照名称自动装配,与JNDI查找相关。 @Bean:标注在方法上,表示该方法返回的对象应该被注册为Spring容器中的Bean。 @Configuration:表明该类是一个配置类,可以包含@Bean注解的方法。 @ComponentScan:用于指定Spring容器启动时扫描的包路径,以便发现并注册带有特定注解的类。 @Value:用于将外部属性值注入到Bean中。 @Qualifier:与@Autowired一起使用,用于指定需要装配的Bean的名称。 @Scope:用于指定Bean的作用域,如singleton(单例)或prototype(原型)。 @Primary:用于指定当有多个相同类型的Bean时,优先选择哪个Bean进行装配。 @Transactional:用于声明事务管理,可以标注在类或方法上。 @RequestMapping:用于映射Web请求到特定的处理方法。 @GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping:分别用于处理HTTP的GET、POST、PUT、DELETE和PATCH请求。 @RequestBody:用于将请求体中的JSON数据绑定到方法参数。 @ResponseBody:用于将方法返回值写入响应体。 @PathVariable:用于从URL模板变量中提取参数。 @RequestParam:用于将请求参数绑定到方法参数。 @RequestHeader:用于将请求头信息绑定到方法参数。 @CookieValue:用于将Cookie信息绑定到方法参数。 @SessionAttribute:用于从会话中获取属性值。 @RequestAttribute:用于从请求中获取属性值。 @ModelAttribute:用于在控制器方法执行前添加模型属性。 @ExceptionHandler:用于处理方法中抛出的异常。 @ControllerAdvice:用于定义全局的异常处理类。 @RestController:是@Controller和@ResponseBody的组合注解,用于RESTful Web服务。 @CrossOrigin:用于支持跨域请求。 @MatrixVariable:用于处理矩阵变量。
40、REST风格的请求是什么?
在REST风格的请求中,核心概念包括资源(Resource)和RESTful API(Application Programming Interface)。资源可以简单理解为URI,表示一个网络实体,具有唯一标识符。客户端通过访问这些标识符来对资源进行操作。RESTful API则是使用HTTP协议的不同方法来实现与资源的交互。
客户端与服务器之间的交互通过HTTP协议进行,客户端不需要知道服务器的实现细节,只需要遵循HTTP协议。
- 无状态,服务器不会保存客户端的任何状态信息,每次请求都需要携带完整的请求信息,这降低了服务器的负担,也使得API更加可扩展。
- 统一接口,REST使用统一的接口,包括HTTP方法、URI、MIME类型等,使API的设计更加简单、清晰、易于理解。
- 分层系统,REST的架构是分层的,每个层有自己的责任和职能,这使得系统更加灵活、可扩展、易于维护。
41、如何防止表单重复提交?
(1)Token验证
在会话中生成一个唯一的Token,并将其嵌入到表单中。当表单被提交时,服务器检查该Token是否有效,并确保其只被使用一次。 一旦处理完请求,服务器应废弃该Token。
(2)按钮禁用(不推荐)
用户点击提交按钮后,立即将其禁用,以防止用户多次点击。
(3)页面重定向
提交成功后,将用户重定向到一个新的页面或消息提示页,这样用户就无法再次点击提交按钮了。
(4)时间限制
对表单提交设置时间间隔限制,例如不允许在一分钟内连续提交。
(5)验证码
对于敏感操作,要求用户输入验证码,这可以有效防止机器人或恶意软件的自动提交。
(6)数据库唯一性约束(简单粗暴)
如果重复提交会导致数据库中的数据冲突,可以在数据库字段上设置唯一性约束。
42、Spring中都应用了哪些设计模式?
(1)简单工厂模式
简单工厂模式的本质就是一个工厂类根据传入的参数,动态的决定实例化哪个类。
Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象。
(2)工厂方法模式
应用程序将对象的创建及初始化职责交给工厂对象,工厂Bean。
定义工厂方法,然后通过config.xml配置文件,将其纳入Spring容器来管理,需要通过factory-method指定静态方法名称。
(3)单例模式
Spring用的是双重判断加锁的单例模式,通过getSingleton方法从singletonObjects中获取bean。
(4)代理模式
Spring的AOP中,使用的Advice(通知)来增强被代理类的功能。Spring实现AOP功能的原理就是代理模式(① JDK动态代理,② CGLIB字节码生成技术代理。)对类进行方法级别的切面增强。
(5)装饰器模式
装饰器模式:动态的给一个对象添加一些额外的功能。
Spring的ApplicationContext中配置所有的DataSource。这些DataSource可能是不同的数据库,然后SessionFactory根据用户的每次请求,将DataSource设置成不同的数据源,以达到切换数据源的目的。
在Spring中有两种表现:
一种是类名中含有Wrapper,另一种是类名中含有Decorator。
(6)观察者模式
定义对象间的一对多的关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
Spring中观察者模式一般用在listener的实现。
(7)策略模式
策略模式是行为性模式,调用不同的方法,适应行为的变化 ,强调父类的调用子类的特性 。
getHandler是HandlerMapping接口中的唯一方法,用于根据请求找到匹配的处理器。
(8)模板方法模式
Spring JdbcTemplate的query方法总体结构是一个模板方法+回调函数,query方法中调用的execute()是一个模板方法,而预期的回调doInStatement(Statement state)方法也是一个模板方法。
43、请举例说明如何在 Spring 中注入一个 Java Collection?
在Spring中,可以使用XML配置或者注解来注入一个Java Collection。
(1)使用XML配置注入Java Collection
Item 1
Item 2
Item 3
(2)使用注解方式注入Java Collection
@Configuration
public class AppConfig {
@Bean
public List stringList() {
return Arrays.asList("Item 1", "Item 2", "Item 3");
}
}
在上面的示例中,我们使用@Configuration注解标注了一个配置类,并且在配置类中定义了一个返回List类型的方法,通过@Bean注解来将List实例注入Spring容器。
44、@Scheduled注解在Spring框架中的执行原理,包括它是如何工作的,以及它的主要组件和功能
(1)@Scheduled注解执行原理
Spring使用一个内部的任务调度器(TaskScheduler)来管理所有被 @Scheduled 注解的方法。
当Spring容器启动时,它会自动扫描所有的Bean,找到被 @Scheduled 注解的方法,并将它们注册到TaskScheduler中。
TaskScheduler会按照注解中指定的时间间隔或表达式来自动调用这些方法。
@Scheduled注解可以根据配置使用多线程执行,发生异常时,根据配置决定是重试还是直接跳过。
深入一点:
- Bean初始化:当Spring容器启动并初始化bean时,它会在bean的生命周期方法执行完成后,通过postProcessAfterInitialization钩子函数拦截所有使用了@Scheduled注解的方法。
- 解析注解参数:Spring会解析这些方法上的@Scheduled注解,包括cron表达式、fixedDelay、fixedRate等参数,以确定任务的执行时间和频率。
- 任务注册:解析后的任务会被注册到Spring的调度器中,这个调度器负责管理所有的定时任务。
- 定时任务执行:根据注解参数指定的规则,调度器会在适当的时间触发任务的执行。
(2)@Scheduled注解主要组件和功能
- cron: 使用Cron表达式来指定任务的执行时间。
- fixedDelay: 表示从上一次任务执行完毕到下一次任务开始的时间间隔(以毫秒为单位)。
- fixedRate: 表示两次任务开始之间的时间间隔(以毫秒为单位),不考虑上一次任务是否已经执行完毕。
- initialDelay: 表示首次执行任务前的延迟时间(以毫秒为单位)。
(3)常见用途和实际应用场景
- 定时任务调度: 执行定时任务,如每隔一段时间执行一次某个操作。
- 报表生成: 每天或每周定期生成报表。
- 数据备份: 定时将数据库中的数据备份到其他地方。
- 系统维护: 如清理临时文件、检查系统健康状况等。
- 定时提醒: 如发送定期通知、定时执行某项操作等。
45、请简述Spring MVC的执行流程
Spring MVC的执行流程遵循了前端控制器模式,通过DispatcherServlet协调各个组件来接收请求、匹配处理器、执行处理器、渲染视图的过程,实现了请求到视图的端到端处理。
具体执行流程如下:
- 客户端发送请求:客户端发送HTTP请求到服务器上部署的Spring MVC应用程序。
- DispatcherServlet接收请求:请求被DispatcherServlet(前端控制器)拦截,它是Spring MVC的核心组件之一,负责统一管理请求的处理过程。
- HandlerMapping匹配处理器:DispatcherServlet调用HandlerMapping来确定请求的处理器(Controller)。
- 执行处理器:确定了处理器之后,DispatcherServlet将请求转发给相应的Controller进行处理,Controller执行业务逻辑并返回ModelAndView对象。
- 视图解析和渲染:DispatcherServlet通过ViewResolver解析Controller返回的逻辑视图名,并选择合适的View进行渲染,最终将视图呈现给用户。
- HTTP响应:渲染后的视图作为HTML响应发送给客户端。
46、谈谈你对Spring MVC中九大组件的理解
Spring MVC是Spring框架的一个模块,用于构建基于MVC(Model-View-Controller)设计模式的Web应用程序。在Spring MVC中,有九大组件是非常重要的,它们分别是:
- DispatcherServlet:这是Spring MVC的核心,是一个前端控制器,负责接收用户请求并分发给相应的处理器。
- HandlerMapping:处理器映射,根据请求信息找到对应的处理器(Controller)。
- HandlerAdapter:处理器适配器,用于调用相应的处理器(Controller)的方法。
- Controller:控制器,处理由DispatcherServlet分发的请求。
- ViewResolver:视图解析器,解析逻辑视图名到真正的视图(如JSP)。
- View:视图,用于展示模型(Model)数据。
- ModelAndView:模型和视图,用于封装模型数据和视图信息。
- ModelMap:模型数据,用于存放模型数据,可以在视图中直接使用。
- LocaleResolver:区域解析器,用于解析用户的语言和区域设置。
这九大组件共同构成了Spring MVC的完整流程:
首先,DispatcherServlet接收用户请求,然后通过HandlerMapping找到对应的Controller,再通过HandlerAdapter调用Controller的方法,方法返回的ModelAndView包含了模型数据和视图信息,最后通过ViewResolver解析视图,并通过View展示模型数据。在这个过程中,还可以通过LocaleResolver来解析用户的语言和区域设置,以实现国际化。
47、Spring MVC有哪些常用注解?它们的作用是什么?
@Controller:用于标识一个类为Spring MVC的控制器,处理客户端的请求并返回相应的视图。
@RequestMapping:用于将URL路径映射到特定的处理方法,可用于类级别或方法级别,用于定义请求URL和处理请求的方法。
@ResponseBody:用于将方法的返回值直接作为HTTP Response的主体内容返回,通常用于返回 JSON 或 XML 格式的数据。
@PathVariable:用于将请求URL中的模板变量映射到处理方法的参数中。
@RequestParam:用于将请求参数映射到处理方法的参数中,可以指定参数名称、是否必须等。
@RequestParamMap:用于将所有的请求参数映射为一个Map类型的参数。
@ModelAttribute:用于将请求参数绑定到Model对象中,通常用于在视图渲染之前填充模型数据。
@SessionAttributes:用于指定处理方法的返回值需要存储到会话中的属性值。
@InitBinder:用于定制数据绑定规则和初始化数据绑定规则。
@Validated:用于标记其后的方法参数需要进行验证。
48、spring mvc 和 struts 的区别是什么?
(1)架构设计
Spring MVC是基于Servlet API构建的,而Struts2是通过Filter实现的。这意味着Spring MVC的入口是一个Servlet,而Struts2的入口是一个Filter。这导致了两者在处理请求时的不同机制。
(2)拦截器
Spring MVC 的拦截器机制通过 HandlerInterceptor 接口进行实现。通过实现这个接口,你可以在处理程序执行的前后插入逻辑,可以在请求处理之前或之后对请求进行预处理或后处理。拦截器可以用来实现日志记录、权限校验、国际化、主题切换等各种需求。
Struts 的拦截器机制是通过拦截器栈(interceptor stacks)实现的。在Struts2中,通过配置拦截器栈,可以在请求处理的各个阶段插入预处理逻辑或后处理逻辑。拦截器可以用于日志记录、权限校验、异常处理、输入验证等。
(3)配置方式
Spring MVC 的配置更加灵活,可以使用 XML 配置、Java 注解或者 Java 类配置(JavaConfig)来配置控制器、视图解析器等。
Struts 使用基于 XML 的配置文件来定义控制器(Action)、拦截器、结果视图等。
(4)扩展性和灵活性
Spring MVC强调依赖注入,通过Spring容器管理对象和组件,使得应用更加灵活和可测试。 Struts不提供像Spring那样的依赖注入机制,更多地依赖于配置文件,因此在灵活性方面可能稍逊于Spring MVC。
(5)性能
由于Spring MVC的轻量级设计,其代码量相对较少,运行时间也更快,因此在性能方面通常优于Struts2。
49、@RestController和@Controller有什么区别?
@RestController和@Controller是Spring MVC中常用的注解,它们的主要区别在于返回值的不同:
@Controller:用于标识控制器类的注解。在Spring MVC中,使用@Controller注解标记的类表示该类是控制器,可以处理客户端的请求,并返回相应的视图。
@RestController:是Spring4.0引入的一个组合注解,用于标识RESTful风格的控制器类。它相当于@Controller和@ResponseBody的组合,表示该类处理RESTful请求,方法的返回值直接作为HTTP Response的主体内容返回,而不是作为视图进行解析。
因此,@RestController注解在处理HTTP请求时,会将方法的返回值直接转换为JSON/XML等格式的数据返回给客户端,而@Controller注解一般用于传统的Web应用程序开发,将方法的返回值解析为视图进行渲染。
50、Spring MVC如何匹配请求路径?
在Spring MVC中,请求路径的匹配是通过DispatcherServlet和HandlerMapping来完成的。具体步骤如下:
- 客户端发送请求到服务器,请求首先到达DispatcherServlet。
- DispatcherServlet接收到请求后,会调用HandlerMapping(处理器映射)来找到对应的Controller(控制器)。
- HandlerMapping会根据请求路径(URI)和请求方法(GET、POST等)来查找匹配的Controller。这是通过配置文件或者注解来实现的。
- 如果找到匹配的Controller,HandlerMapping会返回一个HandlerExecutionChain对象,包含了处理器(Handler)、拦截器(Interceptors)和适配器(Adapter)。
- DispatcherServlet根据HandlerExecutionChain对象,依次调用拦截器、处理器和适配器来处理请求。
- 最后,将处理结果返回给客户端。
在Spring MVC中,可以通过多种方式来配置请求路径与Controller的映射关系,例如使用XML配置文件或者使用注解(如@RequestMapping)。
RequestMapping可以实现模糊匹配路径,比如:
- ?表示一个字符;
- *表示任意字符;
- **匹配多层路径;
51、Spring MVC中,在调用controller接口前都进行了哪些操作?
Spring MVC的执行流程:
- DispatcherServlet(前端控制器):所有的请求都会先到达前端控制器 DispatcherServlet,它是整个 Spring MVC 的核心。
- HandlerMapping(处理器映射器):DispatcherServlet 会根据请求的 URL 找到对应的 Handler(处理器),这个过程中会用到 HandlerMapping。
- Interceptors(拦截器),在请求到达 Handler 之前,可以设置一些拦截器对请求进行预处理,例如权限验证、日志记录等。
- Handler(处理器):这里指的是 Controller 接口,当请求到达 Controller 后,会执行相应的方法进行处理。
- ModelAndView(模型和视图):Controller 处理完请求后,会返回一个 ModelAndView 对象,其中包含了响应的数据和视图信息。
- ViewResolver(视图解析器):根据 ModelAndView 中的视图信息,通过 ViewResolver 解析出实际的视图对象。
- View(视图):将解析出的视图对象渲染成 HTML 页面,返回给客户端。
因此,DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、Interceptors(拦截器)就是在调用controller接口前进行的操作。
52、SpringMVC如何获取请求的参数?
(1)@RequestParam
最常见的方法,用于从请求参数中取值到控制器方法的参数上。
(2)@PathVariable
从URL路径中提取参数时,可以使用@PathVariable注解。
在这个例子中,当发送一个GET请求到/users/123时,123会被解析为id参数的值。
@Controller
public class MyController {
@GetMapping("/users/{id}")
public String getUser(@PathVariable Long id) {
// 使用id参数
return "user";
}
}
(3)@ModelAttribute
当请求参数与对象属性对应时,可以使用@ModelAttribute注解来绑定请求参数到JavaBean上。
当发送一个GET请求到/create?id=1&name=nezha&age=18时,请求参数会被绑定到User对象的属性上。
@Controller
public class MyController {
@GetMapping("/create")
public String create(@ModelAttribute User user) {
// 使用user对象中的属性
return "create";
}
public static class User {
private String id;
private String name;
private String age;
// getters and setters
}
}
(4)@RequestBody
当请求体中包含JSON或XML数据时,可以使用@RequestBody注解来自动解析请求体并绑定到方法参数上。
(5)HttpServletRequest
还可以直接通过HttpServletRequest对象来获取请求参数,尽管这不是Spring MVC推荐的方式,因为它没有利用Spring MVC的参数绑定特性。
在使用HttpServletRequest时,你需要自己处理参数的获取和类型转换。
@Controller
public class MyController {
@GetMapping("/oldWay")
public String oldWay(HttpServletRequest request) {
String query = request.getParameter("query");
// 使用query参数
return "oldWay";
}
}
53、mybatis 中 #{}和 ${}的区别是什么?
#{}:被称为占位符,在执行SQL语句时,通过PreparedStatement对参数值进行预处理,MyBatis会将#{}替换为?,然后将参数值传递给SQL语句,这样可以防止SQL注入攻击。当使用#{}时,MyBatis会自动处理参数的转义和引号等问题,确保参数作为一个完整的字符串被传递。
#{}主要用于替换 SQL 语句中的条件值,它不能直接用于替换 SQL 语句的片段或关键字,比如表名。
:称为变量替换,使用{}时,它在SQL语句中直接替换成相应的参数值,它会将参数值直接拼接到SQL语句中,因此存在SQL注入的风险。
${}:可以替换 SQL 语句中的任何部分,包括列名、表名、SQL 关键字等。
54、使用${}时,如何防止SQL注入?
(1)白名单校验
在将参数传递给SQL语句之前,对参数值进行验证,确保它们符合预期的格式或范围。这可以通过正则表达式、字符串比较或其他逻辑来实现。
避免敏感操作,比如DROP、TRUNCATE 等。
(2)转义特殊字符
对于可能引起注入的特殊字符,需要进行合适的转义处理,比如对单引号、双引号、分号等特殊字符进行转义,防止它们被误解为SQL命令的一部分。
(3)使用数据库权限控制
确保数据库用户只有执行必要操作的权限,避免给予过多的权限。这样即使发生了 SQL 注入攻击,攻击者也只能执行有限的操作。
(4)日志记录和监控
记录所有执行的 SQL 语句,并监控任何异常或可疑行为。这有助于及时发现并应对潜在的 SQL 注入攻击。
55、mybatis 是否支持延迟加载?延迟加载的原理是什么?
MyBatis支持延迟加载,它允许在需要时动态地加载与某个对象关联的数据。延迟加载可以帮助减少不必要的数据库查询,提高性能,并且提供了一种方便的方式来管理复杂对象之间的关联关系。
延迟加载的原理是,在查询主对象时,并不会立即加载关联对象的信息,而是在真正需要使用这些关联对象的时候再去发起对关联对象的查询。具体来说,延迟加载通常使用代理对象(Proxy)来实现。当主对象被查询并加载到内存中时,关联对象并没有被加载,而是创建一个代理对象来代替关联对象的位置。当应用程序实际使用关联对象的属性或方法时,代理对象会拦截这些调用,并触发对关联对象数据的实际加载查询,然后返回结果给应用程序。
在MyBatis中,延迟加载通常与二级缓存(二级缓存是一种全局性的缓存机制,可以跨多个会话对查询进行缓存)结合使用,可以延迟加载对象的时候首先尝试从二级缓存中获取数据,如果缓存中不存在再去查询数据库。
延迟加载可以提高查询性能,特别是在处理大量数据或者复杂关联查询的时候。但是,它也会增加一些额外的内存开销,因为需要创建代理对象,并且在访问数据时需要进行额外的数据库查询操作。因此,在使用延迟加载时需要根据具体的业务需求和性能要求进行权衡。
56、说一下 mybatis 的一级缓存和二级缓存?
(1)一级缓存
MyBatis的一级缓存是SqlSession级别的缓存,也就是说当我们执行查询之后,会将查询的结果放在SqlSession的缓存中。
对于同一个SqlSession,当执行相同的查询时,MyBatis会先查看一级缓存中是否有相同的查询结果,如果有,则直接返回缓存的结果,而不再去数据库中查询。
一级缓存是MyBatis默认开启的,它可以减少对数据库的访问,提高查询性能。
(2)二级缓存
MyBatis的二级缓存是Mapper级别的缓存,它可以跨SqlSession共享缓存数据。
二级缓存是在Mapper的映射文件配置开启的,我们可以在Mapper的映射文件中配置元素来开启二级缓存。
使用二级缓存时,要确保查询的 SQL 语句和参数是完全相同的,否则 MyBatis 会认为它们是不同的查询,从而不会从二级缓存中获取数据。
对于频繁更新的数据,不建议使用二级缓存,因为频繁的更新会导致缓存失效,反而降低性能。
57、mybatis 有哪些执行器(Executor)?
(1)简单执行器SimpleExecutor
SimpleExecutor是MyBatis默认的执行器,它对每个SQL语句的执行进行了封装,每次都会生成一个新的Statement对象,并执行SQL语句,SimpleExecutor适用于短时、简单的操作。
(2)重用执行器ReuseExecutor
ReuseExecutor是一种复用的执行器,它会在多次执行相同SQL语句时重用Statement对象,从而减少了Statement对象的创建和销毁,提升了性能。
(3)批处理执行器BatchExecutor
BatchExecutor是一种执行器,用于批量操作。当我们需要执行批量的SQL语句时,可以使用BatchExecutor来提高性能。BatchExecutor通过快速执行批量的SQL语句,来减少与数据库的交互次数,提高操作的效率。
58、mybatis中如何设置执行器?
(1)全局设置
Spring集成MyBatis时,执行器的设置通常是在Spring的配置文件中进行的。你可以通过配置SqlSessionFactoryBean的executorType属性来指定执行器类型。例如:
这个设置会影响所有的SqlSession,但不建议全局修改执行器类型,因为这可能会对性能产生不必要的影响。
(2)局部配置
当获取SqlSession对象时,可以指定所需的执行器类型。这种方式更加灵活,允许根据不同的操作需求选择不同的执行器。
SqlSession是MyBatis中用于执行数据库操作的核心接口,它提供了多种方法来执行SQL语句和映射操作。
通过SqlSessionFactory获取SqlSession对象时,可以配置执行器的类型。
使用SqlSessionFactory的openSession(ExecutorType)方法来获取指定类型的SqlSession。ExecutorType是一个枚举类型,包含了MyBatis支持的三种执行器:SIMPLE、REUSE和BATCH。
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.Reader;
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// 创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 获取指定执行器类型的SqlSession
// 使用SIMPLE执行器
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
// 或者使用REUSE执行器
// SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE);
// 或者使用BATCH执行器
// SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
// 使用sqlSession执行数据库操作...
// ...
} finally {
// 关闭SqlSession
sqlSession.close();
}
(3)xml中标签内设置
可以通过在映射文件中配置和标签来定义相应的执行器,在这些标签中可以针对具体的SQL语句配置执行器类型。
SELECT * FROM BLOG WHERE ID = #{id}
在上面的示例中,可以看到标签指定了该查询的执行器类型为“PREPARED”,也就是预处理执行器。
59、BatchExecutor 如何提高性能?
BatchExecutor 通过批处理的方式提高性能。它允许在 JDBC 客户端缓存多条 SQL 语句,然后在缓存满或手动刷新时,将这些语句打包一起发送到数据库执行。这种方式可以有效减少网络通信次数和数据库交互的开销,从而提高系统性能。
网络通信次数:通过一次发送多条 SQL 语句,减少了与数据库服务器之间的往返次数,从而减少了网络延迟的影响。数据库交互开销:数据库在处理批量请求时,可以优化执行计划,减少编译和准备时间,进一步提高执行效率。
60、mybatis如何防止sql注入
(1)使用参数化的SQL语句 在编写SQL语句时,应使用参数化的方式来构建SQL,而不是将用户输入的值直接拼接到SQL字符串中。这样可以确保用户输入的内容不会被解释为SQL命令。 #{}占位符会自动对输入值进行转义,可以有效防止SQL注入攻击。
SELECT * FROM users WHERE username = #{username}
(2)使用MyBatis的动态SQL MyBatis的动态SQL允许根据条件动态拼接SQL语句,可以在拼接SQL的过程中对用户输入的内容进行转义或其他处理,从而防止SQL注入攻击。
SELECT * FROM users AND username = #{username}
61、mybatis xml映射文件中,有哪些常用标签?
- :定义一个映射器,用于将 SQL 语句与 Java 方法关联起来。
- :定义一个结果映射,用于描述如何将查询结果集映射到 Java 对象。
- :定义一个可重用的 SQL 片段。
- :定义一个查询语句,用于从数据库中检索数据。
- :定义一个插入语句,用于向数据库中插入数据。
- :定义一个更新语句,用于修改数据库中的数据。
- :定义一个删除语句,用于从数据库中删除数据。
- :定义一个参数映射,用于描述如何将 Java 方法的参数传递给 SQL 语句。
- :定义一个刷新语句,用于清空或重置数据库中的缓存。
- :定义一个数据库配置,用于指定数据库连接信息。:定义一个缓存配置,用于设置查询结果的缓存策略。
- :定义一个关联映射,用于描述两个实体类之间的关联关系。
- :定义一个集合映射,用于描述一对多或多对多的关联关系。
- :定义一个鉴别器映射,用于处理继承关系中的类型判断。
- :定义一个子查询映射,用于在查询语句中使用子查询。
- :定义一个构造函数映射,用于在查询结果中使用构造函数创建对象。
- :定义一个修剪表达式,用于在动态 SQL 中控制 SQL 语句的生成。
- :定义一个条件表达式,用于在动态 SQL 中生成 WHERE 子句。
- :定义一个更新表达式,用于在动态 SQL 中生成 SET 子句。
- :定义一个循环表达式,用于在动态 SQL 中处理集合类型的参数。
62、Mybatis 动态 sql 有什么用?执行原理?有哪些动态 sql?
动态 SQL 的主要作用在于,根据运行时不同的条件,动态地生成不同的 SQL 语句,从而实现更灵活、更高效的数据库操作。
常见的动态 SQL 标签:
- :只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若子句的开头是“AND”或“OR”,元素也会将它们去除。
- :用于判断条件,如果满足条件,则包含其中的 SQL 片段。
- 、、:类似于 Java 中的 switch-case-default 结构,用于多条件判断。
- :可以自定义前缀和后缀的去除规则。
- :用于处理 SQL 语句中的 SET 子句,智能地处理逗号。
- :用于遍历集合,根据集合元素生成相应的 SQL 片段。
这些动态 SQL 标签大大增强了 MyBatis 的灵活性,使得开发者能够根据不同的业务逻辑,动态地构建 SQL 语句,从而提高了开发效率和代码的可维护性。
63、Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?
在 MyBatis 的 XML 映射文件中,不同的 XML 映射文件之间的 ID 是可以重复的。因为每个 XML 映射文件都是独立的,它们之间不会相互影响。但是在同一张 XML 映射文件中,每个元素中的 ID 必须是唯一的,不能重复。
64、MyBatis的接口绑定是如何工作的?
当 MyBatis 接收到 Java 接口方法的调用时,它会首先查找是否有与该方法相关的 SQL 映射语句。如果有,MyBatis 会解析该 SQL 语句,并根据传入的参数值动态地生成最终的 SQL 语句。然后,MyBatis 会执行这个 SQL 语句,并将查询结果映射回 Java 对象,最后返回给调用者。
在接口绑定中,MyBatis 提供了两种实现方式:
(1)使用元素
在 MyBatis 的全局配置文件(如 mybatis-config.xml)中,通过元素引入 SQL 映射文件,将映射文件与 Java 接口关联起来。
(2)使用注解
在 Java 接口方法上添加 @Select、@Insert、@Update、@Delete 等注解,直接编写 SQL 语句。
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(int id);
}
65、MyBatis如何处理参数映射?
(1)单个参数
当 SQL 语句中只有一个参数时,MyBatis 无需指定参数的类型或名称,直接使用 #{} 来引用参数。MyBatis 会自动将这个参数绑定到 SQL 语句中。
(2)多个参数
① 使用顺序:MyBatis 会按照参数的顺序进行绑定,可以使用 #{param1}、#{param2} 等来引用参数。
SELECT * FROM user WHERE username = #{param1} AND password = #{param2}
② 使用 @Param 注解:在 Mapper 接口的方法参数上添加 @Param 注解,为参数指定一个名称,然后在 XML 中使用这个名称来引用参数。
User selectUserByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
SELECT * FROM user WHERE username = #{username} AND age = #{age}
③ 使用 Map 或 JavaBean:将多个参数封装到一个 Map 或 JavaBean 中,然后在 XML 中引用 Map 的 key 或 JavaBean 的属性。
User selectUserByCriteria(Map criteria);
SELECT * FROM user WHERE username = #{username} AND age = #{age}
(3)复杂类型
对于复杂类型(如 JavaBean 或自定义类型),MyBatis 会自动映射这些类型的属性到 SQL 语句中。只需要在 XML 中使用 #{} 引用这些属性的名称即可。
public class UserCriteria {
private String username;
private int age;
// getters and setters
}
SELECT * FROM user WHERE username = #{username} AND age = #{age}
(4)使用 @Results 和 @Result 注解
对于结果集的映射,MyBatis 提供了 @Results 和 @Result 注解,用于在接口方法上直接定义结果集的映射关系,而无需编写 XML 映射文件。这些注解可以指定如何将数据库中的列映射到 Java 对象的属性上。
66、ibatis中如何实现Oracle批量入库?
insert all
into USER (
ID,
NAME,
SAVE_DATE
) values
select * from dual
67、mybatis 和 hibernate 的区别有哪些?
(1)SQL书写与灵活性
MyBatis:是一个半自动化的持久层框架,需要手动编写SQL语句和ResultMap。MyBatis支持注解配置和XML配置,可以在XML文件中编写动态SQL语句。
Hibernate:是一个全自动化的ORM框架,它会自动生成SQL语句,开发者无需关心SQL的生成与结果映射。
(2)性能与扩展性
MyBatis:由于SQL语句是手动编写的,所以可以对SQL进行精细的优化,提高性能。但是,由于所有SQL都是依赖数据库书写的,所以MyBatis的扩展性、迁移性相对较差。
Hibernate:支持多种数据库,通过配置可以方便地进行数据库切换。Hibernate使用反射机制实现持久化对象操作,使得业务层与具体数据库分开,降低了数据库之间迁移的成本。
(3)缓存机制
MyBatis:提供了一级缓存和二级缓存机制,但主要是依赖于开发者手动管理和配置。
Hibernate:提供了更为强大的缓存机制,包括一级缓存和二级缓存,并支持多种缓存策略,能够显著提高性能。
68、什么情况下建议使用hibernate?
建议在需要支持多种数据库兼容性和简化持久层开发时使用Hibernate。
Hibernate作为一个对象关系映射(ORM)框架,它的优势在于能够将Java对象与数据库表之间建立映射关系,从而使得开发者可以使用面向对象的方式进行数据库操作,而无需直接编写SQL语句。
69、hibernate 中如何在控制台查看打印的 sql 语句?
(1)日志打印
Hibernate 使用 SLF4J 或其他日志框架(如 Log4j、Logback 等)进行日志记录。你可以在 Hibernate 的配置中启用 SQL 语句的日志记录。
对于 Log4j,你可以在 log4j.properties 或 log4j.xml 文件中添加以下配置:
log4j.logger.org.hibernate.SQL=DEBUG
log4j.logger.org.hibernate.type.descriptor.sql=TRACE
如果你使用的是 Logback,可以在 logback.xml 中添加:
这样,Hibernate 就会打印出执行的 SQL 语句以及相关的绑定参数。
(2)使用 Hibernate 的 Show_SQL+format_sql 属性
在 Hibernate 的配置文件中(如 hibernate.cfg.xml 或通过注解配置),你可以设置 show_sql 属性为 true 来在控制台打印 SQL 语句。但是,请注意,这只会打印 SQL 语句本身,不会显示绑定的参数值。
format_sql 属性为 true 来格式化打印的 SQL 语句,使其更易读。
true
true
70、hibernate 有几种查询方式?
(1)HQL查询
HQL(Hibernate Query Language)是Hibernate专有的查询语言,它类似于SQL,但操作的是实体对象而非数据库表。HQL查询可以更加直观地进行对象之间的关联查询,同时减少对底层数据库结构的依赖。
(2)OID检索
OID(Object Identifier)是 Hibernate 中每个持久化对象的唯一标识符。OID 检索是通过调用 get() 或 load() 方法来获得一个持久化对象的方式。这两个方法的区别在于当对象不存在时,get() 方法返回 null,而 load() 方法会抛出 ObjectNotFoundException 异常。
(3)QBC查询
QBC(Query By Criteria)是一种以面向对象的方式构建查询的方法。它使用Criteria API来构建查询条件,这种方式更加灵活,可以在编译时检查属性和关联的正确性。
(4)对象导航检索
这种方式利用实体类的内部关联进行查找,不需要通过特定的方法和工具。例如,如果有一个 User 对象和一个与之关联的 Order 对象,可以通过调用 user.getOrders() 方法来获得该 User 的所有订单。
(5)原生SQL查询
在某些情况下,开发者可能需要直接使用原生SQL语句进行查询,以便利用特定数据库的特性或编写复杂的查询。Hibernate通过session.createSQLQuery()方法支持这种查询方式。
71、hibernate如何实现HQL查询?
我们首先创建了一个 SessionFactory 实例,然后开启一个 Session。我们构建了一个 HQL 查询字符串,该字符串指定了从 User 实体中选择所有字段,并且 name 属性等于指定的参数值。我们使用 session.createQuery() 方法来创建一个 Query 对象,并指定了查询返回的结果类型为 User。接着,我们设置查询参数,并执行查询来获取结果列表。
public class HQLQueryExample {
public static void main(String[] args) {
// 创建 SessionFactory,这通常是在应用启动时完成的,而不是在每次查询时
SessionFactory sessionFactory = new Configuration()
.configure("hibernate.cfg.xml") // 加载 Hibernate 配置文件
.addAnnotatedClass(User.class) // 注册实体类
.buildSessionFactory();
try (Session session = sessionFactory.openSession()) {
// 开启事务
session.beginTransaction();
// 创建 HQL 查询
String hql = "FROM User WHERE name = :name"; // HQL 查询,从 User 实体选择所有字段
// 创建查询对象
Query query = session.createQuery(hql, User.class);
// 设置参数
query.setParameter("name", "nezha soft");
// 执行查询并获取结果列表
List users = query.getResultList();
// 处理查询结果
for (User user : users) {
System.out.println("User ID: " + user.getId() + ", Name: " + user.getName() + ", Email: " + user.getEmail());
}
// 提交事务
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭 SessionFactory(通常在应用关闭时)
sessionFactory.close();
}
}
}
@Entity
@Table(name = "users")
public class User {
@Id
private Long id;
private String name;
private String email;
// 省略构造器、getter 和 setter 方法
}
72、hibernate如何实现OID检索?
代码与HQL查询类似,下面是关键代码:
// 使用 get() 方法进行 OID 检索
User user = session.get(User.class, userId);
73、hibernate如何实现QBC查询?
代码与HQL查询类似,下面是关键代码:
// 创建 Criteria 对象
Criteria criteria = session.createCriteria(Employee.class);
// 添加查询条件
criteria.add(Restrictions.eq("firstName", "John"));
criteria.add(Restrictions.gt("salary", 50000.0));
// 执行查询并获取结果
List employees = criteria.list();
74、hibernate如何实现对象导航检索?
Hibernate 的对象导航检索是指通过已经加载到内存中的持久化对象来访问其关联的其他对象。这种检索方式基于对象之间的关系,而不是直接通过数据库查询。
在上面的示例中,我们首先通过用户的 ID 使用 session.get() 方法检索用户对象。然后,我们直接访问用户的 orders 属性(这是一个 List),这是通过对象导航实现的。如果 orders 列表尚未加载到内存中(例如,如果使用了延迟加载),那么可能需要显式地初始化它,这通常通过 Hibernate 的 Hibernate.initialize() 方法或访问列表中的元素来触发。
请注意,对象导航检索的性能取决于 FetchType 的设置以及是否启用了延迟加载。如果 FetchType 设置为 LAZY(延迟加载),那么关联的对象在首次访问其属性之前不会被加载。这可以减少初始加载时的数据库交互次数,但可能会增加后续访问关联对象时的数据库交互次数。因此,在设计应用程序时,需要权衡初始加载时间和后续访问性能。
首先,假设我们有两个实体类:User 和 Order,它们之间存在一对多的关系,即一个用户可以拥有多个订单。
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List orders;
// 省略构造器、getter 和 setter 方法
}
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String orderNumber;
private double amount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
// 省略构造器、getter 和 setter 方法
}
现在,我们将使用对象导航来检索一个用户及其关联的订单:
// 使用 OID 检索获取用户对象
User user = session.get(User.class, userId);
// 检查用户是否存在
if (user != null) {
// 使用对象导航访问用户的订单列表
if (user.getOrders() != null) { // 确保订单列表已经被加载或者没有被延迟加载(取决于 FetchType)
for (Order order : user.getOrders()) {
...
}
}
}
75、hibernate如何实现原生SQL查询?
代码与HQL查询类似,下面是关键代码:
// 创建原生 SQL 查询
String sql = "SELECT * FROM employees WHERE first_name = :firstName";
NativeQuery nativeQuery = session.createNativeQuery(sql, Employee.class);
nativeQuery.setParameter("firstName", "John");
// 执行查询并获取结果列表
List employees = nativeQuery.getResultList();
76、hibernate 实体类可以被定义为 final 吗?
在Hibernate中,实体类可以被定义为final。然而,如果实体类被定义为final,那么Hibernate在进行一些代理和延迟加载等操作时可能会遇到一些限制。因为Hibernate通常会创建代理类来对实体类进行一些操作,而final类无法被继承,所以可能会导致Hibernate无法生成有效的代理类或抛出异常。
通常情况下,建议不要将实体类定义为final,以免出现潜在的问题。如果需要限制对实体类的继承,可以通过其他方式来实现,如在类的设计中采用不可变对象模式、使用其他方式来限制继承等。
77、在 hibernate 中使用 Integer 和 int 做映射有什么区别?
使用对象类型Integer允许实体属性值为null,而基础数据类型int则不允许。
对于基本数据类型int,JVM直接在栈内存中为其分配空间,因此访问速度更快。Integer对象在堆内存中分配空间,并通过引用访问。虽然现代JVM的性能优化已经使得这种差异变得很小,但在处理大量数据或高并发场景下,基本数据类型的性能优势可能会更加明显。
78、项目中是如何实现权限验证的,权限验证需要几张表?
Spring Security 是实现权限验证的常用框架,通常需要三张表来实现基本的权限验证。
Spring Security提供了一套全面的安全服务,包括身份认证、权限授权等功能。在实现权限验证时,通常会使用到该框架提供的多种机制和扩展点。例如,通过实现UserDetailsService接口并覆写里面的用户认证方法,可以自定义用户的认证逻辑。此外,权限管理过程包括鉴权管理和授权管理,即判断用户是否有权访问某个资源以及如何将权限分配给用户。
对于权限验证所需的表结构,RBAC(Role-Based Access Control,基于角色的访问控制)模型是一个常见的设计模式。在这个模型中,通常至少需要三张表:用户表、角色表、权限表。用户表存储用户信息,角色表定义了不同的角色,而权限表则规定了不同角色可以执行的操作。除此之外,还需要两张关系表来维护用户和角色之间、角色和权限之间的关系。这些表共同构成了权限验证的基础数据结构。
在设计和实施权限验证系统时,开发者需要根据实际业务需求和技术选型来决定具体的实现方式和所需表结构。
79、说说你对RBAC的理解?
RBAC 的核心思想是将权限赋予角色,然后将角色赋予用户。这种分离让系统管理员能够更容易地管理和控制资源的访问权限。通过 RBAC,可以对用户的权限进行集中管理,确保授权的一致性。 RBAC 主要包括三种基本权利:用户、角色和权限。当用户和角色是多对多的关系,当用户角色变更时只需要对关系进行更改即可,简化了权限管理工作。RBAC 通过模块化的方式进行权限管理,可以减少权限管理的复杂性,提高系统的安全性。
这是RBAC的基础模型。在RBAC0中,角色是权限的集合,用户则被分配到这些角色中。用户通过其角色获得访问资源的权限。RBAC0模型主要关注用户、角色和权限之间的基本关系。
RBAC1在RBAC0的基础上增加了角色继承的概念。在RBAC1中,角色可以继承其他角色的权限,形成一个角色层次结构。这种继承关系使得权限的管理更加灵活和方便,可以满足更复杂的权限控制需求。
RBAC2是RBAC的扩展模型,引入了约束的概念。这些约束可以是静态的,也可以是动态的,用于限制角色、权限和用户之间的关联关系。例如,可以设置约束来防止某个角色被赋予过多的权限,或者限制某个用户只能被分配到特定的角色中。
RBAC3结合了RBAC1和RBAC2的特性,既支持角色继承,又允许定义约束来限制权限的分配和使用。这使得RBAC3成为一个功能全面且高度可配置的权限管理模型。
80、rbac的实现理论分析
在实现RBAC(基于角色的访问控制)时,需要考虑以下因素:
- 角色定义:定义系统中所有角色和这些角色可以执行的权限或操作。
- 权限管理:对系统中需要进行控制的所有资源和操作进行明确定义,并分配相应的权限。
- 用户-角色分配:确定系统中的用户与角色之间的关联关系,即哪些用户属于哪些角色。
- 角色-权限分配:明确每个角色所具有的权限。这个关系是角色拥有哪些权限的映射。
- 角色层级关系(如果需要):在某些情况下,需要为角色定义父子关系以实现角色的继承和权限的继承。
- 安全策略实施:制定并实施适当的安全策略,包括访问控制列表、访问策略、密码策略等。
- 审计和监控:监控系统对权限的使用情况,进行审计和日志记录,以及定期的安全审查和漏洞分析。
- 用户界面及管理工具:提供用户管理界面和权限管理界面,以便管理员和用户能够方便地管理和使用RBAC系统。
- 用户认证和会话管理:RBAC系统通常需要与用户认证和会话管理系统集成,以确保合法用户可以正常访问系统资源。
- 数据库设计:RBAC系统的实现通常涉及数据库设计,包括用户、角色、权限信息的存储和管理。
- 系统优化和性能:为支持RBAC系统的高效运行,可能需要对系统进行特定的优化,确保系统可以快速、安全地进行权限验证和验证。
81、rbac拦截器权限控制关键代码?
Java RBAC拦截器权限控制的关键代码如下:
(1)定义权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {
String[] value() default {};
}
(2)实现拦截器
public class PermissionInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求的URL
String url = request.getRequestURI();
// 获取当前登录用户的角色列表
List roles = userService.getRolesByUsername(request.getRemoteUser());
// 判断是否有访问该URL的权限
if (hasPermission(url, roles)) {
return true;
} else {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return false;
}
}
private boolean hasPermission(String url, List roles) {
// 根据URL和角色列表判断是否有访问权限
// 这里可以根据具体的业务逻辑进行判断,例如查询数据库等
// 如果具有访问权限,返回true;否则返回false
return true;
}
}
(3)配置拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private PermissionInterceptor permissionInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");
}
}
(4)在需要控制权限的方法上添加注解
@RestController
public class UserController {
@Permission({"ROLE_ADMIN", "ROLE_USER"})
@GetMapping("/users")
public List getUsers() {
// ...
}
}
当用户访问/users接口时,拦截器会根据用户的角色列表判断是否具有访问权限。如果具有访问权限,则允许访问;否则返回403 Forbidden错误。