Spring面试

一、对Spring的理解

(一)Spring的发展史

Spring版本发展节点

(二)Spring的体系结构

Spring的体系结构

(三)Spring相关组件

1.Spring和SpringMVC的关系

2.Spring和SpringBoot的关系

3.Spring和SpringCloud的关系

4.Spring和SpringSecurity的关系

5. …

二、对Spring IOC的理解

(一)IOC的应用

1.Bean的定义

基于xml配置的方式;基于配置类(@Configuration注解)的方式;基于@Component注解。

2.Bean的发现

(1)@Resource:按名称注入
(2)@Autowired:按类型注入(同一类型匹配多个bean时使用@Primary作用的bean)

(二)IOC的原理

1.Bean的管理

(1)Bean的定义

通过xml配置的方式或者注解的方式定义bean。

(2)Bean的加载

容器启动时会加载配置文件并将bean的定义信息封装在BeanDefinition对象中。

(3)Bean的存储

通过BeanDefinitionRegistry接口提供的方法将bean名称和其对应的BeanDefinition对象存储在容器属性beanDefinitionMap和beanDefinitionNames两个集合中。在beanDefinitionMap这个HashMap集合中以bean名称为key、以bean名称对应的BeanDefinition为value进行存储;beanDefinitionNames这个ArrayList集合中存储bean的名称。

(4)Bean的实例化

容器启动完成后会先实例化一部分Spring框架内部需要的实例对象并存入Spring一级缓存中,然后再实例化剩下的自定义的bean实例并同样存入Spring一级缓存中。bean对象分为单例和多例,多例bean不会存入Spring一级缓存,只是在需要用到实例对象时才通过getBean()方法完成对象的实例化。

①Spring初始化过程

Spring初始化过程

三、Spring Bean的生命周期

(一)创建前准备阶段

创建前准备阶段

这个阶段的主要作用是在bean开始加载之前,从上下文和bean的相关配置中查找有关bean的扩展实现。比如像init-method这个容器初始化bean时调用的方法;destroy-method容器销毁bean时调用的方法;以及BeanFactoryPostProcessor这类的bean加载过程中的前置和后置处理。这些类或者配置其实是Spring提供给开发者用来实现bean加载过程中的扩展机制,在很多和Spring集成的中间件中比较常见,比如Dubbo。

(二)创建实例阶段

创建实例阶段

这个阶段主要是通过反射来创建bean的实例对象,并且扫描和解析bean声明的一些属性。

(三)依赖注入阶段

依赖注入阶段

如果被实例化的bean存在依赖其他bean的情况,则需要对依赖bean进行对象注入,比如@Autowired、@Setter注解等常见的依赖注入配置形式。同时,在这个阶段会触发一些扩展调用,比如常见的扩展类:BeanPostProcessors(用来实现bean初始化前后的扩展回调)、InitializingBean(通过afterPropertiesSet()方法对初始化过程中的bean进行属性设置)以及BeanFactoryAware等等。

(四)容器缓存阶段

容器缓存阶段

容器缓存阶段主要是把bean保存到容器以及Spring缓存中,这个阶段的bean可以被开发者使用因为已经是成熟的bean。这个阶段涉及到的操作,常见的比如:init-method这个属性配置的方法会在这个阶段调用;以及BeanPostProcessors这个后置处理接口提供的postProcessAfterInitialization()方法后置处理方法也会在这个阶段被触发。

(五)销毁实例阶段

销毁实例阶段

当Spring应用上下文关闭时,该上下文中所有的bean都会被销毁。如果存在bean实现了DisposableBean接口或者配置了destroy-method属性则会在这个阶段被调用。

(六)流程说明

1.如果实现BeanFactoryPostProcessor接口

那么在容器启动过程中bean实例化之前会执行该接口提供的postProcessBeanFactory方法来修改bean的元数据中的信息。

2.如果实现了InstantiationAwareBeanPostProcessor接口

那么在实例化Bean对象之前会调用postProcessBeforeInstantiation方法,该方法如果返回的不为null则会直接调用postProcessAfterInitialization方法,而跳过了Bean实例化后及初始化前的相关方法,如果返回null则正常流程,postProcessAfterInstantiation在实例化成功后执行,这个时候对象已经被实例化,但是该实例的属性还未被设置,都是null。因为它的返回值是决定要不要调用postProcessPropertyValues方法的其中一个因素(因为还有一个因素是mbd.getDependencyCheck());如果该方法返回false,并且不需要check,那么postProcessPropertyValues就会被忽略不执行;如果返回true, postProcessPropertyValues就会被执行,postProcessPropertyValues用来修改属性,在初始化方法之前执行。

3.如果实现了Aware接口

那么该接口中的相关方法会在bean初始化之前执行。

4.如果实现了BeanPostProcessor接口

那么该接口的方法会在实例化后的执行初始化方法前后执行,在执行初始化方法前执行postProcessBeforeInitialization方法、在执行初始化方法后执行postProcessAfterInitialization方法。

5.如果实现了InitializingBean接口

则在执行init-method属性声明的初始化方法过程中执行InitializingBean接口提供的afterPropertiesSet()方法来设置bean的属性。

6.如果指定了init-method属性

则会在bean实例对象初始化过程中执行指定的初始化方法。

7.如果指定了@PostConstruct

则在初始化的时候会执行标注的方法。

8.至此,完整的对象创建完成

9.当对象需要销毁时则执行后续步骤

10.如果实现了DisposableBean接口会执行destroy方法

11.如果指定了destroy-method属性则会执行指定的方法

12.如果指定了@PreDestroy注解则会执行标注的方法

四、对Spring AOP的理解

(一)AOP的概念

AOP(Aspect Orientied Programming,面向切面编程)可以说是OOP(Object Orientied Programming,面向对象编程)的补充和完善。面向切面编程是面向对象编程的一种方式而已,它是在代码执行过程中动态织入额外的业务逻辑来对原有业务逻辑进行增强,这种方式尽可能的减少了对原有代码的侵入性。

(二)AOP的使用

Spring AOP使用原理

(三)AOP的原理

AOP分析和原理
Spring AOP使用原理
AOP相关概念
程序执行流程

五、对Spring事务的理解

单体事务的解决方案;
分布式事务的解决方案;
JDBC事务的概念。

(一)事物的传播属性和隔离级别

Spring事务传播行为
Spring事务
Spring事务四大特性
事务隔离级别

(二)事务原理分析

Spring事务_1
Spring事务_2

六、对Spring循环依赖的理解

Spring循环依赖_1
Spring循环依赖_2
Spring中的循环依赖

(一)非Spring场景下的循环依赖问题

提前暴漏半成品对象并将其存入Map中,解决循环依赖问题后将创建好的完整bean存入Spring一级缓存中。

(二)Spring场景下的循环依赖问题

通过AOP创建被依赖bean的代理对象并将代理对象存入Spring二级缓存中来达到提前暴漏被依赖bean的目的,此时的代理对象是半成品对象因为只是将对象实例化出来(其属性值还是默认值)还没有完成属性赋值。创建被依赖bean的λ表达式存储在Spring三级缓存中,当存在循环依赖时通过λ表达式创建出代理对象并在二级缓存中完成代理对象对原始对象的覆盖操作,最终会将完成属性赋值的bean存入Spring一级缓存中。

(三)Spring三级缓存

如果不涉及AOP动态代理对象的创建,二级缓存足以解决循环依赖问题。第三级缓存的目的是为了通过λ表达式来创建出代理对象来对原始对象做覆盖操作,否则按照Spring源码的设计思想如果同时存在原始对象和其代理对象程序就会报错,提示说依赖的bean对象不是最终版本的bean对象。
一级缓存存放完整bean对象,二级缓存存放半成品对象,三级缓存存放λ表达式。所有完成实例化并完整属性赋值的单例bean实例最终都会存储在Spring一级缓存中。

七、SpringMVC

SpringMVC(M:Model,模型;V:View,视图;C:Controller,控制层)是一个基于三层架构开发的控制层Web框架,其中三层架构指的是控制层(Controller)、业务层(Service)、持久层(Dao),主要用来实现前端和后端的数据交互。

(一)SpringMVC的执行流程

SpringMVC的执行流程

1.客户端请求经过浏览器到达前端核心控制器DispatcherServlet

DispatcherServlet负责将请求转发给HandlerMapping。

2.请求到达处理器映射器HandlerMapping

HandlerMapping根据请求路径找到具体的处理器Handler并返回给DispatcherServlet。DispatcherServlet根据处理器Handler请求处理器适配器HandlerAdapter给匹配具体的后端处理器(Controller)。

3.请求到达处理器适配器HandlerAdapter

HandlerAdapter根据DispatcherServlet传递过来的Handler对象匹配到后端控制器Controller,控制层Controller和业务层进行交互获取ModelAndView对象并返回给前端核心控制器DispatcherServlet。其中业务层的数据来源于持久层,而持久层负责而和数据库进行交互。

4.DispatcherServlet将ModelAndView对象发送给ViewResolver

视图解析器ViewResolver拿到ModelAndView对象之后对其进行数据填充和渲染,并将渲染结果返回给前端进行数据展示。至此,SpringMVC的请求流程执行完毕。

(二)SpringMVC如何统一封装响应信息?

SpringMVC是控制层框架,它可以将后端业务数据处理结果封装成统一格式返回给前端使用,主要是通过RequestMappingHandlerAdapter中提供的HandlerMethodReturnValueHandler来扩展实现的。

@Configuration
public class InitializingAdviceDecorator implements InitializingBean {private final RequestMappingHandlerAdapter adapter;public InitializingAdviceDecorator(RequestMappingHandlerAdapter adapter) {this.adapter = adapter;}@Overridepublic void afterPropertiesSet() {//获取所有的handler对象List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();//因为上面返回的是unmodifiableList,所以需要新建list处理assert returnValueHandlers != null;List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(returnValueHandlers);this.decorateHandlers(handlers);//将增强的返回值回写回去adapter.setReturnValueHandlers(handlers);}/*** 使用自定义的返回值控制类** @param handlers*/private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {for (HandlerMethodReturnValueHandler handler : handlers) {if (handler instanceof RequestResponseBodyMethodProcessor) {//找到返回值的handler并将起包装成自定义的handlerControllerReturnValueHandler decorator = new ControllerReturnValueHandler((RequestResponseBodyMethodProcessor) handler);int index = handlers.indexOf(handler);handlers.set(index, decorator);break;}}}/*** 自定义返回值的Handler* 采用装饰者模式*/private static class ControllerReturnValueHandler implements HandlerMethodReturnValueHandler {//持有一个被装饰者对象private final HandlerMethodReturnValueHandler handler;ControllerReturnValueHandler(RequestResponseBodyMethodProcessor handler) {this.handler = handler;}@Overridepublic boolean supportsReturnType(MethodParameter returnType) {return true;}/*** 增强被装饰者的功能** @param returnValue  返回值* @param returnType   返回类型* @param mavContainer view* @param webRequest   请求对象* @throws Exception 抛出异常*/@Overridepublic void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {//如果是下载文件跳过包装IgnoredResultWrapper ignoredResultWrapper = returnType.getMethodAnnotation(IgnoredResultWrapper.class);if (ignoredResultWrapper != null) {handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);return;}if (returnValue == null) {Optional<String> contentType = Optional.of(webRequest).map(nativeWebRequest -> ((ServletWebRequest) webRequest)).map(ServletRequestAttributes::getResponse).map(ServletResponse::getContentType);if (contentType.isPresent() && contentType.get().contains("application/vnd.openxmlformats-officedocument")) {return;}}//如果已经封装了结构体就直接放行if (returnValue instanceof ResultWrapper) {handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);return;}//正常返回successResultWrapper<Object> success = ResultWrapper.success(returnValue);handler.handleReturnValue(success, returnType, mavContainer, webRequest);}}
}

(三)SpringMVC如何处理Date相关参数?

1.使用@DateTimeFormat注解

可以在Controller方法的参数上使用@DateTimeFormat注解来指定日期参数的格式。

public String getDate(@RequestParam("date") @DateTimeFormat(pattern="yyyy-MM-dd") Date date) {//处理日期参数
}

2.使用@InitBinder注解

可以在Controller中定义一个方法,使用@InitBinder注解来自定义日期参数的转换器。

@InitBinder
public void initBinder(WebDataBinder binder) {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}

3.使用Converter接口

可以自定义一个Converter实现类,实现org.springframework.core.convert.converter.Converter接口,编写逻辑将字符串转换为Date类型,然后在配置文件中注册该Converter,让Spring MVC自动调用。

public class DateConvert implements Converter<String,Date>{@Overridepublic Date convert(String msg) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");try {return sdf.parse(msg);} catch (ParseException e) {// TODO Auto-generated catch blocke.printStackTrace();}return null;}
}

在配置文件中注册Converter

八、SpringSecurity

常见的身份校验和授权框架有Apache提供的Shiro框架和Spring提供的SpringSecurity框架。

(一)工作原理

客户端通过浏览器发起请求,请求经过层层过滤器到达Servlet,这些过滤器属于web过滤器,在这些web过滤器中会嵌入SpringSecurity的Filter,但是SpringSecurity的Filter并不是直接嵌套在web过滤器中,它是通过FilterChainProxy来统一管理的,而FilterChainProxy本身又是通过DelegatingFilterProxy代理过滤器嵌入到web过滤器中的。
FilterChainProxy管理者SpringSecurity的一个个过滤器链,而每个过滤器链负责拦截种类型的请求,当然每个过滤器链中又包含很多个过滤器来对请求进行过滤。

(二)基于Token的认证方式如何实现?

定义两个过滤器来解决认证的处理流程和登陆成功后的校验处理问题。

1.登录认证过滤器

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {private AuthenticationManager authenticationManager;public TokenLoginFilter(AuthenticationManager authenticationManager){this.authenticationManager = authenticationManager;}/*** 具体认证的方法* @param request* @param response* @return* @throws AuthenticationException*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {SysUser sysUser = null;// 前后端分离的项目中我们提交的数据是JSON字符串。不是表单提交的try {String loginInfo = getRequestJSON(request);sysUser = JSON.parseObject(loginInfo, SysUser.class);UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(sysUser.getUsername(),sysUser.getPassword());// 系统认证return authenticationManager.authenticate(authenticationToken);} catch (IOException e) {e.printStackTrace();}return null;}private String getRequestJSON(HttpServletRequest request) throws IOException {BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream()));StringBuilder sb = new StringBuilder();String inputStr = null;while((inputStr = streamReader.readLine() ) != null){sb.append(inputStr);}return sb.toString();}/*** 登录成功的方法* @param request* @param response* @param chain* @param authResult* @throws IOException* @throws ServletException*/@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {// 生成Token信息Map<String,String> map = new HashMap<>();map.put("username",authResult.getName());// TODO 还可以存储当前用户具有的角色// 生成对应的Token信息String token = JWTUtils.getToken(map);// 需要把生成的Token信息响应给客户端response.addHeader("Authorization", SystemConstant.SYS_TOKEN_PREFIX +token);response.addHeader("Access-Control-Expose-Headers","Authorization");response.setContentType("application/json;charset=utf-8");response.setStatus(HttpServletResponse.SC_OK);PrintWriter writer = response.getWriter();Map<String,Object> resultMap = new HashMap<>();resultMap.put("code", HttpServletResponse.SC_OK);resultMap.put("msg","认证通过");writer.write(JSON.toJSONString(resultMap));writer.flush();writer.close();}/*** 登录失败的方法* @param request* @param response* @param failed* @throws IOException* @throws ServletException*/@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {response.setContentType("application/json;charset=utf-8");response.setStatus(HttpServletResponse.SC_OK);PrintWriter writer = response.getWriter();Map<String,Object> resultMap = new HashMap<>();resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);resultMap.put("msg","用户名或密码错误!");writer.write(JSON.toJSONString(resultMap));writer.flush();writer.close();}
}

2.请求校验过滤器

public class TokenVerifyFilter extends BasicAuthenticationFilter {public TokenVerifyFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}/*** 校验提交的Token是否合法的方法* @param request* @param response* @param chain* @throws IOException* @throws ServletException*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("--->"+request.getRequestURI());// 获取请求携带的Token信息String header = request.getHeader("Authorization");String requestURI = request.getRequestURI();String contextPath = request.getContextPath();String path = requestURI.replace(contextPath,"");List<String> msgs = Arrays.asList("/doc.html","/webjars","/v2","/v3","/favicon.ico","swagger-resources");for (String p : msgs) {if(path.contains(p)){// 放过请求chain.doFilter(request,response);return ;}}System.out.println("request.getContextPath() = " + request.getContextPath());if(header != null && header.startsWith(SystemConstant.SYS_TOKEN_PREFIX)){// 传递了Token信息。同时有我们添加的对应的前缀// 1.获取到正常的tokenString token = header.replace(SystemConstant.SYS_TOKEN_PREFIX, "");// 2.校验token信息是否合法DecodedJWT verify = JWTUtils.verify(token);if(verify == null){// 说明验证失败responseLogin(response);}// 走到这儿说明是正常// 获取当前登录的账号信息String userName = verify.getClaim("username").asString();// 放过请求 后续的控制器可能需要相关的权限List<GrantedAuthority> list = new ArrayList<>();list.add(new SimpleGrantedAuthority("ADMIN"));// 根据账号获取相关的权限UsernamePasswordAuthenticationToken authenticationToken= new UsernamePasswordAuthenticationToken(userName,"",list);SecurityContextHolder.getContext().setAuthentication(authenticationToken);// 放过请求chain.doFilter(request,response);}else{// 没有携带Token或者是非法的请求responseLogin(response);}}private void responseLogin(HttpServletResponse response) throws IOException {// 说明校验失败 -- 给用户提示请先登录response.setContentType("application/json;charset=utf-8");response.setStatus(HttpServletResponse.SC_OK);PrintWriter writer = response.getWriter();Map<String,Object> resultMap = new HashMap<>();resultMap.put("code", HttpServletResponse.SC_FORBIDDEN);resultMap.put("msg","请先登录!");writer.write(JSON.toJSONString(resultMap));writer.flush();writer.close();}
}

3.配置类中关联配置

@Configuration
public class MySpringSecurityConfiguration extends WebSecurityConfigurerAdapter {@AutowiredUserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService) // 绑定自定义的认证Service.passwordEncoder(new BCryptPasswordEncoder()); // 绑定密码处理器}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/doc.html", "/doc.html/**", "/webjars/**", "/v2/**", "/v3/**", "/swagger-resources","/swagger-resources/**", "/swagger-ui.html", "/swagger-ui.html/**").permitAll().antMatchers("/api/*/auth/**", "/test/**").permitAll() // 登录注册等请求过滤 // 傻瓜式乱测.anyRequest().authenticated().and()// 设置跨域的处理.cors().configurationSource(corsConfigurationSource()).and().addFilter(new TokenLoginFilter(super.authenticationManager())) // 绑定认证的接口.addFilter(new TokenVerifyFilter(super.authenticationManager())) // 绑定校验的接口.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}public static void main(String[] args) {String password = "123456";System.out.println(new BCryptPasswordEncoder().encode(password).toString());System.out.println(new BCryptPasswordEncoder().encode(password).toString());System.out.println(new BCryptPasswordEncoder().encode(password).toString());}/*** 设置跨域的信息* @return*/CorsConfigurationSource corsConfigurationSource(){CorsConfiguration config = new CorsConfiguration();// 配置跨域拦截的相关信息config.setAllowedHeaders(Arrays.asList("*"));config.setAllowedMethods(Arrays.asList("*"));config.setAllowedOrigins(Arrays.asList("*"));config.setMaxAge(3600l);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**",config);return source;}
}

(三)如何理解JWT?

JWT官网

JWT(JSON Web Token)是一种安全传输信息的开放标准。它提供了生成令牌和校验令牌合法性的API。通常使用JWT实现客户端和服务端之间的身份校验和授权。
JWT由三部分组成:头部(Header)、载荷(Payload)、签名(Signature)。头部包含令牌的元数据和加密算法信息;载荷包含声明(claim)信息,比如用户ID、角色等信息;签名用来验证令牌的真实性和完整性。
JWT令牌的组成部分
JWT的工作流程通常是用户登陆成功后,服务端通过JWT的API生成一个Token加载响应头里面返回给客户端,客户端后续再次请求时将JWT的Token放在请求头中进行请求,服务端收到请求后将请求头中的Token取出来然后通过JWT的API进行校验,验证token的签名和有效期,并提取出用户信息进行权限校验。

九、SpringBoot

(一)SpringBoot自动装配的原理

自动装配,简单来说就是把第三方组件的bean自动装载到Spring IOC容器中,不需要开发人员去写bean的装配配置。在SpringBoot应用里面,我们只需要在启动类加上@SpringBootApplication注解就可以实现自动装配,@SpringBootApplication是一个复合注解,真正实现自动装配的@EnableAutoConfiguration注解。
SpringBoot自动装配机制的实现主要依靠三个核心关键技术:
引入Starter启动依赖组件时,这个组件里面必须要包含@Configuration注解作用的配置类,在配置类里面通过@Bean注解声明需要装载到IOC容器的bean对象。
这个配置类是放在第三方的jar包里面,然后通过约定优于配置的思想把这个配置类的全路径放在classpath:/META-INFO/spring.factories文件中,然后SpringBoot就可以知道第三方jar包中配置类的位置,这个步骤主要通过Spring里面的SpringFactoriesLoader来完成。
SpringBoot拿到第三方jar包中配置类以后,再通过Spring提供的ImportSelector接口实现对这些配置类的动态加载。
在我看来,SpringBoot是约定优于配置这一理念下的产物,在很多的地方都有这类的思想。它的出现,让开发人员更加聚焦在业务代码的开发上,不需要去关心和业务无关的配置。
其实,自动装配的思想在SpringFramework3.x版本里面的@Enable注解里面就有了实现的雏形,Enable是模块驱动的意思,我们只需要增加某个@Enable注解就会自动增加某个功能,不要我们再去针对这个功能做bean的相关配置。@Enable注解底层也是会帮助我们去自动完整这个模块相关bean的注入。

(二)SpringBoot约定优于配置的理解

首先,约定优于配置是一种软件设计范式,它的核心思想是减少开发人员对于配置项的维护,从而更加聚焦在业务逻辑上。SpringBoot就是约定优于配置这一理念下的产物,它类似于Spring框架下的一个脚手架,通过SpringBoot可以快速构建基于Spring生态下的应用程序。
基于传统Spring框架开发web应用,我们需要做很多和业务无关并且只需要做一次的配置,比如管理jar包依赖、web.xml维护、dispatcher-servlet.xml配置项维护、应用部署到容器、第三方组件集成到Spring IOC的配置项维护等等,而在SpringBoot中,我们不需要再去做这些繁琐的配置,因为SpringBoot已经自动帮我们完成了,这就是约定优于配置思想的体现。
SpringBoot中约定优于配置的体现有很多,比如:SpringBoot Starter启动依赖,它能帮我们管理所有的jar包版本;如果当前应用依赖了SpringMVC相关的jar,那么SpringBoot会自动内置Tomcat容器来运行web应用,不需要再去单独做应用部署;在自动装配机制的实现中,SpringBoot通过扫描约定路径下的spring.factories文件来识别配置类,实现bean的自动装配;默认加载的配置文件application.properties等等。
总的来说,约定优于配置是一种比较常见的软件设计思想,它的核心本质都是为了更高效以及更便捷实现软件系统的开发和维护。

(三)@Import注解的作用

1.导入配置类

在主配置类上使用@Import注解可以导入其他配置类,使其他配置类称为主配置类的一部分。这样可以方便的组织代码、使代码更加清晰。

@Configuration
@Import({Config1.class, Config2.class})
public class MainConfig {// 主配置类的内容
}

2.导入普通类

@Import注解除了可以导入配置类以外还可以导入普通java类,这样以来可以在Spring容器中创建这些类的实例并进行管理。

@Configuration
@Import({Service1.class, Service2.class})
public class MainConfig {// 主配置类的内容
}

3.导入ImportSelector接口的实现类

@Import注解还可以导入ImportSelector接口的实现类,通过这种方式可以根据条件动态选择要导入的类。

@Configuration
@Import(MyImportSelector.class)
public class MainConfig {// 主配置类的内容
}public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {// 根据条件返回需要导入的类的全限定名数组return new String[]{"Service1", "Service2"};}
}

4.导入ImportBeanDefinitionRegistrar接口的实现类

@Import注解还可以导入ImportBeanDefinitionRegistrar接口的实现类,通过这种方式可以在运行时动态注册Bean到Spring容器中。

@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class MainConfig {// 主配置类的内容
}public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 动态注册 Bean 到 Spring 容器中registry.registerBeanDefinition("bean1", new RootBeanDefinition(Bean1.class));}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/422089.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言基础——⑩③数据结构——②栈和队列

一、栈(Stack) 1、基本概念 栈是一种逻辑结构&#xff0c;是特殊的线性表。特殊在&#xff1a; 只能在固定的一端操作 只要满足上述条件&#xff0c;那么这种特殊的线性表就会呈现一种“后进先出”的逻辑&#xff0c;这种逻辑就被称为栈。栈 在生活中到处可见&#xff0c;比…

为什么企业需要数据目录?

想象一下&#xff0c;如果在没有目录系统的庞大图书馆里寻找一本特定的书&#xff0c;你可能会耗费无数个小时搜索&#xff0c;但最终却一无所获。 同理&#xff0c;企业的数据如果没有一个组织良好、易于搜索的系统&#xff0c;也无法充分发挥其潜力。企业数据目录能够简化这一…

Kafka 基础与架构理解

目录 前言 Kafka 基础概念 消息队列简介&#xff1a;Kafka 与传统消息队列&#xff08;如 RabbitMQ、ActiveMQ&#xff09;的对比 Kafka 的组件 Kafka 的工作原理&#xff1a;消息的生产、分发、消费流程 Kafka 系统架构 Kafka 的分布式架构设计 Leader-Follower 机制与…

进击J6:ResNeXt-50实战

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、实验目的&#xff1a; 阅读ResNeXt论文&#xff0c;了解作者的构建思路对比之前介绍的ResNet50V2、DenseNet算法使用ResNeXt-50算法完成猴痘病识别 二、实…

jmeter之仅一次控制器

仅一次控制器作用&#xff1a; 不管线程组设置多少次循环&#xff0c;它下面的组件都只会执行一次 Tips&#xff1a;很多情况下需要登录才能访问其他接口&#xff0c;比如&#xff1a;商品列表、添加商品到购物车、购物车列表等&#xff0c;在多场景下&#xff0c;登录只需要…

【EJB】会话Bean(Session Bean)

单例会话****bean在每个应用程序中实例化一次&#xff0c;并存在于应用程序的生命周期中。单例会话bean是为单个企业bean实例在客户端之间共享和并发访问的环境而设计的。 单例会话Bean提供了与无状态会话Bean相似的功能&#xff0c;但与它们不同&#xff0c;因为每个应用程序…

【CanMV K230 AI视觉】人脸关键部位

【CanMV K230 AI视觉】人脸关键部位 人脸关键部位 &#xff08;动态测试效果可以去下面网站自己看。&#xff09; B站视频链接&#xff1a;已做成合集 抖音链接&#xff1a;已做成合集 人脸关键部位 人脸关键部位检测&#xff0c;主要检测脸部轮廓、眉毛、眼睛、鼻子和嘴巴&a…

【Kubernetes】K8s 的鉴权管理(二):基于属性 / 节点 / Webhook 的访问控制

K8s 的鉴权管理&#xff08;二&#xff09;&#xff1a;基于属性 / 节点 / Webhook 的访问控制 1.基于属性的访问控制&#xff08;ABAC 鉴权&#xff09;2.基于节点的访问控制&#xff08;node 鉴权&#xff09;2.1 读取操作2.2 写入操作 3.基于 Webhook 的访问控制3.1 基于 We…

什么是 Grafana?

什么是 Grafana&#xff1f; Grafana 是一个功能强大的开源平台&#xff0c;用于创建、查看、查询和分析来自多个来源的数据。通过可视化仪表盘&#xff08;Dashboard&#xff09;&#xff0c;它能够帮助用户监控实时数据、生成历史报告&#xff0c;甚至进行预测分析。Grafana…

深入解读Docker核心原理:Cgroups资源限制机制详解

在容器化技术中&#xff0c;除了资源的隔离&#xff0c;如何有效地控制和分配系统资源同样至关重要。Cgroups&#xff08;Control Groups&#xff09; 是Linux内核提供的一个强大机制&#xff0c;允许限制、监控和隔离进程组的系统资源使用情况。Cgroups是Docker实现容器资源限…

ffmpeg实现视频的合成与分割

视频合成与分割程序使用 作者开发了一款软件&#xff0c;可以实现对视频的合成和分割&#xff0c;界面如下&#xff1a; 播放时&#xff0c;可以选择多个视频源&#xff1b;在选中“保存视频”情况下&#xff0c;会将多个视频源合成一个视频。如果只取一个视频源中一段视频…

初识爬虫1

学习路线&#xff1a;爬虫基础知识-requests模块-数据提取-selenium-反爬与反反爬-MongoDB数据库-scrapy-appium。 对应视频链接(百度网盘)&#xff1a;正在整理中 爬虫基础知识&#xff1a; 1.爬虫的概念 总结&#xff1a;模拟浏览器&#xff0c;发送请求&#xff0c;获取…

Minimax-秋招正式批-面经(SQL相关)

1. 谈谈对聚簇索引的理解 聚簇索引 InnoDB通过主键聚集数据&#xff0c;如果没有定义主键&#xff0c;InnoDB会选择非空的唯一索引代替。如果没有这样的索引&#xff0c;InnoDB会隐式定义一个主键来作为聚簇索引聚簇索引就是按照每张表的主键构造一颗B树&#xff0c;同时叶子…

挖耳勺可以和别人共用吗?口碑好的可视耳勺!

人体分泌的耳垢会有细菌&#xff0c;如果与别人共用挖耳勺很有可能会交叉感染&#xff0c;所以一般建议自己有专用的挖耳勺。小编可以给大家分享一款超好用又能实现一人一用的挖耳勺--可视挖耳勺&#xff0c;它有着高清内窥镜可以进入耳道实时查看情况&#xff0c;并且耳勺头采…

Unity人工智能开发学习心得

在Unity中进行人工智能研究与应用主要集中在几个关键领域&#xff0c;包括使用Unity ML-Agents插件进行强化学习、利用神经网络技术和深度学习技术训练AI&#xff0c;以及基于行为树技术设计游戏人工智能。 ‌使用Unity ML-Agents插件进行强化学习‌&#xff1a;Unity ML-Agent…

浏览器百科:网页存储篇-IndexedDB介绍(十)

1.引言 在现代网页开发中&#xff0c;数据存储需求日益增多和复杂&#xff0c;传统的客户端存储技术如localStorage和sessionStorage已难以满足大型数据的存储和管理需求。为了解决这一问题&#xff0c;HTML5 引入了 IndexedDB&#xff0c;在本篇《浏览器百科&#xff1a;网页…

Debug-027-el-tooltip组件的使用及注意事项

前言&#xff1a; 这两天&#xff0c;碰到这个饿了么的el-tooltip比较多。这个组件使用起来也挺简单的&#xff0c;常用于展示鼠标 hover 时的提示信息。但是有一些小点需要注意。这里不再机械化的介绍文档&#xff0c;不熟悉的话可以先看一下&#xff1a; https://element-pl…

Linux 硬件学习 s3c2440 arm920t蜂鸣器

1.查找手册时钟图&#xff0c;输入12m想要通过pll得到400m的信号 2.对比pll值&#xff0c;找到最近的为405&#xff0c;得到pll中mdiv为127&#xff0c;pdiv为2&#xff0c;sdiv为1 3.想要得到fclk400&#xff0c;hclk100&#xff0c;pclk50&#xff0c;对比分频比例&#xff0…

jmeter执行python脚本,python脚本的Faker库

jmeter安装 jython的插件jar包 通过如下地址下载jython-standalone-XXX.jar包并放到jmeter的XXX\lib\ext目录下面 Downloads | JythonThe Python runtime on the JVMhttps://www.jython.org/download.html 重启jmeter在JSR223中找到jython可以编写python代码执行 python造数据…

MySQL:运维管理-主从复制

目录 一、主从复制的概述二、主从复制的工作原理三、搭建主从复制的结构3.1 环境准备3.2 搭建配置&#xff08;主库配置&#xff09;3.3 搭建配置&#xff08;从库配置&#xff09;3.4 测试 一、主从复制的概述 主从复制是指将主数据库中的DDL和DML操作的二进制文件保存到本地&…