JAVA基礎知識複習-Springboot原理
優點
- 元件自動裝配:規約大於配置,專注核心業務
- 外部化配置:一次構建,按需調配,到處執行
- 嵌入式容器:內建容器,無需部署,獨立執行
- Spring Boot Starter:簡化依賴,按需裝配,自我包含
- Production-Ready:一站式運維,生態無縫整合
元件自動裝配
Spring模式註解裝配
註解裝配即在應用中扮演元件角色的註解,例如@Configuration,@Component.在SSM專案中我們一般通過context:component-scan進行裝配,在springboot專案中則通過@ComponentScan
- SSM(xml檔案配置)
<!-- 啟用註解驅動特性 -->
<context:annotation-config />
<!-- 找尋被 @Component 或者其派生 Annotation 標記的類(Class),將它們註冊為 Spring Bean -->
<context:component-scan base-package="com.springbootlearning.test" />
複製程式碼
- Spring Boot
@ComponentScan(basePackages = "com.springbootlearning.test")
public class TestApplication {
...
}
複製程式碼
- 模式註解
Spring Framework 註解 | 描述 |
---|---|
@Component | 通用元件模式註解 |
@Configuration | 配置類模式註解 |
@Controller | Web 控制器模式註解 |
@Service | 服務模式註解 |
@Repository | 資料倉儲模式註解 |
簡單比對@Service和@Controller,我們不能發現兩者均為@Component的派生註解(值(此處為value)必須一致),只是被賦予了不同的含義,本質是一樣的
- @Service
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
複製程式碼
- @Controller
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "" ;
}
複製程式碼
Spring @Enable 模組裝配
@Enable 模組即具備相同領域的功能元件集合, 組合所形成一個獨立 的單元。比如 Web MVC 模組、AspectJ代理模組等
框架 | @Enable 模組註解 | 相關模組 |
---|---|---|
Spring Framework | @EnableWebMvc | Web MVC 模組 |
@EnableTransactionManagement | 事務管理模組 | |
@EnableCaching | Caching 模組 | |
@EnableMBeanExport | JMX 模組 | |
@EnableAsync | 非同步處理模組 | |
@EnableWebFlux | Web Flux 模組 | |
@EnableAspectJAutoProxy | AspectJ 代理模組 | |
Spring Boot | @EnableAutoConfiguration | 自動裝配模組 |
@EnableManagementContext | Actuator 管理模組 | |
@EnableConfigurationProperties | 配置屬性繫結模組 | |
@EnableOAuth2Sso | OAuth2 單點登入模組 | |
Spring Cloud | @EnableEurekaServer | Eureka伺服器模組 |
@EnableConfigServer | 配置伺服器模組 | |
@EnableFeignClients | Feign客戶端模組 | |
@EnableZuulProxy | 服務閘道器 Zuul 模組 |
基於模式註解驅動(以@EnableWebMvc為例)
- 註解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
複製程式碼
- DelegatingWebMvcConfiguration
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
public DelegatingWebMvcConfiguration() {
}
@Autowired(
required = false
)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
protected void configurePathMatch(PathMatchConfigurer configurer) {
this.configurers.configurePathMatch(configurer);
}
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
this.configurers.configureContentNegotiation(configurer);
}
protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
this.configurers.configureAsyncSupport(configurer);
}
protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
this.configurers.configureDefaultServletHandling(configurer);
}
protected void addFormatters(FormatterRegistry registry) {
this.configurers.addFormatters(registry);
}
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry);
}
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
this.configurers.addResourceHandlers(registry);
}
protected void addCorsMappings(CorsRegistry registry) {
this.configurers.addCorsMappings(registry);
}
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
protected void configureViewResolvers(ViewResolverRegistry registry) {
this.configurers.configureViewResolvers(registry);
}
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
this.configurers.addArgumentResolvers(argumentResolvers);
}
protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
this.configurers.addReturnValueHandlers(returnValueHandlers);
}
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.configurers.configureMessageConverters(converters);
}
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
this.configurers.extendMessageConverters(converters);
}
protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
this.configurers.configureHandlerExceptionResolvers(exceptionResolvers);
}
protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
this.configurers.extendHandlerExceptionResolvers(exceptionResolvers);
}
@Nullable
protected Validator getValidator() {
return this.configurers.getValidator();
}
@Nullable
protected MessageCodesResolver getMessageCodesResolver() {
return this.configurers.getMessageCodesResolver();
}
}
複製程式碼
自定義@Enable模組
- 自定義註解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MyServiceConfiguration.class})
public @interface EnableMyService {
}
複製程式碼
- 自定義Configuration
@Configuration
public class MyServiceConfiguration {
@Bean
public String test() {
return "測試服務";
}
}
複製程式碼
- 呼叫
@EnableMyService
public class TestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext =
new SpringApplicationBuilder(TestApplication.class).web(WebApplicationType.NONE).run(args);
String test = configurableApplicationContext.getBean("test",String.class);
System.out.println(test);
}
}
複製程式碼
基於ImportSelector介面(以@EnableCaching為例)
- 註解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({CachingConfigurationSelector.class})
public @interface EnableCaching {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
複製程式碼
- ConfigurationSelector
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
private static final String PROXY_JCACHE_CONFIGURATION_CLASS = "org.springframework.cache.jcache.config.ProxyJCacheConfiguration";
private static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.cache.aspectj.AspectJCachingConfiguration";
private static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.cache.aspectj.AspectJJCacheConfiguration";
private static final boolean jsr107Present = ClassUtils.isPresent("javax.cache.Cache",CachingConfigurationSelector.class.getClassLoader());
private static final boolean jcacheImplPresent = ClassUtils.isPresent("org.springframework.cache.jcache.config.ProxyJCacheConfiguration",CachingConfigurationSelector.class.getClassLoader());
public CachingConfigurationSelector() {
}
public String[] selectImports(AdviceMode adviceMode) {
switch(adviceMode) {
case PROXY:
return this.getProxyImports();
case ASPECTJ:
return this.getAspectJImports();
default:
return null;
}
}
private String[] getProxyImports() {
List<String> result = new ArrayList();
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
if (jsr107Present && jcacheImplPresent) {
result.add("org.springframework.cache.jcache.config.ProxyJCacheConfiguration");
}
return StringUtils.toStringArray(result);
}
private String[] getAspectJImports() {
List<String> result = new ArrayList();
result.add("org.springframework.cache.aspectj.AspectJCachingConfiguration");
if (jsr107Present && jcacheImplPresent) {
result.add("org.springframework.cache.aspectj.AspectJJCacheConfiguration");
}
return StringUtils.toStringArray(result);
}
}
複製程式碼
- AdviceModeImportSelector(實現對selectImports重新封裝)
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode";
public AdviceModeImportSelector() {
}
protected String getAdviceModeAttributeName() {
return "mode";
}
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<?> annType = GenericTypeResolver.resolveTypeArgument(this.getClass(),AdviceModeImportSelector.class);
Assert.state(annType != null,"Unresolvable type argument for AdviceModeImportSelector");
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata,annType);
if (attributes == null) {
throw new IllegalArgumentException(String.format("@%s is not present on importing class '%s' as expected",annType.getSimpleName(),importingClassMetadata.getClassName()));
} else {
AdviceMode adviceMode = (AdviceMode)attributes.getEnum(this.getAdviceModeAttributeName());
String[] imports = this.selectImports(adviceMode);
if (imports == null) {
throw new IllegalArgumentException(String.format("Unknown AdviceMode: '%s'",adviceMode));
} else {
return imports;
}
}
}
@Nullable
protected abstract String[] selectImports(AdviceMode var1);
}
複製程式碼
- ImportSelector
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
複製程式碼
自定義@Enable模組
- 自定義註解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(MyServiceConfigurationSelector.class)
public @interface EnableMyService {
}
複製程式碼
- ConfigurationSelector
public class MyServiceConfigurationSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[] {MyServiceConfiguration.class.getName()};
}
}
複製程式碼
- 呼叫
@EnableMyService
public class TestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext =
new SpringApplicationBuilder(TestApplication.class).web(WebApplicationType.NONE).run(args);
String test = configurableApplicationContext.getBean("test",String.class);
System.out.println(test);
configurableApplicationContext.close();
}
}
複製程式碼
條件裝配@Profile
在例子中加入@Profile("dev")啟動會發生空指標異常
@Configuration
public class MyServiceConfiguration {
@Bean
@Profile("dev")
public String test() {
return "測試服務";
}
}
複製程式碼
指定啟動profile
@EnableMyService
public class TestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext =
new SpringApplicationBuilder(TestApplication.class).web(WebApplicationType.NONE).profiles("dev").run(args);
String test = configurableApplicationContext.getBean("test",String.class);
System.out.println(test);
configurableApplicationContext.close();
}
}
複製程式碼
條件裝配@Conditional
@Profile也是基於@Conditional來實現的
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class})
public @interface Profile {
String[] value();
}
複製程式碼
Condition
class ProfileCondition implements Condition {
ProfileCondition() {
}
public boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata) {
MultiValueMap<String,Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
Iterator var4 = ((List)attrs.get("value")).iterator();
Object value;
do {
if (!var4.hasNext()) {
return false;
}
value = var4.next();
} while(!context.getEnvironment().acceptsProfiles((String[])((String[])value)));
return true;
} else {
return true;
}
}
}
複製程式碼
模仿寫一個,Conditional
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {
// Java 系統屬性名稱
String key();
// Java 系統屬性值
String value();
}
複製程式碼
Condition
public class OnSystemPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata) {
Map<String,Object> attributes =
metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
String propertyName = String.valueOf(attributes.get("key"));
String propertyValue = String.valueOf(attributes.get("value"));
String javaPropertyValue = System.getProperty(propertyName);
return propertyValue.equals(javaPropertyValue);
}
}
複製程式碼
@Configuration
public class MyServiceConfiguration {
@Bean
@ConditionalOnSystemProperty(key = "user.name",value = "zhaozhihao")
public String test() {
return "測試服務";
}
}
複製程式碼
@EnableMyService
public class TestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext =
new SpringApplicationBuilder(TestApplication.class).web(WebApplicationType.NONE).run(args);
String test = configurableApplicationContext.getBean("test",String.class);
System.out.println(test);
configurableApplicationContext.close();
}
}
複製程式碼
Springboot自動裝配
- 定義:基於約定大於配置的原則,實現Spring元件自動裝配
- 裝配:
- 模式註解
- @Enable模組
- 條件裝配
- 工廠載入機制
- 實現類:SpringFactoriesLoader
- 配置:META-INF/spring.factories
- 實現
- 啟用自動裝配:@EnableAutoConfiguration
- 實現自動裝配:XXXAutoConfiguration
- 配置自動裝配:META-INF/spring.factories
SpringFactoriesLoader
public abstract class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader,MultiValueMap<String,String>> cache = new ConcurrentReferenceHashMap();
public SpringFactoriesLoader() {
}
public static <T> List<T> loadFactories(Class<T> factoryClass,@Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass,"'factoryClass' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryNames = loadFactoryNames(factoryClass,classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList(factoryNames.size());
Iterator var5 = factoryNames.iterator();
while(var5.hasNext()) {
String factoryName = (String)var5.next();
result.add(instantiateFactory(factoryName,factoryClass,classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
public static List<String> loadFactoryNames(Class<?> factoryClass,@Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,Collections.emptyList());
}
private static Map<String,List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String,String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?,?> entry = (Entry)var6.next();
List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
result.addAll((String)entry.getKey(),factoryClassNames);
}
}
cache.put(classLoader,result);
return result;
} catch (IOException var9) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]",var9);
}
}
}
private static <T> T instantiateFactory(String instanceClassName,Class<T> factoryClass,ClassLoader classLoader) {
try {
Class<?> instanceClass = ClassUtils.forName(instanceClassName,classLoader);
if (!factoryClass.isAssignableFrom(instanceClass)) {
throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
} else {
return ReflectionUtils.accessibleConstructor(instanceClass,new Class[0]).newInstance();
}
} catch (Throwable var4) {
throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(),var4);
}
}
}
複製程式碼
spring.factories
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
複製程式碼
自定義實現
- 啟用自動裝配
@EnableAutoConfiguration
public class TestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext =
new SpringApplicationBuilder(TestApplication.class).web(WebApplicationType.NONE).run(args);
String test = configurableApplicationContext.getBean("test",String.class);
System.out.println(test);
configurableApplicationContext.close();
}
}
複製程式碼
- 實現自動裝配
@Configuration //Spring 模式註解裝配
@EnableMyService //Spring @Enable 模組裝配
@ConditionalOnSystemProperty(key = "user.name",value = "zhaozhihao") //Spring 條件裝配
public class MyServiceAutoConfiguration {
}
複製程式碼
- 配置自動裝配實現
SpringApplication
準備階段
節選自SpringApplication,從構造器中我們大概可以推斷出準備階段需要經過primarySources合規性校驗、推斷 Web 應用型別、載入應用上下文初始器、載入應用事件監聽器、推斷引導類等步驟。
public class SpringApplication {
...
public SpringApplication(ResourceLoader resourceLoader,Class... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources,"PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = this.deduceWebApplicationType();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
...
}
複製程式碼
primarySources合規性校驗
節選自SpringApplication,載入primarySources呼叫的是BeanDefinitionLoader
public class SpringApplication {
...
protected void load(ApplicationContext context,Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = this.createBeanDefinitionLoader(this.getBeanDefinitionRegistry(context),sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
...
}
複製程式碼
節選BeanDefinitionLoader,可見其可以通過xml/註解載入
class BeanDefinitionLoader {
...
BeanDefinitionLoader(BeanDefinitionRegistry registry,Object... sources) {
Assert.notNull(registry,"Registry must not be null");
Assert.notEmpty(sources,"Sources must not be empty");
this.sources = sources;
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
this.xmlReader = new XmlBeanDefinitionReader(registry);
if (this.isGroovyPresent()) {
this.groovyReader = new GroovyBeanDefinitionReader(registry);
}
this.scanner = new ClassPathBeanDefinitionScanner(registry);
this.scanner.addExcludeFilter(new BeanDefinitionLoader.ClassExcludeFilter(sources));
}
...
}
複製程式碼
三種方式都是可以的,傳入的source只要是@Component的派生註解就行了,@SpringBootApplication只是提供了一些預設配置的組合註解
@EnableAutoConfiguration
public class TestApplication {
public static void main(String[] args) {
// 方式一
ConfigurableApplicationContext configurableApplicationContext =
SpringApplication.run(TestApplication.class,args);
String test = configurableApplicationContext.getBean("test",String.class);
System.out.println(test);
configurableApplicationContext.close();
// 方式二
// Set<String> sources = new HashSet<>();
// sources.add(TestApplication.class.getName());
// SpringApplication springApplication = new SpringApplication();
// springApplication.setSources(sources);
// ConfigurableApplicationContext configurableApplicationContext =
// springApplication.run(args);
// String test = configurableApplicationContext.getBean("test",String.class);
// System.out.println(test);
// configurableApplicationContext.close();
// 方式三
// ConfigurableApplicationContext configurableApplicationContext =
// new
// SpringApplicationBuilder(TestApplication.class).web(WebApplicationType.NONE).run(args);
// String test = configurableApplicationContext.getBean("test",String.class);
// System.out.println(test);
// configurableApplicationContext.close();
}
}
複製程式碼
推斷Web應用型別
節選SpringApplication程式碼,推斷邏輯在deduceWebApplicationType方法中,根據相關的類是否被裝配來進行判斷應用型別。顯然原始碼提示REACTIVE和SERVLET共存的時候,優先SERVLET,所以在引入netty的時候要把tomcat排除出去。
public class SpringApplication {
...
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler",(ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet",(ClassLoader)null)) {
return WebApplicationType.REACTIVE;
} else {
String[] var1 = WEB_ENVIRONMENT_CLASSES;
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
String className = var1[var3];
if (!ClassUtils.isPresent(className,(ClassLoader)null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
}
...
}
複製程式碼
除此之外,我們也可以通過配置強制選型
springApplication.setWebApplicationType(WebApplicationType.SERVLET);
複製程式碼
載入應用上下文初始器
節選自SpringApplication,通過SpringFactoriesLoader載入META-INF/spring.factories中的bean,通過AnnotationAwareOrderComparator.sort()方法進行排序載入
public class SpringApplication {
...
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes,Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type,classLoader));
List<T> instances = this.createSpringFactoriesInstances(type,parameterTypes,classLoader,args,names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
...
}
複製程式碼
自定義MyFirstApplicationContextInitializer,通過實現Ordered介面進行排序
public class MyFirstApplicationContextInitializer
implements ApplicationContextInitializer,Ordered {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println("MyFirstApplicationContextInitializer");
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
複製程式碼
自定義MySecondApplicationContextInitializer,通過@Order註解進行排序
@Order(Ordered.LOWEST_PRECEDENCE)
public class MySecondApplicationContextInitializer<C extends ConfigurableApplicationContext>
implements ApplicationContextInitializer<C> {
@Override
public void initialize(C c) {
System.out.println("MySecondApplicationContextInitializer");
}
}
複製程式碼
META-INF/spring.factories,此處順序和載入順序無關
# Initializers
org.springframework.context.ApplicationContextInitializer=\
com.springbootlearning.test.context.MySecondApplicationContextInitializer,\
com.springbootlearning.test.context.MyFirstApplicationContextInitializer
複製程式碼
執行
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class,args);
}
}
複製程式碼
效果
載入應用事件監聽器
實現原理類似載入應用上下文初始器,只不過裝配是要等待監聽的事件發生後
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MyFirstApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.println("MyFirstApplicationListener");
}
}
複製程式碼
public class MySecondApplicationListener
implements ApplicationListener<ContextRefreshedEvent>,Ordered {
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.println("MySecondApplicationListener");
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
複製程式碼
META-INF/spring.factories
# Application Listeners
org.springframework.context.ApplicationListener=\
com.springbootlearning.test.listener.MyFirstApplicationListener,\
com.springbootlearning.test.listener.MySecondApplicationListener
複製程式碼
效果
推斷引導類
節選SpringApplication程式碼,通過迴圈匹配棧內元素
public class SpringApplication {
...
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
StackTraceElement[] var2 = stackTrace;
int var3 = stackTrace.length;
for(int var4 = 0; var4 < var3; ++var4) {
StackTraceElement stackTraceElement = var2[var4];
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException var6) {
;
}
return null;
}
...
}
複製程式碼
執行階段
節選SpringApplication,通過檢視原始碼,我們大致可以發現整個過程:載入SpringApplicationRunListeners,然後開始執行,伴隨著各大監聽事件的發生,相應的監聽器被啟用,載入各自Bean,特別地environmentPrepared後開始載入context
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners,applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[]{ConfigurableApplicationContext.class},context);
this.prepareContext(context,environment,listeners,applicationArguments,printedBanner);
this.refreshContext(context);
this.afterRefresh(context,applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(),stopWatch);
}
listeners.started(context);
this.callRunners(context,applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context,var10,exceptionReporters,listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context,var9,(SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
}
複製程式碼
載入SpringApplicationRunListeners
利用 Spring 工廠載入機制,讀取 SpringApplicationRunListener 物件集合,並且封裝到組合類SpringApplicationRunListeners
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class[]{SpringApplication.class,String[].class};
return new SpringApplicationRunListeners(logger,this.getSpringFactoriesInstances(SpringApplicationRunListener.class,types,this,args));
}
複製程式碼
執行SpringApplicationRunListeners
監聽以下執行狀態
public interface SpringApplicationRunListener {
void starting();
void environmentPrepared(ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void started(ConfigurableApplicationContext context);
void running(ConfigurableApplicationContext context);
void failed(ConfigurableApplicationContext context,Throwable exception);
}
複製程式碼
監聽Springboot、Spring事件
Spring Boot 通過 SpringApplicationRunListener 的實現類 EventPublishingRunListener 利用 Spring Framework 事件 API ,廣播 Spring Boot 事件(initialMulticaster)
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
複製程式碼
public class EventPublishingRunListener implements SpringApplicationRunListener,Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application,String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
Iterator var3 = application.getListeners().iterator();
while(var3.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var3.next();
this.initialMulticaster.addApplicationListener(listener);
}
}
public int getOrder() {
return 0;
}
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application,this.args));
}
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application,this.args,environment));
}
public void contextPrepared(ConfigurableApplicationContext context) {
}
public void contextLoaded(ConfigurableApplicationContext context) {
ApplicationListener listener;
for(Iterator var2 = this.application.getListeners().iterator(); var2.hasNext(); context.addApplicationListener(listener)) {
listener = (ApplicationListener)var2.next();
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware)listener).setApplicationContext(context);
}
}
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application,context));
}
public void started(ConfigurableApplicationContext context) {
context.publishEvent(new ApplicationStartedEvent(this.application,context));
}
public void running(ConfigurableApplicationContext context) {
context.publishEvent(new ApplicationReadyEvent(this.application,context));
}
public void failed(ConfigurableApplicationContext context,Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application,context,exception);
if (context != null && context.isActive()) {
context.publishEvent(event);
} else {
if (context instanceof AbstractApplicationContext) {
Iterator var4 = ((AbstractApplicationContext)context).getApplicationListeners().iterator();
while(var4.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var4.next();
this.initialMulticaster.addApplicationListener(listener);
}
}
this.initialMulticaster.setErrorHandler(new EventPublishingRunListener.LoggingErrorHandler());
this.initialMulticaster.multicastEvent(event);
}
}
private static class LoggingErrorHandler implements ErrorHandler {
private static Log logger = LogFactory.getLog(EventPublishingRunListener.class);
private LoggingErrorHandler() {
}
public void handleError(Throwable throwable) {
logger.warn("Error calling ApplicationEventListener",throwable);
}
}
}
複製程式碼
Spring Framework 事件/監聽器程式設計模型
- Spring 應用事件
- 普通應用事件:ApplicationEvent
- 應用上下文事件:ApplicationContextEvent
- Spring 應用監聽器
- 介面程式設計模型:ApplicationListener
- 註解程式設計模型:@EventListener
- Spring 應用事廣播器
- 介面:ApplicationEventMulticaster
- 實現類:SimpleApplicationEventMulticaster
- 模式:同步或非同步
程式碼
// 建立上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 註冊應用事件監聽器
context.addApplicationListener(event -> {
System.out.println("監聽到事件: " + event);
});
// 啟動上下文
context.refresh();
// 傳送事件PayloadApplicationEvent
context.publishEvent("first");
context.publishEvent("second");
// 關閉上下文
context.close();
複製程式碼
效果
自定義執行監聽器
MyRunListener
public class MyRunListener implements SpringApplicationRunListener {
public MyRunListener(SpringApplication application,String[] args){}
@Override
public void starting() {
System.out.println("1.MyRunListener starting");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System.out.println("2.MyRunListener environmentPrepared");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("3.MyRunListener contextPrepared");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("4.MyRunListener contextLoaded");
}
@Override
public void started(ConfigurableApplicationContext context) {
System.out.println("5.MyRunListener started");
}
@Override
public void running(ConfigurableApplicationContext context) {
System.out.println("6.MyRunListener running");
}
@Override
public void failed(ConfigurableApplicationContext context,Throwable exception) {
System.out.println("7.MyRunListener failed");
}
}
複製程式碼
META-INF/spring.factorie
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.springbootlearning.test.run.MyRunListener
複製程式碼
引導類
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class,args);
}
}
複製程式碼
效果
1.MyRunListener starting
2.MyRunListener environmentPrepared
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__,| / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.1.RELEASE)
MyFirstApplicationContextInitializer
MySecondApplicationContextInitializer
3.MyRunListener contextPrepared
2019-12-08 23:02:15.139 INFO 3110 --- [ main] c.s.test.TestApplication : Starting TestApplication on zzh.local with PID 3110 (/Users/zhaozhihao/Documents/test/target/classes started by zhaozhihao in /Users/zhaozhihao/Documents/test)
2019-12-08 23:02:15.141 INFO 3110 --- [ main] c.s.test.TestApplication : No active profile set,falling back to default profiles: default
4.MyRunListener contextLoaded
2019-12-08 23:02:15.188 INFO 3110 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5be6e01c: startup date [Sun Dec 08 23:02:15 CST 2019]; root of context hierarchy
2019-12-08 23:02:15.736 INFO 3110 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
MyFirstApplicationListener
MySecondApplicationListener
2019-12-08 23:02:15.746 INFO 3110 --- [ main] c.s.test.TestApplication : Started TestApplication in 0.898 seconds (JVM running for 1.446)
5.MyRunListener started
6.MyRunListener running
2019-12-08 23:02:15.748 INFO 3110 --- [ Thread-4] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5be6e01c: startup date [Sun Dec 08 23:02:15 CST 2019]; root of context hierarchy
2019-12-08 23:02:15.749 INFO 3110 --- [ Thread-4] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
複製程式碼
Spring MVC
圖和步驟轉載自原文連線
步驟
- 發起請求到前端控制器(DispatcherServlet)
- 前端控制器請求HandlerMapping查詢 Handler (可以根據xml配置、註解進行查詢)
- 處理器對映器HandlerMapping向前端控制器返回Handler,HandlerMapping會把請求對映為HandlerExecutionChain物件(包含一個Handler處理器(頁面控制器)物件,多個HandlerInterceptor攔截器物件),通過這種策略模式,很容易新增新的對映策略
- 前端控制器呼叫處理器介面卡去執行Handler
- 處理器介面卡HandlerAdapter將會根據適配的結果去執行Handler
- Handler執行完成給介面卡返回ModelAndView
- 處理器介面卡向前端控制器返回ModelAndView (ModelAndView是springmvc框架的一個底層物件,包括 Model和view)
- 前端控制器請求檢視解析器去進行檢視解析 (根據邏輯檢視名解析成真正的檢視(jsp)),通過這種策略很容易更換其他檢視技術,只需要更改檢視解析器即可
- 檢視解析器向前端控制器返回View
- 前端控制器進行檢視渲染 (檢視渲染將模型資料(在ModelAndView物件中)填充到request域)
- 前端控制器向用戶響應結果
基於配置
web.xml
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
複製程式碼
app-context.xml
<context:component-scan base-package="com.test"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
複製程式碼
斷點分析
- HandlerMapping
- HandlerAdapter
- Controller
- ViewResolver
基於註解
上述專案註解化改造
Servlet 3.0+ & Spring Framework 3.1 +就可以實現註解裝配,Servlet SPI 的ServletContainerInitializer 被 Spring Framework 封裝為SpringServletContainerInitializer
- Spring SPI
- 基礎介面:WebApplicationInitializer
- 程式設計驅動:AbstractDispatcherServletInitializer
- 註解驅動:AbstractAnnotationConfigDispatcherServletInitializer
掃描
@ComponentScan(basePackages = "com.test")
public class DispatcherServletConfiguration {
}
複製程式碼
WebMvcConfigurer
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
複製程式碼
AbstractAnnotationConfigDispatcherServletInitializer
public class DefaultAnnotationConfigDispatcherServletInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() { // web.xml
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() { // DispatcherServlet
return new Class[]{DispatcherServletConfiguration.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
複製程式碼
Controller
@Controller
public class HelloWorldController {
@RequestMapping("/test")
public String index() {
return "index";
}
}
複製程式碼
其他相關MVC註解
註解 | 描述 |
---|---|
@ModelAttribute | 註冊模型屬性 |
@RequestHeader | 讀取請求頭 |
@CookieValue | 讀取Cookie |
@Valid | 校驗引數 |
@Validated | 校驗引數 |
@ExceptionHandler | 異常處理 |
@ControllerAdvice | 切面通知 |
Spring Boot 自動裝配
自動裝配DispatcherServlet
DispatcherServletAutoConfiguration(spring.factories)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
@EnableConfigurationProperties(ServerProperties.class)
public class DispatcherServletAutoConfiguration {
...
}
複製程式碼
由原始碼可以分析出,裝配DispatcherServletAutoConfiguration需要滿足一些條件
- @ConditionalOnWebApplication:需要SERVLET容器
- @ConditionalOnClass:API需要DispatcherServlet
- @AutoConfigureAfter(相對順序):需要在ServletWebServerFactoryAutoConfiguration後載入
- @AutoConfigureOrder(絕對順序):Ordered.HIGHEST_PRECEDENCE,最小整數即最高優先順序
自動裝配WebMvc
WebMvcAutoConfiguration(spring.factories)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class,DispatcherServlet.class,WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
...
public static class WebMvcAutoConfigurationAdapter
implements WebMvcConfigurer,ResourceLoaderAware {
...
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,WebMvcProperties mvcProperties,ListableBeanFactory beanFactory,@Lazy HttpMessageConverters messageConverters,ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConverters = messageConverters;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider
.getIfAvailable();
}
...
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
}
...
}
複製程式碼
由原始碼可以分析出,裝配WebMvcAutoConfiguration中有一個條件即@ConditionalOnMissingBean(WebMvcConfigurationSupport.class),也就是說WebMvcConfigurationSupport在的時候就不會裝配,而@EnableWebMvc註解的DelegatingWebMvcConfiguration就繼承了WebMvcConfigurationSupport,所以當@EnableWebMvc存在的時候,我們可以自由定義,其實在WebMvcAutoConfiguration中也裝配了繼承DelegatingWebMvcConfiguration的EnableWebMvcConfiguration
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
複製程式碼
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
...
}
複製程式碼
Spring Boot 實現以及Filter&Interceptor&Aspect
型別 | 描述 |
---|---|
Filter | 過濾器依賴於servlet容器。在實現上,基於函式回撥,它可以對幾乎所有請求進行過濾,一個過濾器例項只能在容器初始化時呼叫一次,隨著web的銷燬而銷燬,filter初始化在dispathServlet前面,依賴注入的邏輯不要在filter中使用 |
Interceptor | 攔截器基於Java的反射機制,屬於面向切面程式設計(AOP)的一種運用 |
Aspect | 日誌,事務,請求引數安全驗證等 |
引導類
@SpringBootApplication(scanBasePackages = "com.test")
public class SpringBootWebMvcBootstrap {
public static void main(String[] args) {
SpringApplication.run(SpringBootWebMvcBootstrap.class,args);
}
}
複製程式碼
Controller
@RestController
public class HelloWorldController {
@RequestMapping("")
public String index() {
return "index";
}
}
複製程式碼
Filter
@WebFilter(filterName = "MyFirstFilter",urlPatterns = "/*")
@Component
@Order(1)
public class MyFirstFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFirstFilter init");
}
@Override
public void doFilter(
ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)
throws IOException,ServletException {
System.out.println("MyFirstFilter begin");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("MyFirstFilter end");
}
@Override
public void destroy() {
System.out.println("MyFirstFilter destroy");
}
}
複製程式碼
@WebFilter(filterName = "MySecondFilter",urlPatterns = "/*")
@Component
@Order(2)
public class MySecondFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MySecondFilter init");
}
@Override
public void doFilter(
ServletRequest servletRequest,ServletException {
System.out.println("MySecondFilter begin");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("MySecondFilter end");
}
@Override
public void destroy() {
System.out.println("MySecondFilter destroy");
}
}
複製程式碼
Interceptor
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new HandlerInterceptor() {
@Override
public boolean preHandle(
HttpServletRequest request,HttpServletResponse response,Object handler)
throws Exception {
System.out.println("MyFirstInterceptor:preHandle");
return true;
}
//postHandler方法的執行,當controller內部有異常,posthandler方法是不會執行的
@Override
public void postHandle(
HttpServletRequest request,Object handler,@Nullable ModelAndView modelAndView)
throws Exception {
System.out.println("MyFirstInterceptor:postHandle");
}
@Override
public void afterCompletion(
HttpServletRequest request,@Nullable Exception ex)
throws Exception {
System.out.println("MyFirstInterceptor:afterCompletion");
}
}).order(1).addPathPatterns("/");
registry.addInterceptor(
new HandlerInterceptor() {
@Override
public boolean preHandle(
HttpServletRequest request,Object handler)
throws Exception {
System.out.println("MySecondInterceptor:preHandle");
return true;
}
//postHandler方法的執行,當controller內部有異常,posthandler方法是不會執行的
@Override
public void postHandle(
HttpServletRequest request,@Nullable ModelAndView modelAndView)
throws Exception {
System.out.println("MySecondInterceptor:postHandle");
}
@Override
public void afterCompletion(
HttpServletRequest request,@Nullable Exception ex)
throws Exception {
System.out.println("MySecondInterceptor:afterCompletion");
}
}).order(2).addPathPatterns("/");
}
}
複製程式碼
Aspect
@Aspect
@Component
@Order(1)
public class MyFirstAspect {
@Pointcut("execution(* com.test.controller.*.*(..))")
public void pointcut() {}
@Before("pointcut()")
public void begin() {
System.out.println("MyFirstAspect:Before");
}
@After("pointcut()")
public void commit() {
System.out.println("MyFirstAspect:After");
}
@AfterReturning("pointcut()")
public void afterReturning(JoinPoint joinPoint) {
System.out.println("MyFirstAspect:AfterReturning");
}
@AfterThrowing("pointcut()")
public void afterThrowing() {
System.out.println("MyFirstAspect:AfterThrowing");
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
System.out.println("MyFirstAspect:Around begin");
return joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
throw e;
} finally {
System.out.println("MyFirstAspect:Around end");
}
}
}
複製程式碼
@Aspect
@Component
@Order(2)
public class MySecondAspect {
@Pointcut("execution(* com.test.controller.*.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void begin() {
System.out.println("MySecondAspect:Before");
}
@After("pointcut()")
public void commit() {
System.out.println("MySecondAspect:After");
}
@AfterReturning("pointcut()")
public void afterReturning(JoinPoint joinPoint) {
System.out.println("MySecondAspect:AfterReturning");
}
@AfterThrowing("pointcut()")
public void afterThrowing() {
System.out.println("MySecondAspect:AfterThrowing");
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
System.out.println("MySecondAspect:Around begin");
return joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
throw e;
} finally {
System.out.println("MySecondAspect:Around end");
}
}
}
複製程式碼
本質也是Aspect
@ControllerAdvice(assignableTypes = HelloWorldController.class)
@Order(3)
public class MyFirstControllerAdvice {
@ModelAttribute("acceptLanguage")
public String acceptLanguage(@RequestHeader("Accept-Language") String acceptLanguage){
System.out.println("切面引數:acceptLanguage");
return acceptLanguage;
}
}
複製程式碼
效果
# Filter初始化只執行一次
MyFirstFilter init
MySecondFilter init
複製程式碼
MyFirstFilter begin
MySecondFilter begin
MyFirstInterceptor:preHandle
MySecondInterceptor:preHandle
MyFirstAspect:Around begin
MyFirstAspect:Before
MySecondAspect:Around begin
MySecondAspect:Before
MyFirstControllerAdvice
MySecondAspect:Around end
MySecondAspect:After
MySecondAspect:AfterReturning
MyFirstAspect:Around end
MyFirstAspect:After
MyFirstAspect:AfterReturning
MyFirstAspect:Around begin
MyFirstAspect:Before
MySecondAspect:Around begin
MySecondAspect:Before
MySecondAspect:Around end
MySecondAspect:After
MySecondAspect:AfterReturning
MyFirstAspect:Around end
MyFirstAspect:After
MyFirstAspect:AfterReturning
MySecondInterceptor:postHandle
MyFirstInterceptor:postHandle
MySecondInterceptor:afterCompletion
MyFirstInterceptor:afterCompletion
MySecondFilter end
MyFirstFilter end
複製程式碼
整體圖示
Aspect圖示- @Order(1):越小越先執行
- around->before->around->after->afterReturning
- 橙色:@Order(1),綠色:@Order(2)