IOC是指将对象的创建和依赖关系的管理交给Spring容器来处理。
IOC控制反转通常通过依赖注入来实现,这可以通过XML配置或者注解来完成。
IOC可以帮助开发者减少代码的复杂性,提高模块之间的解耦,使得代码更加灵活和可维护。
AOP允许开发者将横切关注点(如日志、事务管理等)与业务逻辑分离,从而提供更好的模块化。
在Spring中,AOP可以通过动态代理或者字节码操作来实现,常用的是动态代理。
AOP可以提高代码的重用性,使得横切关注点的修改更加集中和方便。
Spring AOP(Aspect Oriented Programming)的工作流程主要涉及到面向切面编程的核心概念,如切面、连接点和通知等。以下是Spring AOP的基本工作流程:
Spring AOP 是 Spring Framework 提供的一种 AOP 实现方式,是基于动态代理实现的。它允许在方法调用前、方法调用后、抛出异常时等切点进行切入通知,并通过动态代理技术织入切面。Spring AOP 只能在方法级别上生效。
AspectJ AOP 是一个独立的 AOP 框架, 是基于字节码操作实现的。它提供了比 Spring AOP 更为强大和灵活的 AOP 功能。AspectJ 可以在方法调用前、方法调用后、抛出异常时等切点进行切入通知,并且还支持构造器、字段、对象初始化和异常处理等更多切点。AspectJ 可以通过编译器织入(AspectJ编译器),也可以使用代理织入(在运行时为目标对象创建代理,称为 Spring AOP 的 AspectJ 代理模式)。
简而言之,如果需要更强大的功能和更好的性能,可以选择使用 AspectJ AOP;如果只需要简单的切面编程并且希望保持代码的简单性,可以选择使用 Spring AOP。
Spring AOP 提供了两种代理方式:JDK 动态代理和 CGLIB 动态代理。
基于接口的代理,要求目标类实现一个接口,通过接口生成代理对象。这种方式的优点是性能较好,但缺点是只能代理接口,不能代理类。
基于类的代理,不要求目标类实现接口,直接对类进行代理。这种方式的优点是可以代理类和接口,但缺点是性能相对较差。
相对于 CGLIB 动态代理,JDK 动态代理的性能较好,因为它是基于接口的代理,生成的代理类较少,使用起来比较简单,运行时开销较小。
与 JDK 动态代理相比,CGLIB 动态代理不要求目标类必须实现接口,因此更加灵活,但代理对象的创建过程相对更为耗时。
Spring的依赖注入(Dependency Injection,简称DI)是通过Java的反射机制实现的。在Spring中,你可以使用XML配置文件或注解的方式,定义Bean之间的依赖关系,然后由Spring容器在运行时将这些依赖关系注入到相应的Bean中。
在所有这些方式中,Spring都使用了Java的反射机制来动态地创建Bean的实例,并设置其属性或调用其构造函数。反射机制允许Spring在运行时获取类的信息,包括类的构造函数、方法、属性等,然后根据配置信息动态地创建和配置Bean。
通过编写 XML 文件来配置 Spring 应用程序的组件、依赖项和行为。
使用注解(如 @Component, @Autowired, @Configuration 等)来配置应用程序的部分或全部组件,以及它们之间的依赖关系
使用纯 Java 代码配置 Spring 应用程序的组件、依赖项和行为,不需要 XML 文件。通常使用 @Configuration 和 @Bean 注解来实现。
当您使用FileSystemXmlApplicationContext来加载XML配置文件时,虽然您可以成功地创建和管理Bean,但是这些Bean不会被自动注入到Spring容器中进行托管。因此,当您尝试使用@Autowired注解来获取这些Bean时,Spring容器无法找到这些Bean并将它们注入到目标对象中。
解决方法是将通过FileSystemXmlApplicationContext加载的Bean手动注册到Spring容器中。
具体步骤如下:
FileSystemXmlApplicationContext是Spring框架中的一个类,它用于从文件系统加载XML配置文件并创建应用程序上下文。它是AbstractApplicationContext的一个具体实现,专门用于处理XML配置文件。
当你有一个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()方法。
FileSystemXmlApplicationContext和ClassPathXmlApplicationContext都是Spring容器在加载XML配置文件时使用的接口实现类,它们之间的主要区别在于加载配置文件的方式和路径。
FileSystemXmlApplicationContext是通过文件系统加载XML配置文件的方式来初始化Spring容器。
ClassPathXmlApplicationContext是通过类路径(class path)加载XML配置文件的方式来初始化Spring容器。
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; }}
如果需要精确注入某个特定名称的bean,那么可以选择@Resource,因为它会根据名称进行匹配。
如果需要在多个相同类型的bean中选择一个进行注入,那么可以选择@Autowired,并结合@Qualifier注解来指定具体的bean。
在Spring框架中,Bean的作用域定义了Bean实例的生命周期和可见范围。Spring框架提供了以下五种主要的Bean作用域:
这些作用域之间的主要区别在于Bean实例的生命周期、创建方式以及可见范围。选择合适的Bean作用域取决于应用程序的需求和设计。
作用域规定了bean实例的生命周期和在容器中的存储方式。作用域确定了bean实例的创建、初始化和销毁方式,以及在应用程序中使用这些bean的方式。
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;}
<bean id="CustomerBean" class="com.Customer"> <property name="person"> <bean class="com.person"> <property name="id" value=1 /> <property name="name" value="哪吒编程" /> <property name="age" value=18 /> </bean> </property></bean>
在这个例子中,"person"是一个内部bean,它被嵌套在"CustomerBean"的属性中。内部bean的生命周期受外部bean控制,并且外部bean销毁时,内部bean也会被销毁。 inner beans的使用可以帮助简化配置文件,并在外部bean范围内限制bean的可见性。
Spring 框架本身并不保证单例Beans的线程安全性,因为线程安全性通常取决于Bean的实现方式,而不是容器本身。因此,开发者需要自行确保他们的单例Beans是线程安全的。
有几种常见的策略可以帮助确保单例Beans的线程安全性:
总之,虽然Spring框架本身不保证单例Beans的线程安全性,但开发者可以通过上述策略来确保他们的Bean在多线程环境中能够正常工作。
Spring Bean的自动装配是指Spring容器根据预先定义的规则,自动在Spring应用程序上下文中将Bean与其他Bean进行关联。这样可以避免手动配置Bean之间的依赖关系,从而简化了应用程序的配置。
Spring提供了以下几种自动装配的模式:
使用自动装配可以减少配置工作,并且更易于维护。然而,过度依赖自动装配也可能导致代码不够清晰,因此需要根据具体情况进行合理的选择。
FileSystemResource 是从文件系统路径加载文件,而 ClassPathResource 是从类路径(classpath)中加载文件。
FileSystemResource 适用于加载本地文件系统中的文件,而 ClassPathResource 适用于加载应用程序内部的资源文件,如配置文件、模板等。
FileSystemResource 需要提供文件的绝对路径或相对路径,而 ClassPathResource 只需要提供资源文件的相对路径即可。
在XML配置文件中定义一个util:properties元素来创建一个Properties对象,然后将其注入到Bean中。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <!-- 定义Properties --> <util:properties id="myProperties" location="classpath:my-properties.properties"/> <!-- 注入Properties到Bean --> <bean id="myBean" class="com.example.MyBean"> <property name="properties" ref="myProperties"/> </bean> </beans>
如果你正在使用Java配置或者希望直接在代码中使用注解,那么可以使用@Value注解来注入Properties。但请注意,@Value注解主要用于注入单个属性值,而不是整个Properties对象。然而,你可以通过Spring的@ConfigurationProperties注解或者@PropertySource和Environment类来注入整个Properties对象。
BeanFactory是Spring框架中的一个核心接口,它主要用于管理和提供应用程序中的Bean实例。BeanFactory接口定义了Spring容器的基本规范和行为,它提供了一种机制来将配置文件中定义的Bean实例化、配置和管理起来。它负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory的主要作用是提供Bean的创建、配置、初始化和销毁等基本操作,它可以根据配置文件或注解来创建并管理Bean实例,并提供了各种方法来获取和操作Bean实例。
FactoryBean接口定义了一种创建Bean的方式,它允许开发人员在Bean的创建过程中进行更多的自定义操作。通过实现FactoryBean接口,开发人员可以创建复杂的Bean实例,或者在Bean实例化之前进行一些额外的逻辑处理。
ApplicationContext则是BeanFactory的子接口,它提供了比BeanFactory更完整的功能。除了继承BeanFactory的所有功能外,ApplicationContext还提供了国际化、资源文件访问、监听器注册等功能。
BeanFactory 是延迟初始化,即在真正需要使用到某个 Bean 时才会创建该 Bean。ApplicationContext 则是在容器启动时就会预先加载所有的 Bean,所以它是预初始化。
BeanFactory 的注册必须要在配置文件中指定,而 ApplicationContext 可以通过注解的方式自动扫描并注册 Bean。
ApplicationContext 会管理 Bean 的完整生命周期,包括创建、初始化、销毁等过程。而 BeanFactory 则只负责创建和管理 Bean 实例,不会对 Bean 进行生命周期管理。
Spring框架中有三级缓存。
Spring中最基本的缓存,用于存放完全初始化并确定类型的Bean实例。当一个Bean被创建并完成所有的初始化过程后,它会被转移到这个缓存中。这个缓存是对外提供服务的主要缓存,当我们通过Spring获取一个Bean时,首先会从这个缓存中查找是否有现成的对象。
存放的是已经实例化但未初始化的bean。
保证了在多次循环依赖时,同一个类只会被构建一次,从而确保了单例性质。
用于存储用于创建单例bean的ObjectFactory。
当依赖的bean实例创建完成后,Spring会使用这个ObjectFactory来创建bean实例,并从三级缓存中移除。
三级缓存的主要作用是解决循环依赖问题,特别是当涉及到AOP代理时。通过将代理的bean或普通bean提前暴露,使得依赖注入成为可能。
先了解一下什么是循环依赖?
当两个或多个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代理时。
Spring中的事务传播行为定义了七种类型,分别是:
PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常见的选择。
PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行操作。
PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则与PROPAGATION_REQUIRED类似。
这些传播行为可以通过@Transactional注解的propagation属性来设置,用于控制业务方法的事务行为。
开发者如果对@Transactional注解的工作原理和使用方式理解不深入,或者在使用时存在误解,也可能导致事务失效。
在Spring中,如果@Transactional注解添加在不是public修饰的方法上,事务就会失效。因为Spring的事务是通过AOP代理实现的,而AOP代理需要目标方法能够被外部访问,所以只有public方法才能被代理。
如果事务方法所在的类没有被加载到Spring IoC容器中,即该类没有被Spring管理,那么Spring就无法实现代理,从而导致事务失效。
如果事务方法抛出的异常被catch处理,那么@Transactional注解无法感知到异常,因此无法回滚事务,导致事务失效。
如果内部方法的事务传播类型被配置为不支持事务的传播类型,那么该方法的事务在Spring中就会失效。
如果在事务方法中调用了异步方法,那么异步方法中的事务可能会失效。
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数据库)集成,进一步提升了批处理作业的灵活性和适用性。
在Spring中实现定时任务,可以使用@Scheduled注解。以下是一个简单的示例:
首先,在Spring配置文件中开启定时任务支持,添加task:annotation-driven/标签:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> <!-- 开启定时任务支持 --> <task:annotation-driven/></beans>
创建一个定时任务类,并在需要执行定时任务的方法上添加@Scheduled注解:
import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;@Componentpublic class MyTask { // 每隔5秒执行一次 @Scheduled(fixedRate = 5000) public void doTask() { System.out.println("执行定时任务"); }}
@Required注解是Spring框架中的注解之一,用于标记Bean的属性在配置时必须进行注入的。当在Bean配置中使用了@Required注解标记的属性时,Spring在初始化Bean时会检查这些属性是否被正确注入,如果未被注入,则会抛出BeanInitializationException异常。
在Spring 5.x及更高版本中,@Required注解已经被废弃,因为它依赖于一些不推荐使用的特性。
推荐使用Java配置或XML配置中的required="true"属性来指定必需的属性。
推荐使用JSR-330中的@Inject或者Spring的@Autowired注解来代替@Required。这些注解在实现依赖注入时更加灵活,并且更容易用于各种场合。
以下是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:用于处理矩阵变量。
在REST风格的请求中,核心概念包括资源(Resource)和RESTful API(Application Programming Interface)。资源可以简单理解为URI,表示一个网络实体,具有唯一标识符。客户端通过访问这些标识符来对资源进行操作。RESTful API则是使用HTTP协议的不同方法来实现与资源的交互。
客户端与服务器之间的交互通过HTTP协议进行,客户端不需要知道服务器的实现细节,只需要遵循HTTP协议。
在会话中生成一个唯一的Token,并将其嵌入到表单中。当表单被提交时,服务器检查该Token是否有效,并确保其只被使用一次。 一旦处理完请求,服务器应废弃该Token。
用户点击提交按钮后,立即将其禁用,以防止用户多次点击。
提交成功后,将用户重定向到一个新的页面或消息提示页,这样用户就无法再次点击提交按钮了。
对表单提交设置时间间隔限制,例如不允许在一分钟内连续提交。
对于敏感操作,要求用户输入验证码,这可以有效防止机器人或恶意软件的自动提交。
如果重复提交会导致数据库中的数据冲突,可以在数据库字段上设置唯一性约束。
简单工厂模式的本质就是一个工厂类根据传入的参数,动态的决定实例化哪个类。
Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象。
应用程序将对象的创建及初始化职责交给工厂对象,工厂Bean。
定义工厂方法,然后通过config.xml配置文件,将其纳入Spring容器来管理,需要通过factory-method指定静态方法名称。
Spring用的是双重判断加锁的单例模式,通过getSingleton方法从singletonObjects中获取bean。
Spring的AOP中,使用的Advice(通知)来增强被代理类的功能。Spring实现AOP功能的原理就是代理模式(① JDK动态代理,② CGLIB字节码生成技术代理。)对类进行方法级别的切面增强。
装饰器模式:动态的给一个对象添加一些额外的功能。
Spring的ApplicationContext中配置所有的DataSource。这些DataSource可能是不同的数据库,然后SessionFactory根据用户的每次请求,将DataSource设置成不同的数据源,以达到切换数据源的目的。
在Spring中有两种表现:
一种是类名中含有Wrapper,另一种是类名中含有Decorator。
定义对象间的一对多的关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
Spring中观察者模式一般用在listener的实现。
策略模式是行为性模式,调用不同的方法,适应行为的变化 ,强调父类的调用子类的特性 。
getHandler是HandlerMapping接口中的唯一方法,用于根据请求找到匹配的处理器。
Spring JdbcTemplate的query方法总体结构是一个模板方法+回调函数,query方法中调用的execute()是一个模板方法,而预期的回调doInStatement(Statement state)方法也是一个模板方法。
在Spring中,可以使用XML配置或者注解来注入一个Java Collection。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="stringList" class="java.util.ArrayList"> <constructor-arg> <list> <value>Item 1</value> <value>Item 2</value> <value>Item 3</value> </list> </constructor-arg> </bean></beans>
@Configurationpublic class AppConfig { @Bean public List<String> stringList() { return Arrays.asList("Item 1", "Item 2", "Item 3"); }}
在上面的示例中,我们使用@Configuration注解标注了一个配置类,并且在配置类中定义了一个返回List类型的方法,通过@Bean注解来将List实例注入Spring容器。
Spring使用一个内部的任务调度器(TaskScheduler)来管理所有被 @Scheduled 注解的方法。
当Spring容器启动时,它会自动扫描所有的Bean,找到被 @Scheduled 注解的方法,并将它们注册到TaskScheduler中。
TaskScheduler会按照注解中指定的时间间隔或表达式来自动调用这些方法。
@Scheduled注解可以根据配置使用多线程执行,发生异常时,根据配置决定是重试还是直接跳过。
深入一点:
Spring MVC的执行流程遵循了前端控制器模式,通过DispatcherServlet协调各个组件来接收请求、匹配处理器、执行处理器、渲染视图的过程,实现了请求到视图的端到端处理。
具体执行流程如下:
Spring MVC是Spring框架的一个模块,用于构建基于MVC(Model-View-Controller)设计模式的Web应用程序。在Spring MVC中,有九大组件是非常重要的,它们分别是:
这九大组件共同构成了Spring MVC的完整流程:
首先,DispatcherServlet接收用户请求,然后通过HandlerMapping找到对应的Controller,再通过HandlerAdapter调用Controller的方法,方法返回的ModelAndView包含了模型数据和视图信息,最后通过ViewResolver解析视图,并通过View展示模型数据。在这个过程中,还可以通过LocaleResolver来解析用户的语言和区域设置,以实现国际化。
@Controller:用于标识一个类为Spring MVC的控制器,处理客户端的请求并返回相应的视图。
@RequestMapping:用于将URL路径映射到特定的处理方法,可用于类级别或方法级别,用于定义请求URL和处理请求的方法。
@ResponseBody:用于将方法的返回值直接作为HTTP Response的主体内容返回,通常用于返回 JSON 或 XML 格式的数据。
@PathVariable:用于将请求URL中的模板变量映射到处理方法的参数中。
@RequestParam:用于将请求参数映射到处理方法的参数中,可以指定参数名称、是否必须等。
@RequestParamMap:用于将所有的请求参数映射为一个Map类型的参数。
@ModelAttribute:用于将请求参数绑定到Model对象中,通常用于在视图渲染之前填充模型数据。
@SessionAttributes:用于指定处理方法的返回值需要存储到会话中的属性值。
@InitBinder:用于定制数据绑定规则和初始化数据绑定规则。
@Validated:用于标记其后的方法参数需要进行验证。
Spring MVC是基于Servlet API构建的,而Struts2是通过Filter实现的。这意味着Spring MVC的入口是一个Servlet,而Struts2的入口是一个Filter。这导致了两者在处理请求时的不同机制。
Spring MVC 的拦截器机制通过 HandlerInterceptor 接口进行实现。通过实现这个接口,你可以在处理程序执行的前后插入逻辑,可以在请求处理之前或之后对请求进行预处理或后处理。拦截器可以用来实现日志记录、权限校验、国际化、主题切换等各种需求。
Struts 的拦截器机制是通过拦截器栈(interceptor stacks)实现的。在Struts2中,通过配置拦截器栈,可以在请求处理的各个阶段插入预处理逻辑或后处理逻辑。拦截器可以用于日志记录、权限校验、异常处理、输入验证等。
Spring MVC 的配置更加灵活,可以使用 XML 配置、Java 注解或者 Java 类配置(JavaConfig)来配置控制器、视图解析器等。
Struts 使用基于 XML 的配置文件来定义控制器(Action)、拦截器、结果视图等。
Spring MVC强调依赖注入,通过Spring容器管理对象和组件,使得应用更加灵活和可测试。 Struts不提供像Spring那样的依赖注入机制,更多地依赖于配置文件,因此在灵活性方面可能稍逊于Spring MVC。
由于Spring MVC的轻量级设计,其代码量相对较少,运行时间也更快,因此在性能方面通常优于Struts2。
@RestController和@Controller是Spring MVC中常用的注解,它们的主要区别在于返回值的不同:
@Controller:用于标识控制器类的注解。在Spring MVC中,使用@Controller注解标记的类表示该类是控制器,可以处理客户端的请求,并返回相应的视图。
@RestController:是Spring4.0引入的一个组合注解,用于标识RESTful风格的控制器类。它相当于@Controller和@ResponseBody的组合,表示该类处理RESTful请求,方法的返回值直接作为HTTP Response的主体内容返回,而不是作为视图进行解析。
因此,@RestController注解在处理HTTP请求时,会将方法的返回值直接转换为JSON/XML等格式的数据返回给客户端,而@Controller注解一般用于传统的Web应用程序开发,将方法的返回值解析为视图进行渲染。
在Spring MVC中,请求路径的匹配是通过DispatcherServlet和HandlerMapping来完成的。具体步骤如下:
在Spring MVC中,可以通过多种方式来配置请求路径与Controller的映射关系,例如使用XML配置文件或者使用注解(如@RequestMapping)。
RequestMapping可以实现模糊匹配路径,比如:
Spring MVC的执行流程:
因此,DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、Interceptors(拦截器)就是在调用controller接口前进行的操作。
最常见的方法,用于从请求参数中取值到控制器方法的参数上。
从URL路径中提取参数时,可以使用@PathVariable注解。
在这个例子中,当发送一个GET请求到/users/123时,123会被解析为id参数的值。
@Controller public class MyController { @GetMapping("/users/{id}") public String getUser(@PathVariable Long id) { // 使用id参数 return "user"; } }
当请求参数与对象属性对应时,可以使用@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 } }
当请求体中包含JSON或XML数据时,可以使用@RequestBody注解来自动解析请求体并绑定到方法参数上。
还可以直接通过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"; } }
#{}:被称为占位符,在执行SQL语句时,通过PreparedStatement对参数值进行预处理,MyBatis会将#{}替换为?,然后将参数值传递给SQL语句,这样可以防止SQL注入攻击。当使用#{}时,MyBatis会自动处理参数的转义和引号等问题,确保参数作为一个完整的字符串被传递。
#{}主要用于替换 SQL 语句中的条件值,它不能直接用于替换 SQL 语句的片段或关键字,比如表名。
:称为变量替换,使用{}时,它在SQL语句中直接替换成相应的参数值,它会将参数值直接拼接到SQL语句中,因此存在SQL注入的风险。
${}:可以替换 SQL 语句中的任何部分,包括列名、表名、SQL 关键字等。
在将参数传递给SQL语句之前,对参数值进行验证,确保它们符合预期的格式或范围。这可以通过正则表达式、字符串比较或其他逻辑来实现。
避免敏感操作,比如DROP、TRUNCATE 等。
对于可能引起注入的特殊字符,需要进行合适的转义处理,比如对单引号、双引号、分号等特殊字符进行转义,防止它们被误解为SQL命令的一部分。
确保数据库用户只有执行必要操作的权限,避免给予过多的权限。这样即使发生了 SQL 注入攻击,攻击者也只能执行有限的操作。
记录所有执行的 SQL 语句,并监控任何异常或可疑行为。这有助于及时发现并应对潜在的 SQL 注入攻击。
MyBatis支持延迟加载,它允许在需要时动态地加载与某个对象关联的数据。延迟加载可以帮助减少不必要的数据库查询,提高性能,并且提供了一种方便的方式来管理复杂对象之间的关联关系。
延迟加载的原理是,在查询主对象时,并不会立即加载关联对象的信息,而是在真正需要使用这些关联对象的时候再去发起对关联对象的查询。具体来说,延迟加载通常使用代理对象(Proxy)来实现。当主对象被查询并加载到内存中时,关联对象并没有被加载,而是创建一个代理对象来代替关联对象的位置。当应用程序实际使用关联对象的属性或方法时,代理对象会拦截这些调用,并触发对关联对象数据的实际加载查询,然后返回结果给应用程序。
在MyBatis中,延迟加载通常与二级缓存(二级缓存是一种全局性的缓存机制,可以跨多个会话对查询进行缓存)结合使用,可以延迟加载对象的时候首先尝试从二级缓存中获取数据,如果缓存中不存在再去查询数据库。
延迟加载可以提高查询性能,特别是在处理大量数据或者复杂关联查询的时候。但是,它也会增加一些额外的内存开销,因为需要创建代理对象,并且在访问数据时需要进行额外的数据库查询操作。因此,在使用延迟加载时需要根据具体的业务需求和性能要求进行权衡。
MyBatis的一级缓存是SqlSession级别的缓存,也就是说当我们执行查询之后,会将查询的结果放在SqlSession的缓存中。
对于同一个SqlSession,当执行相同的查询时,MyBatis会先查看一级缓存中是否有相同的查询结果,如果有,则直接返回缓存的结果,而不再去数据库中查询。
一级缓存是MyBatis默认开启的,它可以减少对数据库的访问,提高查询性能。
MyBatis的二级缓存是Mapper级别的缓存,它可以跨SqlSession共享缓存数据。
二级缓存是在Mapper的映射文件配置开启的,我们可以在Mapper的映射文件中配置元素来开启二级缓存。
使用二级缓存时,要确保查询的 SQL 语句和参数是完全相同的,否则 MyBatis 会认为它们是不同的查询,从而不会从二级缓存中获取数据。
对于频繁更新的数据,不建议使用二级缓存,因为频繁的更新会导致缓存失效,反而降低性能。
SimpleExecutor是MyBatis默认的执行器,它对每个SQL语句的执行进行了封装,每次都会生成一个新的Statement对象,并执行SQL语句,SimpleExecutor适用于短时、简单的操作。
ReuseExecutor是一种复用的执行器,它会在多次执行相同SQL语句时重用Statement对象,从而减少了Statement对象的创建和销毁,提升了性能。
BatchExecutor是一种执行器,用于批量操作。当我们需要执行批量的SQL语句时,可以使用BatchExecutor来提高性能。BatchExecutor通过快速执行批量的SQL语句,来减少与数据库的交互次数,提高操作的效率。
Spring集成MyBatis时,执行器的设置通常是在Spring的配置文件中进行的。你可以通过配置SqlSessionFactoryBean的executorType属性来指定执行器类型。例如:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:mybatis-config.xml" /> <property name="executorType" value="REUSE"/> </bean>
这个设置会影响所有的SqlSession,但不建议全局修改执行器类型,因为这可能会对性能产生不必要的影响。
当获取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(); }
可以通过在映射文件中配置和标签来定义相应的执行器,在这些标签中可以针对具体的SQL语句配置执行器类型。
<select id="selectBlog" parameterType="int" resultType="Blog" statementType="PREPARED" useCache="true" flushCache="true"> SELECT * FROM BLOG WHERE ID = #{id}</select>
在上面的示例中,可以看到标签指定了该查询的执行器类型为“PREPARED”,也就是预处理执行器。
BatchExecutor 通过批处理的方式提高性能。它允许在 JDBC 客户端缓存多条 SQL 语句,然后在缓存满或手动刷新时,将这些语句打包一起发送到数据库执行。这种方式可以有效减少网络通信次数和数据库交互的开销,从而提高系统性能。
网络通信次数:通过一次发送多条 SQL 语句,减少了与数据库服务器之间的往返次数,从而减少了网络延迟的影响。数据库交互开销:数据库在处理批量请求时,可以优化执行计划,减少编译和准备时间,进一步提高执行效率。
(1)使用参数化的SQL语句 在编写SQL语句时,应使用参数化的方式来构建SQL,而不是将用户输入的值直接拼接到SQL字符串中。这样可以确保用户输入的内容不会被解释为SQL命令。 #{}占位符会自动对输入值进行转义,可以有效防止SQL注入攻击。
<select id="getUser" parameterType="string" resultType="User"> SELECT * FROM users WHERE username = #{username}</select>
(2)使用MyBatis的动态SQL MyBatis的动态SQL允许根据条件动态拼接SQL语句,可以在拼接SQL的过程中对用户输入的内容进行转义或其他处理,从而防止SQL注入攻击。
<select id="getUsers" parameterType="map" resultType="User"> SELECT * FROM users <where> <if test="username != null"> AND username = #{username} </if> </where></select>
动态 SQL 的主要作用在于,根据运行时不同的条件,动态地生成不同的 SQL 语句,从而实现更灵活、更高效的数据库操作。
常见的动态 SQL 标签:
这些动态 SQL 标签大大增强了 MyBatis 的灵活性,使得开发者能够根据不同的业务逻辑,动态地构建 SQL 语句,从而提高了开发效率和代码的可维护性。
在 MyBatis 的 XML 映射文件中,不同的 XML 映射文件之间的 ID 是可以重复的。因为每个 XML 映射文件都是独立的,它们之间不会相互影响。但是在同一张 XML 映射文件中,每个元素中的 ID 必须是唯一的,不能重复。
当 MyBatis 接收到 Java 接口方法的调用时,它会首先查找是否有与该方法相关的 SQL 映射语句。如果有,MyBatis 会解析该 SQL 语句,并根据传入的参数值动态地生成最终的 SQL 语句。然后,MyBatis 会执行这个 SQL 语句,并将查询结果映射回 Java 对象,最后返回给调用者。
在接口绑定中,MyBatis 提供了两种实现方式:
在 MyBatis 的全局配置文件(如 mybatis-config.xml)中,通过元素引入 SQL 映射文件,将映射文件与 Java 接口关联起来。
在 Java 接口方法上添加 @Select、@Insert、@Update、@Delete 等注解,直接编写 SQL 语句。
public interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") User getUserById(int id);}
当 SQL 语句中只有一个参数时,MyBatis 无需指定参数的类型或名称,直接使用 #{} 来引用参数。MyBatis 会自动将这个参数绑定到 SQL 语句中。
① 使用顺序:MyBatis 会按照参数的顺序进行绑定,可以使用 #{param1}、#{param2} 等来引用参数。
<select id="selectUserByUsernameAndPassword" resultType="User"> SELECT * FROM user WHERE username = #{param1} AND password = #{param2} </select>
② 使用 @Param 注解:在 Mapper 接口的方法参数上添加 @Param 注解,为参数指定一个名称,然后在 XML 中使用这个名称来引用参数。
User selectUserByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
<select id="selectUserByCriteria" resultType="User"> SELECT * FROM user WHERE username = #{username} AND age = #{age} </select>
③ 使用 Map 或 JavaBean:将多个参数封装到一个 Map 或 JavaBean 中,然后在 XML 中引用 Map 的 key 或 JavaBean 的属性。
User selectUserByCriteria(Map<String, Object> criteria);
<select id="selectUserByCriteria" resultType="User"> SELECT * FROM user WHERE username = #{username} AND age = #{age} </select>
对于复杂类型(如 JavaBean 或自定义类型),MyBatis 会自动映射这些类型的属性到 SQL 语句中。只需要在 XML 中使用 #{} 引用这些属性的名称即可。
public class UserCriteria { private String username; private int age; // getters and setters }
<select id="selectUserByCriteria" resultType="User"> SELECT * FROM user WHERE username = #{username} AND age = #{age} </select>
对于结果集的映射,MyBatis 提供了 @Results 和 @Result 注解,用于在接口方法上直接定义结果集的映射关系,而无需编写 XML 映射文件。这些注解可以指定如何将数据库中的列映射到 Java 对象的属性上。
<insert id="saveUserBatch" parameterClass="java.util.List"> insert all <iterate conjunction=" "> into USER ( ID, NAME, SAVE_DATE ) values <![CDATA[ ( #list[].id#, #list[].name#, SYSDATE ) ]]> </iterate> select * from dual</insert>
MyBatis:是一个半自动化的持久层框架,需要手动编写SQL语句和ResultMap。MyBatis支持注解配置和XML配置,可以在XML文件中编写动态SQL语句。
Hibernate:是一个全自动化的ORM框架,它会自动生成SQL语句,开发者无需关心SQL的生成与结果映射。
MyBatis:由于SQL语句是手动编写的,所以可以对SQL进行精细的优化,提高性能。但是,由于所有SQL都是依赖数据库书写的,所以MyBatis的扩展性、迁移性相对较差。
Hibernate:支持多种数据库,通过配置可以方便地进行数据库切换。Hibernate使用反射机制实现持久化对象操作,使得业务层与具体数据库分开,降低了数据库之间迁移的成本。
MyBatis:提供了一级缓存和二级缓存机制,但主要是依赖于开发者手动管理和配置。
Hibernate:提供了更为强大的缓存机制,包括一级缓存和二级缓存,并支持多种缓存策略,能够显著提高性能。
建议在需要支持多种数据库兼容性和简化持久层开发时使用Hibernate。
Hibernate作为一个对象关系映射(ORM)框架,它的优势在于能够将Java对象与数据库表之间建立映射关系,从而使得开发者可以使用面向对象的方式进行数据库操作,而无需直接编写SQL语句。
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 中添加:
<logger name="org.hibernate.SQL" level="DEBUG" /> <logger name="org.hibernate.type.descriptor.sql" level="TRACE" />
这样,Hibernate 就会打印出执行的 SQL 语句以及相关的绑定参数。
在 Hibernate 的配置文件中(如 hibernate.cfg.xml 或通过注解配置),你可以设置 show_sql 属性为 true 来在控制台打印 SQL 语句。但是,请注意,这只会打印 SQL 语句本身,不会显示绑定的参数值。
format_sql 属性为 true 来格式化打印的 SQL 语句,使其更易读。
<hibernate-configuration> <session-factory> <!-- 其他配置 --> <property name="show_sql">true</property> <property name="format_sql">true</property> </session-factory> </hibernate-configuration>
HQL(Hibernate Query Language)是Hibernate专有的查询语言,它类似于SQL,但操作的是实体对象而非数据库表。HQL查询可以更加直观地进行对象之间的关联查询,同时减少对底层数据库结构的依赖。
OID(Object Identifier)是 Hibernate 中每个持久化对象的唯一标识符。OID 检索是通过调用 get() 或 load() 方法来获得一个持久化对象的方式。这两个方法的区别在于当对象不存在时,get() 方法返回 null,而 load() 方法会抛出 ObjectNotFoundException 异常。
QBC(Query By Criteria)是一种以面向对象的方式构建查询的方法。它使用Criteria API来构建查询条件,这种方式更加灵活,可以在编译时检查属性和关联的正确性。
这种方式利用实体类的内部关联进行查找,不需要通过特定的方法和工具。例如,如果有一个 User 对象和一个与之关联的 Order 对象,可以通过调用 user.getOrders() 方法来获得该 User 的所有订单。
在某些情况下,开发者可能需要直接使用原生SQL语句进行查询,以便利用特定数据库的特性或编写复杂的查询。Hibernate通过session.createSQLQuery()方法支持这种查询方式。
我们首先创建了一个 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<User> query = session.createQuery(hql, User.class); // 设置参数 query.setParameter("name", "nezha soft"); // 执行查询并获取结果列表 List<User> 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 方法 }
代码与HQL查询类似,下面是关键代码:
// 使用 get() 方法进行 OID 检索 User user = session.get(User.class, userId);
代码与HQL查询类似,下面是关键代码:
// 创建 Criteria 对象 Criteria criteria = session.createCriteria(Employee.class); // 添加查询条件 criteria.add(Restrictions.eq("firstName", "John")); criteria.add(Restrictions.gt("salary", 50000.0)); // 执行查询并获取结果 List<Employee> employees = criteria.list();
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<Order> 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()) { ... } }}
代码与HQL查询类似,下面是关键代码:
// 创建原生 SQL 查询 String sql = "SELECT * FROM employees WHERE first_name = :firstName"; NativeQuery<Employee> nativeQuery = session.createNativeQuery(sql, Employee.class); nativeQuery.setParameter("firstName", "John"); // 执行查询并获取结果列表 List<Employee> employees = nativeQuery.getResultList();
在Hibernate中,实体类可以被定义为final。然而,如果实体类被定义为final,那么Hibernate在进行一些代理和延迟加载等操作时可能会遇到一些限制。因为Hibernate通常会创建代理类来对实体类进行一些操作,而final类无法被继承,所以可能会导致Hibernate无法生成有效的代理类或抛出异常。
通常情况下,建议不要将实体类定义为final,以免出现潜在的问题。如果需要限制对实体类的继承,可以通过其他方式来实现,如在类的设计中采用不可变对象模式、使用其他方式来限制继承等。
使用对象类型Integer允许实体属性值为null,而基础数据类型int则不允许。
对于基本数据类型int,JVM直接在栈内存中为其分配空间,因此访问速度更快。Integer对象在堆内存中分配空间,并通过引用访问。虽然现代JVM的性能优化已经使得这种差异变得很小,但在处理大量数据或高并发场景下,基本数据类型的性能优势可能会更加明显。
Spring Security 是实现权限验证的常用框架,通常需要三张表来实现基本的权限验证。
Spring Security提供了一套全面的安全服务,包括身份认证、权限授权等功能。在实现权限验证时,通常会使用到该框架提供的多种机制和扩展点。例如,通过实现UserDetailsService接口并覆写里面的用户认证方法,可以自定义用户的认证逻辑。此外,权限管理过程包括鉴权管理和授权管理,即判断用户是否有权访问某个资源以及如何将权限分配给用户。
对于权限验证所需的表结构,RBAC(Role-Based Access Control,基于角色的访问控制)模型是一个常见的设计模式。在这个模型中,通常至少需要三张表:用户表、角色表、权限表。用户表存储用户信息,角色表定义了不同的角色,而权限表则规定了不同角色可以执行的操作。除此之外,还需要两张关系表来维护用户和角色之间、角色和权限之间的关系。这些表共同构成了权限验证的基础数据结构。
在设计和实施权限验证系统时,开发者需要根据实际业务需求和技术选型来决定具体的实现方式和所需表结构。
RBAC 的核心思想是将权限赋予角色,然后将角色赋予用户。这种分离让系统管理员能够更容易地管理和控制资源的访问权限。通过 RBAC,可以对用户的权限进行集中管理,确保授权的一致性。 RBAC 主要包括三种基本权利:用户、角色和权限。当用户和角色是多对多的关系,当用户角色变更时只需要对关系进行更改即可,简化了权限管理工作。RBAC 通过模块化的方式进行权限管理,可以减少权限管理的复杂性,提高系统的安全性。
这是RBAC的基础模型。在RBAC0中,角色是权限的集合,用户则被分配到这些角色中。用户通过其角色获得访问资源的权限。RBAC0模型主要关注用户、角色和权限之间的基本关系。
RBAC1在RBAC0的基础上增加了角色继承的概念。在RBAC1中,角色可以继承其他角色的权限,形成一个角色层次结构。这种继承关系使得权限的管理更加灵活和方便,可以满足更复杂的权限控制需求。
RBAC2是RBAC的扩展模型,引入了约束的概念。这些约束可以是静态的,也可以是动态的,用于限制角色、权限和用户之间的关联关系。例如,可以设置约束来防止某个角色被赋予过多的权限,或者限制某个用户只能被分配到特定的角色中。
RBAC3结合了RBAC1和RBAC2的特性,既支持角色继承,又允许定义约束来限制权限的分配和使用。这使得RBAC3成为一个功能全面且高度可配置的权限管理模型。
在实现RBAC(基于角色的访问控制)时,需要考虑以下因素:
Java RBAC拦截器权限控制的关键代码如下:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Permission { String[] value() default {};}
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<String> 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<String> roles) { // 根据URL和角色列表判断是否有访问权限 // 这里可以根据具体的业务逻辑进行判断,例如查询数据库等 // 如果具有访问权限,返回true;否则返回false return true; }}
@Configurationpublic class WebMvcConfig implements WebMvcConfigurer { @Autowired private PermissionInterceptor permissionInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(permissionInterceptor).addPathPatterns("/**"); }}
@RestControllerpublic class UserController { @Permission({"ROLE_ADMIN", "ROLE_USER"}) @GetMapping("/users") public List<User> getUsers() { // ... }}
当用户访问/users接口时,拦截器会根据用户的角色列表判断是否具有访问权限。如果具有访问权限,则允许访问;否则返回403 Forbidden错误。
本文链接:http://www.28at.com/showinfo-26-83628-0.html81道SSM经典面试题总结
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 美团二面:SpringBoot读取配置优先级顺序是什么?
下一篇: 一篇带给你 Spring 循环依赖详解