SpringBoot学习之自动装配

一。SpringBoot中创建对象的注解扩充

  其实说白了,SpringBoot并不属于一种新的技术,只不过Spring-Boot-Starter-xxxx的启动器帮我们配置了若干个被Spring管理的bean,当我们的项目依赖这些jar并启动Spring应用时,Spring的Container容器已经把jar包下的对象加以创建及管理了,我们请看下面的例子:

该例子是spring-boot-autoconfigure-2.0.0.M7.jar包的内容,我们找到如下类: DispatcherServletAutoConfiguration ,我贴出源代码:

/*
 * Copyright 2012-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.web.servlet;

import java.util.Arrays;
import java.util.List;

import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.DispatcherServlet;

/**
 * {@link EnableAutoConfiguration Auto-configuration} for the Spring
 * {@link DispatcherServlet}. Should work for a standalone application where an embedded
 * web server is already present and also for a deployable application using
 * {@link SpringBootServletInitializer}.
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Stephane Nicoll
 * @author Brian Clozel
 */
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
@EnableConfigurationProperties(ServerProperties.class)
public class DispatcherServletAutoConfiguration {

    /*
     * The bean name for a DispatcherServlet that will be mapped to the root URL "/"
     */
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

    /*
     * The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
     */
    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

    @Configuration
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    protected static class DispatcherServletConfiguration {

        private final WebMvcProperties webMvcProperties;

        public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {
            this.webMvcProperties = webMvcProperties;
        }

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet() {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(
                    this.webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(
                    this.webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(
                    this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
            return dispatcherServlet;
        }

        @Bean
        @ConditionalOnBean(MultipartResolver.class)
        @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
        public MultipartResolver multipartResolver(MultipartResolver resolver) {
            // Detect if the user has created a MultipartResolver but named it incorrectly
            return resolver;
        }

    }

    @Configuration
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {

        private final ServerProperties serverProperties;

        private final WebMvcProperties webMvcProperties;

        private final MultipartConfigElement multipartConfig;

        public DispatcherServletRegistrationConfiguration(
                ServerProperties serverProperties, WebMvcProperties webMvcProperties,
                ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
            this.serverProperties = serverProperties;
            this.webMvcProperties = webMvcProperties;
            this.multipartConfig = multipartConfigProvider.getIfAvailable();
        }

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
                DispatcherServlet dispatcherServlet) {
            ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
                    dispatcherServlet,
                    this.serverProperties.getServlet().getServletMapping());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(
                    this.webMvcProperties.getServlet().getLoadOnStartup());
            if (this.multipartConfig != null) {
                registration.setMultipartConfig(this.multipartConfig);
            }
            return registration;
        }

    }

    @Order(Ordered.LOWEST_PRECEDENCE - 10)
    private static class DefaultDispatcherServletCondition extends SpringBootCondition {

        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context,
                AnnotatedTypeMetadata metadata) {
            ConditionMessage.Builder message = ConditionMessage
                    .forCondition("Default DispatcherServlet");
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            List<String> dispatchServletBeans = Arrays.asList(beanFactory
                    .getBeanNamesForType(DispatcherServlet.class, false, false));
            if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
                return ConditionOutcome.noMatch(message.found("dispatcher servlet bean")
                        .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
            }
            if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
                return ConditionOutcome
                        .noMatch(message.found("non dispatcher servlet bean")
                                .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
            }
            if (dispatchServletBeans.isEmpty()) {
                return ConditionOutcome
                        .match(message.didNotFind("dispatcher servlet beans").atAll());
            }
            return ConditionOutcome.match(message
                    .found("dispatcher servlet bean", "dispatcher servlet beans")
                    .items(Style.QUOTE, dispatchServletBeans)
                    .append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
        }

    }

    @Order(Ordered.LOWEST_PRECEDENCE - 10)
    private static class DispatcherServletRegistrationCondition
            extends SpringBootCondition {

        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context,
                AnnotatedTypeMetadata metadata) {
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            ConditionOutcome outcome = checkDefaultDispatcherName(beanFactory);
            if (!outcome.isMatch()) {
                return outcome;
            }
            return checkServletRegistration(beanFactory);
        }

        private ConditionOutcome checkDefaultDispatcherName(
                ConfigurableListableBeanFactory beanFactory) {
            List<String> servlets = Arrays.asList(beanFactory
                    .getBeanNamesForType(DispatcherServlet.class, false, false));
            boolean containsDispatcherBean = beanFactory
                    .containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            if (containsDispatcherBean
                    && !servlets.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
                return ConditionOutcome
                        .noMatch(startMessage().found("non dispatcher servlet")
                                .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
            }
            return ConditionOutcome.match();
        }

        private ConditionOutcome checkServletRegistration(
                ConfigurableListableBeanFactory beanFactory) {
            ConditionMessage.Builder message = startMessage();
            List<String> registrations = Arrays.asList(beanFactory
                    .getBeanNamesForType(ServletRegistrationBean.class, false, false));
            boolean containsDispatcherRegistrationBean = beanFactory
                    .containsBean(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
            if (registrations.isEmpty()) {
                if (containsDispatcherRegistrationBean) {
                    return ConditionOutcome
                            .noMatch(message.found("non servlet registration bean").items(
                                    DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
                }
                return ConditionOutcome
                        .match(message.didNotFind("servlet registration bean").atAll());
            }
            if (registrations
                    .contains(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)) {
                return ConditionOutcome.noMatch(message.found("servlet registration bean")
                        .items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
            }
            if (containsDispatcherRegistrationBean) {
                return ConditionOutcome
                        .noMatch(message.found("non servlet registration bean").items(
                                DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
            }
            return ConditionOutcome.match(message.found("servlet registration beans")
                    .items(Style.QUOTE, registrations).append("and none is named "
                            + DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
        }

        private ConditionMessage.Builder startMessage() {
            return ConditionMessage.forCondition("DispatcherServlet Registration");
        }

    }

}

该代码是创建SpringMvc的典型示例  我想@Bean @Configuration 这个大家在熟悉不过了,下面我们来看几个比较陌生的注解,我简单解释一下:

1.spring-boot利用Conditional来确定是否创建Bean实例,这个注解我们可以理解为满足一定条件我们才创建Bean

2.这些注解我们可以在spring-boot-autoconfigure-2.0.0.M7.jar下 org.springframework.boot.autoconfigure.condition下找到

3.常见的注解解释:

  3.1 @ConditionalOnBean :匹配给定的class类型或者Bean的名字是否在SpringBeanFactory中存在

  3.2 @ConditionalOnClass:匹配给定的class类型是否在类路径(classpath)中存在

  3.3 @ConditionalOnExpression : 匹配给定springEL表达式的值返回true时

  3.4 @ConditionalOnJava :匹配JDK的版本,其中range属性是枚举类型有两个值可以选择 

      3.4.1 EQUAL_OR_NEWER 不小于

      3.4.2 OLDER_THAN 小于

                  3.4.3 value属性用于设置jdk版本

  3.5 ConditionalOnMissingBean:spring上下文中不存在指定bean时

    3.6 ConditionalOnWebApplication:在web环境下创建

 

@ConditionalOnBean is evaluated after all configuration classes have been processed, i.e you can't use it to make a whole configuration class conditional on the presence of another bean. You can, however, use it where you have to make all of the configuration's beans conditional on the presence of another bean.

注意:在配置类中定义Bean,如果使用@ConditionalOnBean依赖的也是配置类中Bean,则执行结果不可控,和配置类加载顺序有关。

在spring ioc的过程中,优先解析@Component,@Service,@Controller注解的类。其次解析配置类,也就是@Configuration标注的类。最后开始解析配置类中定义的bean。
示例代码中bean1是定义在配置类中的,当执行到配置类解析的时候,@Component,@Service,@Controller ,@Configuration标注的类已经全部被解析,所以这些BeanDifinition已经被同步。
但是bean1的条件注解依赖的是bean2,bean2是被定义的配置类中的,因为两个Bean都是配置类中Bean,所以此时配置类的解析无法保证先后顺序,就会出现不生效的情况。

原因可以查看:https://blog.csdn.net/woshilijiuyi/article/details/84147483

@Bean
@ConditionalOnBean({Teacher.class})
public Student student(StudentProperties studentProperties) {
    Student student = new Student();
    student.setStuName(studentProperties.getName());
    return student;
}

@Bean
@ConditionalOnMissingBean
public static Teacher teacher() {
    return new Teacher();
}

@Bean
@ConditionalOnMissingBean
public static School school() {
    return new School();
}

比如上述代码 Student是不会被创建的,如果非要@Bean和@Conditional使用,则可以借助于@Import方式实现

@Configuration
@EnableConfigurationProperties(StudentProperties.class)
@Import(TeacherAutoConfiguration.class)
public class MyTestAutoConfiguration {

    @Bean
    @ConditionalOnBean(Teacher.class)
    public Student student(StudentProperties studentProperties) {
        Student student = new Student();
        student.setStuName(studentProperties.getName());
        return student;
    }
}
@Configuration
public class TeacherAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public static Teacher teacher() {
        return new Teacher();
    }
}

 

 

二。实现简单的SpringBoot示例

1.我们先创建两个类分别为 Teacher Student,项目结构图

 

 

 注意图中标圈的文件:spring-configuration-metadata.json文件,我们配置这个文件后,可以在idea或者spring sts中配置application.yml得到相关智能提示

 json数据如下

{
  "hints":[],
  "groups":[],
  "properties": [
    {
      "sourceType": "com.bdqn.lyrk.springboot.study.configuration.MyProperties",
      "name": "my.loginName",
      "description": "登录名",
      "type": "java.lang.String"
    }
  ]
}

 

2.MyObjectAutoConfiguration类代码:

package com.bdqn.lyrk.springboot.study.configuration;

import com.bdqn.lyrk.springboot.study.pojo.School;
import com.bdqn.lyrk.springboot.study.pojo.Student;
import com.bdqn.lyrk.springboot.study.pojo.Teacher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 自动创建对象示例,例子中我们创建Teacher与Student对象。
 * 当项目打成jar包依赖到其他Spring容器中,这些对象我们可以自动进行注入
 */
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyObjectAutoConfiguration {



    @Configuration
    static class TeacherAutoConfiguration {

        @Bean
        @ConditionalOnClass({Teacher.class, School.class})
        public static Teacher teacher() {
            return new Teacher();
        }
    }

    @Configuration
    static class StudentAutoConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public Student student(@Autowired MyProperties myProperties) {
            return new Student(myProperties.getLoginName());
        }
    }
}

3.MyProperties代码:

package com.bdqn.lyrk.springboot.study.configuration;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 用于实现读取application.yml中的配置
 */
@ConfigurationProperties(prefix = MyProperties.PREFIX)
public class MyProperties {

    public static final String PREFIX = "my";

    private String loginName;

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }
}

4.application.yml配置:

my:
  loginName: test
spring:
  main:
    web-environment: false

5.Application代码:

package com.bdqn.lyrk.springboot.study;

import com.bdqn.lyrk.springboot.study.pojo.Student;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args);
        Student student = applicationContext.getBean(Student.class);
        System.out.println(student.getLoginName());
    }
}

当运行成功时:我们可以看到输出Student的loginName属性是test

 

 

 

 

6.在META-INF/spring.factories编写如下代码:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.bdqn.lyrk.springboot.study.configuration.MyTestAutoConfiguration

 

注意:此配置文件非常重要,当我们这个jar包被SpringBoot依赖时,spring会读取org.springframework.boot.autoconfigure.EnableAutoConfiguration所定义的配置类并加载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值