Spring面试

3 篇文章 0 订阅
1 篇文章 0 订阅

一、对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;
    }

    @Override
    public 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并将起包装成自定义的handler
                ControllerReturnValueHandler 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;
        }

        @Override
        public boolean supportsReturnType(MethodParameter returnType) {
            return true;
        }

        /**
         * 增强被装饰者的功能
         *
         * @param returnValue  返回值
         * @param returnType   返回类型
         * @param mavContainer view
         * @param webRequest   请求对象
         * @throws Exception 抛出异常
         */
        @Override
        public 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;
            }
            //正常返回success
            ResultWrapper<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>{
	@Override
	public Date convert(String msg) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		try {
			return sdf.parse(msg);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.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
     */
    @Override
    public 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
     */
    @Override
    protected 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
     */
    @Override
    protected 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
     */
    @Override
    protected 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.获取到正常的token
            String 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 {
    @Autowired
    UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService) // 绑定自定义的认证Service
                .passwordEncoder(new BCryptPasswordEncoder()); // 绑定密码处理器
    }

    @Override
    protected 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 {
    @Override
    public 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 {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 动态注册 Bean 到 Spring 容器中
        registry.registerBeanDefinition("bean1", new RootBeanDefinition(Bean1.class));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值