0、什么是spring
Spring是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。Spring框架是为了解决企业应用开发的复杂性而创建的,旨在实现敏捷开发的应用型框架。Spring是一个集成各类型工具的开源容器框架,通过核心的Bean factory实现底层类的实例化和生命周期的管理。在整个框架中,各类型的功能被抽象成一个个的Bean,以实现各种功能的管理,包括动态加载和切面编程。此外,Spring的核心技术包括IoC(控制反转)和AOP(面向切面编程),用于实现解耦合。因此,可以将Spring理解为一个包含IoC和AOP技术的开源容器框架,用于简化企业应用开发的过程。
1、IOC控制反转概念
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其核心思想是将原本由程序代码直接操控的对象调用权交给第三方(容器)来控制,通过容器来实现对象的创建、装配和管理。这种方式实现了对象控制权的转移,从程序代码本身转移到了外部容器,从而实现了控制反转。
IoC的实现方式有多种,其中最常见的是依赖注入(Dependency Injection,简称DI)。依赖注入是指程序运行过程中,若需要调用另一个对象协助时,无需在代码中创建被调用者,而是由外部容器来创建并传递给程序。这样,程序代码的依赖关系就由容器来管理,从而实现了对象之间的解耦。依赖注入是目前最优秀的解耦方式之一,它可以大大降低代码之间的耦合度,提高代码的可重用性和可扩展性。
除了依赖注入外,还有一种实现方式是依赖查找(Dependency Lookup)。这种方式是指容器提供回调接口和上下文环境给组件,让组件自己通过容器来获取所需要的依赖对象。这种方式相对于依赖注入来说,需要组件自己主动去查找依赖对象,因此耦合度相对较高。
2、从Spring容器中获取对象的方式
在Spring框架中,从容器中获取对象主要有以下几种方式:
-
使用
ApplicationContext
:
这是最常见的方式。一旦配置了Spring容器(通常是通过XML文件或Java配置类),就可以使用ApplicationContext
来获取容器中的bean。ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); MyBean myBean = (MyBean) context.getBean("myBean");
或者使用注解配置时的Java配置类:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); MyBean myBean = context.getBean(MyBean.class);
-
使用
@Autowired
注解:
在Spring管理的bean中,你可以使用@Autowired
注解来自动装配依赖。Spring容器会自动查找并注入匹配的bean。@Component public class MyComponent { @Autowired private MyBean myBean; // ... }
-
使用
@Resource
或@Inject
注解:
这两个注解与@Autowired
类似,也可以用来自动装配依赖,但它们来自不同的规范(JSR-250和JSR-330)。@Component public class MyComponent { @Resource(name = "myBean") private MyBean myBean; // ... }
或者使用
@Inject
:@Component public class MyComponent { @Inject private MyBean myBean; // ... }
3、@Autowared 和 @Resource的区别
@Autowired和@Resource都是用于依赖注入的注解,但它们来自不同的定义,并具有一些关键的区别。
- 来源与提供方:
- @Autowired:这是由Spring框架提供的注解,专门用于Spring的依赖注入。
- @Resource:这是由JSR-250规范定义的注解,是Java的标准规范之一。它在Java EE应用中也是常见的,并且可以在非Spring环境中使用。
- 注入规则与查找顺序:
- @Autowired:默认根据类型(byType)进行自动装配。如果存在多个相同类型的Bean,则需要配合@Qualifier注解来指定Bean的名称,或者可以使用@Primary注解来指定首选的Bean。当根据类型无法找到匹配的Bean时,会尝试根据名称(byName)进行查找,但这并不是其默认行为。
- @Resource:默认根据名称(byName)进行查找并注入依赖。如果找不到指定名称的Bean,那么它会回退到根据类型(byType)进行查找。这个注解支持通过其name属性显式指定Bean的名称。
- 支持的参数:
- @Autowired:只支持设置一个参数,即required,用于指定依赖是否必须存在。不过,从Spring 4.3开始,这个属性已经不再需要,因为可以通过将字段、构造函数或方法标记为Optional来表示依赖是非必需的。
- @Resource:支持更多的参数,包括name、type、authenticationType、shareable、description、mappedName和lookupName。然而,在实际应用中,大多数开发者通常只使用name和type这两个参数。
- 依赖注入的用法支持:
- @Autowired:支持属性注入、构造方法注入和Setter方法注入。它可以应用于字段、构造函数和Setter方法上。
- @Resource:只支持属性注入和Setter方法注入。它不能应用于构造函数上,但可以用于字段或Setter方法上。
4、BeanFactory和ApplicationContext的关系
- BeanFactory:
- BeanFactory是Spring框架中的基础容器,负责管理和配置应用程序中的Bean。
- 它提供了基本的IOC(控制反转)功能,允许开发者通过配置文件或注解定义Bean,并由容器负责实例化和依赖注入。
- BeanFactory主要处理Bean的生命周期管理、依赖查找等核心功能。
- ApplicationContext:
- ApplicationContext是BeanFactory的子接口,也可以看作是一个更高级的容器。它继承了BeanFactory的所有功能,并在此基础上进行了大量的扩展。
- ApplicationContext添加了更多企业级的功能,如AOP(面向切面编程)支持、事件传播、资源加载、国际化等。
- 与BeanFactory相比,ApplicationContext提供了更丰富的功能集和更便捷的使用方式。
关系:
- ApplicationContext是BeanFactory的子类,因此它继承了BeanFactory的所有功能。实际上,在ApplicationContext的实现中,通常会包含一个BeanFactory实例,用于处理基础的Bean管理任务。
- ApplicationContext对BeanFactory进行了功能扩展。除了提供基本的IOC功能外,ApplicationContext还添加了事件处理、资源加载、国际化等高级功能。这些功能使得ApplicationContext成为一个更全面、更易于使用的容器。
- 在使用上,开发者通常会直接与ApplicationContext打交道,而不是直接使用BeanFactory。这是因为ApplicationContext提供了更高级别的抽象和更便捷的功能。然而,在某些特殊情况下,如果需要更底层的控制或访问BeanFactory提供的特定功能,开发者仍然可以直接访问底层的BeanFactory实例。
5、spring aop概念
Spring AOP(Aspect-Oriented Programming)是Spring框架中的一个重要模块,它提供了面向切面编程的实现。面向切面编程是一种编程范式,它通过预编译方式和运行期动态代理,实现程序功能的统一维护。这种技术是对OOP(面向对象编程)的补充和完善,它使得开发者可以将应用程序分解为不同的切面,从而实现业务逻辑各部分之间的解耦,提高程序的可重用性和开发效率。
在Spring AOP中,切面(Aspect)是一个核心概念,它表示应用程序中的一个模块或关注点。切面定义了通知(Advice)和切点(Pointcut),通知是切面中的具体实现逻辑,而切点则定义了通知应该在何时被触发。通过将这些关注点模块化,开发者可以更加灵活地处理应用程序中的交叉关注点,如日志记录、事务管理、安全控制等。
此外,Spring AOP还支持多种代理方式,包括JDK动态代理和CGLIB代理。JDK动态代理只能针对接口进行代理,而CGLIB代理则可以针对类进行代理。这使得Spring AOP可以更加灵活地应用于不同的场景。
6、AspectJ切点表达式
切点表达式是AspectJ中用来指定切点的一种强大语法。这些表达式可以非常精确地描述哪些方法执行应该被拦截,以便织入额外的逻辑。切点表达式可以基于方法的签名、注解、类名、包名等多种条件进行匹配。
以下是一些常见的AspectJ切点表达式示例:
-
执行方法:
execution(* com.example.MyClass.myMethod(..))
这个表达式匹配
com.example.MyClass
类中的myMethod
方法的所有执行,无论该方法的参数是什么。 -
匹配任何返回类型的方法:
execution(* com.example.*.*(..))
这个表达式匹配
com.example
包下任何类的任何方法。 -
匹配特定注解的方法:
@annotation(com.example.MyAnnotation)
这个表达式匹配任何被
@com.example.MyAnnotation
注解的方法。 -
匹配特定参数的方法:
execution(* com.example.MyClass.myMethod(String, int))
这个表达式匹配
com.example.MyClass
中的myMethod
方法,但仅当该方法接受一个String
和一个int
作为参数时。 -
匹配包及其子包中的所有方法:
within(com.example..*)
这个表达式匹配
com.example
包及其任何子包中的所有类的所有方法。 -
匹配特定类型的参数:
args(com.example.MyClass)
这个表达式匹配任何方法,只要其参数列表中包含
com.example.MyClass
类型的参数。 -
组合表达式:
你可以使用&&
(并且)、||
(或者)和!
(非)来组合多个切点表达式。@annotation(com.example.MyAnnotation) && execution(* *(..))
这个表达式匹配任何被
@com.example.MyAnnotation
注解的方法的执行。
请注意,上面的例子只是为了演示切点表达式的语法,实际使用中可能需要根据你的应用程序的特定需求进行调整。在Spring AOP中,这些切点表达式通常用在@Pointcut
注解中,然后在通知方法上通过引用这些点来指定应该拦截哪些方法调用。
7、事务ACID原则
事务的ACID原则是指数据库事务正常执行的四个基本要素,它们分别是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。这些原则保证了事务在数据库中的正确处理,确保数据的完整性和一致性。
- 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。也就是说,一个事务不可能只执行了一部分操作就停止了。比如,从取款机取钱的事务可以分成两个步骤:划卡和出钱。这两个步骤必须同时完成,要么就不完成,不能只完成其中一个步骤。
- 一致性(Consistency):一致性是指在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的任何数据都必须满足所有设置的规则,包括数据的约束条件、级联更新、触发器等。例如,完整性约束了a+b=10,一个事务改变了a,那么b也应该随之改变,以保持数据的一致性。
- 隔离性(Isolation):多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
- 持久性(Durability):持久性意味着一旦事务被提交,那么它对数据库中数据的改变就应该是永久性的。接下来的操作或故障不应该对其有任何影响。这意味着,即使在系统崩溃、宕机或者发生其他故障的情况下,数据也不会丢失或回滚到事务开始前的状态。
8、spring事务的传播特性
Spring事务的传播特性定义了在多个事务方法相互调用时,事务如何在这些方法间进行传播。Spring支持7种事务传播行为,它们分别是:
- PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务;如果已存在一个事务中,则加入到这个事务中。这是最常见的选择。
- PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY:使用当前事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,就执行
REQUIRED
行为。这是一个嵌套事务,它使用了一个单独的事务,但可以被回滚到外部事务的保存点。如果外部事务失败,那么内部事务也会被回滚。
这些传播特性允许开发者在复杂的业务逻辑中灵活地控制事务的传播方式,以满足不同的业务需求。在选择适当的事务传播特性时,需要考虑事务的隔离级别、只读属性和超时设置等因素,以确保数据的完整性和一致性。
9、springMVC中DispatcherServlet概念
Spring MVC中的DispatcherServlet
是一个前端控制器(Front Controller),它是Spring MVC框架的核心组件之一。DispatcherServlet
的主要职责是接收用户的请求并将其分发给适当的处理程序(如控制器)进行处理,最终将处理结果返回给用户。
以下是DispatcherServlet
在Spring MVC中的主要功能:
- 请求接收:
DispatcherServlet
负责接收客户端发送的HTTP请求。 - 请求解析:它解析请求,包括URL和HTTP方法(GET、POST等),并根据请求的信息查找对应的处理器映射(Handler Mapping)。
- 处理器映射:
DispatcherServlet
查询一个或多个处理器映射来确定哪个控制器(通常是一个带有@Controller
注解的Java类中的方法)应该处理该请求。 - 请求处理:一旦找到了适当的控制器方法,
DispatcherServlet
就会将请求传递给该方法进行处理。这通常涉及到调用业务逻辑服务、处理表单数据、与数据库交互等。 - 视图解析:控制器处理请求后,通常返回一个逻辑视图名。
DispatcherServlet
会查询视图解析器(View Resolver)来将这个逻辑视图名解析为具体的视图技术(如JSP、Thymeleaf等)。 - 视图渲染:一旦视图被解析,
DispatcherServlet
就会将模型数据传递给视图,并指示视图进行渲染。视图使用模型数据来生成最终的HTML响应。 - 响应返回:最后,
DispatcherServlet
将渲染后的视图返回给客户端作为HTTP响应。
在Spring MVC的配置中,DispatcherServlet
通常被配置为一个Servlet,并在web应用的web.xml
文件中声明,或者通过Java配置(如使用@WebServlet
注解或ServletRegistrationBean
)进行注册。它还可以配置多个上下文参数,如上下文配置位置、命名空间等。
通过DispatcherServlet
,Spring MVC实现了一个清晰、模块化的请求处理流程,使得开发者能够更容易地构建可扩展的web应用程序。
10、spring中常用的注解
Spring框架提供了许多注解来简化Java开发,以下是一些常用的Spring注解:
-
@Autowired
- 用途:自动装配Spring Bean。可以在构造器、方法、参数、成员变量和注解上使用。
- 当Spring发现@Autowired注解时,会自动在Spring上下文中找到匹配的Bean,并注入到被注解的字段、构造器或方法中。
-
@Component
- 用途:泛指各种组件,当类不属于各种明确归类的时候(例如,@Service, @Controller, @Repository等),可以使用@Component来标注这个类。
- 被@Component注解的类会被Spring自动检测到,并加入到Spring的上下文中。
-
@Controller
- 用途:标注一个类为Spring MVC的控制器。
- 被@Controller注解的类中的方法通常处理HTTP请求。
-
@Service
- 用途:标注一个类为业务逻辑层的组件。
- 用于处理业务逻辑,通常会被@Autowired到控制器中。
-
@Repository
- 用途:标注一个类为数据访问对象,即DAO组件。
- 主要用于与数据库交互,执行CRUD操作。
-
@RequestMapping
- 用途:映射HTTP请求到特定的处理器函数上,既可以用在类级别上,也可以用在方法级别上。
- 类级别上的@RequestMapping用于指定该类中的所有方法的基础路径。
- 方法级别上的@RequestMapping用于指定具体的方法处理特定的HTTP请求。
-
@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping
- 用途:分别对应HTTP的GET、POST、PUT、DELETE、PATCH方法,是@RequestMapping的快捷方式。
-
@PathVariable
- 用途:用于获取URI模板变量(即URL路径中的参数)。
-
@RequestParam
- 用途:用于获取查询参数(即URL问号后面的参数)。
-
@RequestBody
- 用途:用于读取请求体中的数据,常用于处理POST请求中的JSON数据。
-
@ResponseBody
- 用途:表示该方法的返回结果直接写入HTTP响应体中,一般用于返回JSON数据或XML数据。
-
@RestController
- 用途:组合注解,相当于@Controller和@ResponseBody的组合。标注的类中的所有方法的返回值都会自动转换为JSON或XML格式的响应数据。
-
@Configuration
- 用途:声明当前类是一个配置类,用于定义Spring的Bean。
-
@Bean
- 用途:注解在方法上,声明当前方法的返回值是一个Bean,需要被Spring管理。通常用在@Configuration类中。
-
@Scope
- 用途:定义Bean的作用域,常见的有单例(singleton)和多例(prototype)。
-
@Qualifier
- 用途:当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定需要注入的是哪一个Bean。常与@Autowired配合使用。
-
@Profile
- 用途:表示当一个或多个指定的profile处于激活状态时,一个组件才能被注册。
-
@EnableScheduling
- 用途:在配置类上使用,开启对注解@Scheduled的支持,使得标注了@Scheduled的方法能够被执行。
-
@Scheduled
- 用途:标注一个方法为计划任务,使得该方法能够按照指定的cron表达式或固定间隔被执行。
这些注解大大简化了Spring应用程序的开发过程,使得开发者能够更专注于业务逻辑的实现,而不用过多地关注底层的配置和细节。
11、spring如何解决循环依赖问题?
Spring框架在解决循环依赖(circular dependency)问题时,主要采用了以下两种方法:
- 构造器注入(Constructor-based Injection)与setter注入(Setter-based Injection):
- 构造器注入:当使用构造器注入时,如果两个或多个Bean彼此依赖,并且都通过构造器注入,那么Spring在初始化这些Bean时会遇到问题,因为它无法同时构造所有这些Bean。这种情况下,循环依赖问题不能通过构造器注入解决。
- Setter注入:与构造器注入不同,Setter注入允许Bean在被构造后,再通过setter方法设置其依赖。这给了Spring解决循环依赖的机会,因为它可以先构造Bean,然后再注入依赖。
- 三级缓存:
在Spring框架中,三级缓存是为了解决循环依赖问题而引入的一个关键机制。当Spring IoC容器在创建Bean的过程中遇到循环依赖时,它依赖这个三级缓存来确保Bean能够按照预期被正确地实例化和初始化。
- 一级缓存:单例池(Singleton Objects Cache)
这是最常见的缓存,其中存放了已经完全初始化好的Bean实例。当一个Bean完成其生命周期的所有步骤(包括实例化、属性填充、初始化等)后,它就会被放入这个单例池中。后续如果有其他Bean引用这个Bean,直接从单例池中获取即可。
- 二级缓存:早期对象缓存(Early Singleton Objects Cache)
当一个Bean实例被创建(通过构造器或工厂方法),但还没有进行属性填充和初始化回调(如@PostConstruct
或实现InitializingBean
接口的afterPropertiesSet
方法)时,它会被放入这个早期对象缓存中。这个缓存用来解决循环依赖中,当Bean A引用Bean B,同时Bean B也引用Bean A的情况。如果Bean A在属性填充阶段发现自己依赖Bean B,而此时Bean B也正在创建中,Spring会检查这个早期对象缓存,如果Bean B的早期对象已经存在,则先注入给Bean A,从而打破了循环依赖。
- 三级缓存:单例工厂缓存(Singleton Factories Cache)
这其实不是一个真正的缓存存放实例化的对象,而是存放创建Bean实例的工厂对象。这个工厂对象是一个实现了ObjectFactory
接口的匿名内部类,其getObject()
方法会返回Bean的早期引用。当Bean还在创建过程中,即它的构造器已经被调用,但还没有完成初始化时,Spring会将这个工厂对象放入三级缓存中。如果此时有其他Bean引用了这个正在创建的Bean,Spring会使用这个工厂对象来获取Bean的早期引用,并通过AOP
代理(如果需要的话)暴露给引用的Bean。这样,即使Bean还没有完全初始化,其他Bean也可以引用它,从而解决了循环依赖问题。
Spring通过这三个级别的缓存协同工作,确保了即使在存在循环依赖的情况下,Bean也能够被正确地创建和初始化。然而,最佳实践仍然是尽量避免设计出循环依赖的Bean,因为循环依赖可能表明应用程序的设计存在潜在的结构问题。
12、@Import注解
1. 导入普通类
可以直接导入普通的类,也可以导入被@Configuration
注解的类。这些类会被加载到Spring的IoC容器中,并被管理为一个Bean。
@Configuration
public class MyConfiguration {
// ... 其他bean定义 ...
}
@Import(MyConfiguration.class)
public class AnotherConfiguration {
// ... 可以在这里使用MyConfiguration中定义的bean ...
}
2. 导入ImportSelector
接口的实现类
主要用于收集需要导入的配置类,会将selectImports
方法返回的id对应的类注入到Spring容器中。这种方式常用于Spring Boot的自动化配置和@EnableXXX
注解中,可以根据参数来决定向容器中注入哪些Bean。
首先,创建一个ImportSelector
的实现:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 根据某些条件选择需要导入的类
return new String[]{MyConfiguration.class.getName()};
}
// 可以添加额外的逻辑,比如基于环境属性来决定导入哪些类
@Override
public Predicate<String> getExclusionFilter() {
// 返回永远为false的Predicate,表示不排除任何类
return className -> false;
}
}
然后,在配置类中使用@Import
导入这个ImportSelector
:
@Import(MyImportSelector.class)
public class MainConfiguration {
// ... MainConfiguration的其他内容 ...
}
3. 导入ImportBeanDefinitionRegistrar
接口的实现类
创建一个ImportBeanDefinitionRegistrar
的实现:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 手动注册bean定义
RootBeanDefinition beanDefinition = new RootBeanDefinition(MyBean.class);
registry.registerBeanDefinition("myBean", beanDefinition);
}
}
然后在配置类中使用@Import
导入这个ImportBeanDefinitionRegistrar
:
@Import(MyImportBeanDefinitionRegistrar.class)
public class YetAnotherConfiguration {
// ... YetAnotherConfiguration的其他内容 ...
// 由于MyBean是通过ImportBeanDefinitionRegistrar手动注册的,
// 所以可以在这里通过@Autowired等方式引用MyBean
}
请注意,这些代码示例仅用于演示@Import
注解的用法,并可能需要根据您的具体需求进行调整。在实际应用中,您可能需要考虑更多细节,如包扫描、组件扫描、环境属性等。