1.什么是IOC?
IOC,即控制反转(Inversion of Control),是面向对象编程中的一种设计原则,其核心思想是将对象的创建和管理的控制权从程序代码转移到外部容器(例如IOC容器)。这样做的目的就是为了降低程序组件之间的耦合度,提高代码的模块化和可维护性。
在传统的程序设计中,我们可能会直接在代码中创建对象实例,这会导致对象之间的依赖关系紧密耦合,不易于扩展和修改。而通过IOC,对象的创建和生命周期管理由容器负责,对象之间的依赖关系通过配置来定义,容器负责将这些依赖注入到需要她们的对象中。
例如,在一个使用了IOC容器的应用程序中,如果对象A依赖于对象B,那么我们不会在A的代码中直接实例化B,而是通过配置告诉IOC容器,当需要A时,容器会自动创建B并将其注入到A中。
以下是一个简化的Java实例,展示了如何使用Spring IOC容器进行依赖注入:
// 定义一个接口
public interface MessageService {
void sendMessage(String message);
}
// 实现接口
public class EmailService implements MessageService {
public void sendMessage(String message) {
System.out.println("Email message sent: " + message);
}
}
// 依赖MessageService的类
public class MessageProcessor {
private MessageService messageService;
// 依赖注入
public void setMessageService(MessageService messageService) {
this.messageService = messageService;
}
public void processMessage(String message) {
messageService.sendMessage(message);
}
}
// 配置文件(XML或注解),定义Bean和依赖关系
// 在这里,我们告诉Spring容器MessageProcessor需要一个EmailService实例
// 使用Spring容器
public class Application {
public static void main(String[] args) {
// 获取IoC容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取Bean
MessageProcessor messageProcessor = context.getBean("messageProcessor", MessageProcessor.class);
// 使用Bean
messageProcessor.processMessage("Hello, World!");
}
}
2.Sping中依赖注入有哪几种方式?
- 构造函数注入(Constructor Injection)
- 属性注入(Field Injection),通常使用@Autowired或@Resource注解直接注入到类的成员变量
- Setter方法注入(Setter Injection)
构造函数注入是通过在类的构造函数上使用@Autowired或其他相关注解来实现;
属性注入通常是在类的成员变量上使用@Autowired;
Setter方法注入是在类的setter方法上使用相应注解
例如:
// 构造函数注入
public class MyClass {
private MyDependency myDependency;
@Autowired
public MyClass(MyDependency myDependency) {
this.myDependency = myDependency;
}
}
// 属性注入
public class MyClass {
@Autowired
private MyDependency myDependency;
}
// setter方法注入
public class MyClass {
private MyDependency myDependency;
@Autowired
public void setMyDependency(MyDependency myDependency) {
this.myDependency = myDependency;
}
}
由此可见 属性注入是最简便的 所以也是使用最多的
3.讲讲AOP
Spring AOP是Spring框架中的一项核心功能,代表Aspect Oriented Programming(面向切面编程)。它允许开发者定义跨多个对象的横切关注点(如日志、事务、安全等)并将这些关注点与业务逻辑分离。Spring AOP通过在运行时使用JDK动态代理或CGLIB来实现方法的增强,即将额外的功能添加到现有的对象中,而无须修改这些对象的代码。
- Spring AOP利用代理模式,在运行期间动态地将切面代码织入到容器对象的方法中。
- 它主要用于将非业务逻辑(如日志、监控、权限校验等)从业务逻辑中解耦,以提高代码的可重用性和降低系统的耦合性。
- AOP中的关键概念包括切面(Aspect)、切点(Pointcut)、增强(Advice)和连接点(Join point)。
- 切点定义了再何处(即在哪些方法上)需要加入增强代码,而链接点是指方法执行的时刻。
- Spring AOP只能在运行时动态代理编织,而不是编译期或类加载期的静态编织。
例如:
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("Method is going to execute!");
}
@After("execution(* com.example.service.*.*(..))")
public void logAfter() {
System.out.println("Method executed!");
}
}
本例中,LoggingAspect是一个切面,它定义了在com.example.service包下所有类的所有方法执行之前和之后应该执行的日志逻辑。@Before和@After注解定义了合适织入这些日志逻辑
4.Bean的生命周期
- 实例化:Spring容器负责创建Bean的实例。
- 依赖注入:如果Bean有依赖关系,Spring容器会在这一步进行依赖注入
- 初始化:
- 调用自定义的init方法:如果Bean实现了InitalizingBean接口,Spring容器会调用afterPropertiesSet方法。
- 执行注解@PostConstruct的方法:如果Bean中存在@PostConstruct注解的方法,Spring容器会在这一步调用该方法。
- 使用:此时,Bean已经准备好,可以被应用程序使用了。
- 销毁:
- 调用自定义的销毁方法:如果Bean实现了DisposableBean接口,Spring容器会调用destroy方法。
- 执行注解@PreDestroy的方法:如果Bean中存在@PreDestroy注解的方法,Spring容器会在销毁Bean之前调用该方法。
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class MyBean implements InitializingBean, DisposableBean {
// 构造函数
// 依赖注入
@PostConstruct
public void init() {
// 自定义初始化逻辑
}
@Override
public void afterPropertiesSet() {
// 自定义初始化逻辑
}
// 业务逻辑
@PreDestroy
public void preDestroy() {
// 自定义销毁逻辑
}
@Override
public void destroy() {
// 自定义销毁逻辑
}
}
在Spring配置中,也可以通过<bean>标签的init-method和destroy-method属性来指定初始化和销毁方法。
5.springBean的作用域
- 单例(Singleton):默认作用域,在整个Spring应用上下文中只创建一个Bean实例,所有对该类型Bean的请求都会返回同一个实例。
@Service
public class SingletonService {
}
- 原型(Prototype):每次请求都会创建一个新的Bean实例。
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeService {
}
- 请求(Request):在Web应用中,为每个HTTP请求创建一个Bean实例。
- 会话(Session):在Web应用中,为每个会话创建一个Bean实例。
- 全局会话(GlobalSession):在portlet上下文中使用,类似于会话的作用域。
- 应用(Application):在ServletContext的生命周期内,创建一个Bean实例。
- 自定义作用域:用户可以通过实现Scope接口来自定义作用域。
6.spring事务的原理
Spring事务的原理基于Java事务API(JTA)、JDBC、Hibernate和JPA等事务API,提供了一套一致的编程模型。Spring事务管理的核心是事务管理器(TransationManager),它负责协调事务的开始、提交、和回滚。 在Spring中,事务可以分为两类
- 编程式事务:开发者需要手动编写代码来控制事务的开始、提交和回滚。这种方式比较灵活,但代码耦合度高。
- 声明式事务:通过AOP(面向切面编程)实现,使得事务管理代码与业务逻辑代码解耦。在方法上使用@Transational注解即可开启声明式事务。Spring容器会自动为该方法创建事务代理,当方法执行成功时提交事务,出现异常时回滚事务。
以下是Spring声明式事务的关键组件和原理:
- 事务代理:Spring容器会标有@Transactional注解的方法创建事务代理对象,代理对象负责在方法执行前和执行后添加事务管理逻辑。
- 事务属性:@Transactional注解可以配置事务的各种属性,如隔离级别(读未提交、读已提交、可重复读、可串行化)、传播行为、超时时间等。
- 事务管理器:负责协调事务的开始、提交和回滚。不同的事务管理器对应不同的数据源和事务类型。
- 事务传播行为:定义了事务方法如何与外部事务相互作用,如REQUIRES_NEW、REQUIRED等。
以下是一个简单的声明式事务实例:
@Service
public class MyService {
@Transactional
public void doBusiness() {
// 数据库操作1
// 数据库操作2
// ...
}
}
在这个实例中,doBusiness方法上加了@Transactional注解确保了方法中的所有数据库操作要么全部成功,要么在出现异常时全部回滚。
7.spring中使用了哪些设计模式
- 观察者模式:用于事件发布(ApplicationEvent)和监听(ApplicationListener),允许框架的使用者在不需要修改源码的情况下,基于事件进行定制化的响应。
- 模板模式:sping中很多带有template后缀的类都是模板类,如JdbcTemplate和RedisTemplate。这些模板类通常使用回调(Callback)机制来实现,允许用户自定义特定行为。
- 职责链模式:Spring中,职责链模式体现在拦截器(Interceptor)的概念上,拦截器允许你对请求进行一系列的处理,形成一个处理链。
- 代理模式:Spring AOP是代理模式的一个经典应用,通过代理对象,可以在不修改原有代码的情况下,对方法进行增强和扩展。
- 单例模式:用于确保Bean的唯一性,Spring默认情况下创建的Bean是单例的。
- 工厂模式和抽象工厂模式:用于创建和管理对象,Spring容器就是一个大型的工厂,负责创建和配置Bean。
- 策略模式:用于封装算法,使它们可以互换。在Spring中,可以通过策略模式来实现不同的业务逻辑。
- 适配器模式:用于将不兼容接口进行适配,Spring中可以用适配器模式将一个接口适配到另外一个接口。
- 装饰器模式:用于在不修改原有类的情况下,动态地添加新的功能。
8.简述servletContext的生命周期
-
启动阶段:
- 当Servlet服务器(如Tomcat)启动Web项目时,首先会读取
web.xml中的context-param节点,获取Web应用的全局参数。 - 接着,服务器会创建一个
ServletContext实例,这个实例对于整个Web应用是全局有效的。 context-param中的参数会被转换为键值对,并存储在ServletContext中。
- 当Servlet服务器(如Tomcat)启动Web项目时,首先会读取
-
监听器初始化:
- 服务器会创建在
web.xml中定义的监听器(Listener)实例。如果监听器实现了ServletContextListener接口,它的contextInitialized(ServletContextEvent event)方法会被调用。在这个方法中,可以通过event.getServletContext().getInitParameter("name")获取到存储在ServletContext中的参数。
- 服务器会创建在
-
过滤器初始化:
- 在监听器初始化完成后,服务器会初始化在
web.xml中定义的Filter过滤器。
- 在监听器初始化完成后,服务器会初始化在
-
Servlet初始化:
- 如果Servlet在
web.xml中配置了load-on-startup参数,且值为正数,那么Servlet将在服务器启动时初始化。load-on-startup的值越小,优先级越高。
- 如果Servlet在
-
使用阶段:
- 在Web应用运行期间,
ServletContext可以被用来存储共享资源或信息,并且在整个Web应用中都可以访问这些资源或信息。
- 在Web应用运行期间,
-
销毁阶段:
- 当Web应用被关闭或服务器停止时,
ServletContext的destroy()方法会被调用,标志着其生命周期的结束。
- 当Web应用被关闭或服务器停止时,
需要注意的是,ServletContext的销毁通常与Web应用的停止或重新加载相关。在Tomcat中,如果开启了热加载(通过设置context.xml中的reloadable参数为true),那么在重新加载Web应用时,ServletContext不会被销毁,但是会重新创建一个新的实例。
以下是web.xml中设置context-param和listener的一个示例:
<context-param>
<param-name>param1</param-name>
<param-value>value1</param-value>
</context-param>
<listener>
<listener-class>com.example.MyServletContextListener</listener-class>
</listener>
在这个示例中,MyServletContextListener类应该实现ServletContextListener接口,以便能够在Web应用启动时接收上下文初始化事件。
9.事务会在什么情况下失效?
1.编程式事务管理使用不当:如果代码中没有正确地开启和提交(或回滚)事务,那么事务可能不会按预期工作。
2.声明式事务管理配置错误:如果事务的传播行为配置不正确,或事务的隔离界别设置不当,可能导致事务行为不符合预期。
3.方法调用不符合事务传播规则:在使用Spring的声明式事务时,如果一个事务方法内部调用另一个事务方法,但传播行为配置不当,可能导致内部方法的事务被忽略。
4.异常处理不当:如果在事务方法中没有正确处理异常,比如没有捕获特定的异常或没有在捕获异常后进行事务回滚,那么事务可能不会回滚。
5.多数据源事务管理问题:在涉及到多数据源的场景中,如果在不同的数据源上没有正确的开启或同步事务,可能导致事务部分失效。例如:
- 在所有分库上都开启事务会浪费资源,而且可能导致性能问题。
- 使用延迟事务时,如果在执行SQL前没有正确地开启事务,那么事务可能不会生效。
10.spring是如何解决循环依赖的?
Spring解决循环依赖的问题主要是通过提前暴露Bean的早期引用(earlyreference),然后使用这个早期引用来打破循环。以下是具体的解决步骤:
- 当Spring容器创建Bean时,它会先实例化Bean(即创建一个对象,但还没有填充其属性)。
- 在实例化之后,如果这个Bean被其他Bean所依赖,Spring容器会将这个早期的、未完全初始化的Bean实例的引用放入一个名为earlySingletonObjects的缓存中。
- 然后,Spring继续对Bean进行属性注入。如果注入的属性依赖其他的Bean,Spring容器会首先检查earlySingletonObjects缓存中是否存在所需Bean的早期引用。
- 如果存在早期引用,Spring容器将使用这个引用来注入,而不是等待依赖的Bean完全初始化。这样,即使存在循环依赖,也能通过这个早期引用来打破循环。
- 一旦所有依赖的Bean都完成了初始化,Spring容器会再次回到原始Bean,完成剩余的属性注入。
这个过程的关键是Spring容器支持将单例作用域Bean的创建分为实例化和属性注入两个阶段,并且在实例化之后立即提供早期引用。
以下是一个简化的代码示例,展示了如何通过 setAllowCircularReferences 来配置循环依赖:
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.setAllowCircularReferences(true); // 允许循环依赖
// ... 注册 Bean 定义
// 开始 Bean 的创建过程
AbstractBeanFactory beanFactory = (AbstractBeanFactory) ...
beanFactory.preInstantiateSingletons();
请注意,Spring 只能解决单例作用域 Bean 的循环依赖问题,对于原型作用域 Bean,Spring 容器无法解决循环依赖问题。此外,建议在设计应用时尽量避免循环依赖,以保持代码的清晰和可维护性。
11.什么是SpringMVC?
SpringMVCs Spring的一个模块,基于MVC的一个框架,无需中间整合层来整合。
12.Spring MVC的优点?
- 它是基于组件技术的,全部的应用对象,无论控制器还是视图,还是业务对象之类的都是java组件,并且和Spring提供的其他基础结构紧密集成。
- 不依赖于Servlet API
- 可以任意使用各种视图技术,而不仅限于JSP
- 支持各种请求资源的映射策略
- 易于扩展
13.SpringMVC的工作原理
- 用户发送请求到前端控制器DispatcherServlet
- DispatcherServlet查询handlerMapping找到处理请求的Controller
- Controller调用业务逻辑后,返回ModelAndView
- DispatcherServlet查询ModelAndView,找到指定视图
- 视图将结果返回到客户端
14.SpringMVC核心组件有哪些
- Model(模型):用于封装业务数据和逻辑
- View(视图):用于展示数据的页面,可以是JSP、HTML、XML等
- Controller(控制器):负责接收请求,处理请求,并返回响应
15.什么是SpringBoot?
SpringBoot是构建在Spring Framework之上的一个开源的Java-based框架,旨在简化Spring应用的创建和部署过程。它使用了"约定优于配置"的原则,通过自动配置和起步依赖(Starter Dependencies)极大地减少了项目配置的复杂性 SpringBoot主要特点包括:
- 自动配置:SpringBoot可以根据项目中添加的依赖自动配置Spring应用。它能够智能地猜测和配置你可能会使用的Bean。
- 起步依赖:通过提供一系列的"起步依赖",SpringBoot简化了依赖管理。这些起步依赖被设计为能够自动提供相关的库和框架,避免了开发者手动添加依赖和版本冲突的问题。
- 内嵌的Web服务器:Spring Boot可以内嵌Tomcat、Jetty或Undertow等Web服务器,这使得开发者可以非常容易地创建独立的HTTP服务。
- 简化Maven和Gradle配置:Spring Boot提供了许多插件和配置来简化Maven和Gradle的构建配置。
16.Spring Boot自动配置是如何工作的?
Spring Boot 自动配置依赖于Spring框架的条件注解(@Conditional)和类路径中的jar依赖。它通过@EnableAutoConfiguration注解启用,自动配置会尝试根据添加到项目中的jar依赖自动配置Spring应用。例如,如果类路径上存在H2数据库的jar,Spring Boot会自动配置内存中的H2数据库。
17.Spring Boot的起步依赖有什么作用?
- 起步依赖(Starter Dependencies)是一组被预配置的依赖集合,它们可以简化构建配置。通过添加一个起步依赖,Spring Boot会自动为你的应用添加所有相关的依赖,无需手动添加每个库。
18.Spring Boot中如何集成Redis
在Spring Boot中集成redis,通常需要添加spring-boot-starter-data-redis依赖,然后通过配置文件(application.properties或application.yml)配置Redis服务器的链接信息。Spring Boot会自动配置RedisTemplate和SpringRedisTemplate,这两个模板可以用于操作Redis数据。
19.简述Sping Boot和Spring MVC的关系
- Spring Boot是Spring框架的一个模块,它简化了Spring应用的配置和部署。
- Spring MVC是Spring框架的一部分,用于构建基于MVC架构的Web应用。
- Spring Boot提供了对Spring MVC的自动配置,使得创建Web应用更加快捷。
20.模板设计模式在Spring Boot中的应用场景是什么
- 模板设计模式是一种行为设计模式,它定义了一个操作中的算法的骨架,将一些步骤延迟到子类中实现。在Spring Boot中,模板设计模式被用于许多地方,如JdbcTemplate、RedisTemplate等,它们提供了通用的操作流程,而具体的数据库或缓存则由用户根据需求实现。
21.MyBatis的一级缓存和二级缓存有什么区别?
- 缓存范围:
- 一级缓存:也被称为本地缓存,默认情况下是开启的。它是在同一个SqlSession范围内的缓存,这意味着在同一个SqlSession中所有查询操作都会共享这个缓存。
- 二级缓存:也被称为全局缓存,它通常是基于namespace级别的缓存,可以被多个SqlSession共享。这意味着不同SqlSession中执行的同名查询操作可以共享这个缓存。
- 数据的过期策略:
- 一级缓存:通常情况下,当SqlSession提交或关闭时,一级缓存中的数据会被清除。
- 二级缓存:它的过期策略更为灵活,通常可以通过配置来设定缓存的过期时间或者基于其他策略(如LRU)来决定何时清除缓存中的数据。
- 数据一致性问题:
- 一级缓存:由于其作用域小,不存在并发问题,因此数据一致性问题较为简单。
- 二级缓存:由于可能被多个SqlSession共享,因此需要考虑并发读写的问题,可能需要配置一些复杂的缓存策略来保证数据的唯一性
- 配置和使用:
- 一级缓存:通常不需要特别的配置,它是Mybatis的默认行为。
- 二级缓存:它需要显式地在映射文件中进行配置,并且可能需要考虑序列化的问题,因为二级缓存的数据可能会被存储在不同的JVM进程中。
SqlSession范围指的是在MyBatis框架中,由同一个SqlSessionFactory创建的SqlSession实例的生命周期范围内。在这个范围内,所有的数据库操作都被视为同一个会话,共享一级缓存(也称为本地缓存)。
22.MyBatis如何处理事务?
- 使用Spring的声明式事务:
- 通过在方法上添加@Transactional注解来声明事务边界。
- 确保方法是通过Spring容器管理的Bean进行调用的,并且方法是public的。
- 配置事务的传播行为(Propagation)、隔离级别(Isolation)、超时时间(Timeout)以及回滚规则(rollbackFor和noRollbackFor)。
@Transactional
public void doBusiness() {
// MyBatis数据库操作
sqlSession.update("update some_table");
// 如果在这个方法中抛出异常,事务将会回滚
}
- 手动处理事务:
- 在MyBatis中,可以通过编程的方式手动控制事务的开始、提交和回滚。
- 通过SqlSessionFactory.openSession()获取SqlSession对象,然后使用SqlSession提供的事务管理方法。
SqlSessionFactory sqlSessionFactory = ...;
try(SqlSession sqlSession = sqlSessionFactory.openSession()) {
// 开启事务
sqlSession.beginTransaction();
// 执行数据库操作
sqlSession.update("update some_table");
// 提交事务
sqlSession.commit();
} catch (Exception e) {
// 处理异常,回滚事务
sqlSession.rollback();
}