
专注于java后端技术,热衷于开源框架的源码阅读,曾经的老码农,现着力于高端IT人才的发现与培养
现在第三方登录的例子数见不鲜。其实在这种示例当中,oauth2.0是使用比较多的一种授权登录的标准。oauth2.0也是从oauth1.0升级过来的。那么关于oauth2.0相关的概念及其原理,大家可以参考这篇文章,这篇文章中会有更详细的解释,下来我们直接进入正题。 1.1、gradle依赖 compile('org.springframework.cloud:spring-cloud-starter-oauth2') compile('org.springframework.cloud:spring-cloud-starter-security') 在这里我直接引入的是spring-cloud的依赖项,这种依赖的jar包更全面一些,这里面的核心基础还是spring-security。这里SpringBoot的版本为2.0.6.REALEASE 1.2、@EnableAuthorizationServer 在这里我着重强调一下这个注解:@EnableAuthorizationServer,这个注解源代码如下: @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class}) public @interface EnableAuthorizationServer { } 这个注解主要是导入两个配置类,分别是: AuthorizationServerEndpointsConfiguration,这个配置类主要配置授权端点,获取token的端点。大家就把对应的端点想象成controller即可,在这个controller下开放了若干个@RequestMapping,比如常见的有:/oauth/authorize(授权路径),/oauth/token(获取token)等 AuthorizationServerSecurityConfiguration,主要是做spring-security的安全配置,我们可以看一下相关代码: public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private List<AuthorizationServerConfigurer> configurers = Collections.emptyList(); @Autowired private ClientDetailsService clientDetailsService; @Autowired private AuthorizationServerEndpointsConfiguration endpoints; @Autowired public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception { for (AuthorizationServerConfigurer configurer : configurers) { configurer.configure(clientDetails); } } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // Over-riding to make sure this.disableLocalConfigureAuthenticationBldr = false // This will ensure that when this configurer builds the AuthenticationManager it will not attempt // to find another 'Global' AuthenticationManager in the ApplicationContext (if available), // and set that as the parent of this 'Local' AuthenticationManager. // This AuthenticationManager should only be wired up with an AuthenticationProvider // composed of the ClientDetailsService (wired in this configuration) for authenticating 'clients' only. } @Override protected void configure(HttpSecurity http) throws Exception { //....省略部分代码 String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token"); String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key"); String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token"); if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) { UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class); endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService); } // @formatter:off //上述节点的请求需要授权验证 http .authorizeRequests() .antMatchers(tokenEndpointPath).fullyAuthenticated() .antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess()) .antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess()) .and() .requestMatchers() .antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath) .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER); // @formatter:on http.setSharedObject(ClientDetailsService.class, clientDetailsService); } protected void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { for (AuthorizationServerConfigurer configurer : configurers) { configurer.configure(oauthServer); } } } 1.2.1、AuthorizationServerConfigurer 这个接口是认证授权配置的核心接口,不过既然是SpringBoot我们就先来看看它怎么帮我们装配的,我们可以在org.springframework.boot.autoconfigure.security.oauth2.authserver这个包下面找到对应配置的Bean: @Configuration @ConditionalOnClass(EnableAuthorizationServer.class) @ConditionalOnMissingBean(AuthorizationServerConfigurer.class) @ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class) @EnableConfigurationProperties(AuthorizationServerProperties.class) public class OAuth2AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { //.... @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //默认基于内存创建ClientDetails ClientDetailsServiceBuilder<InMemoryClientDetailsServiceBuilder>.ClientBuilder builder = clients .inMemory().withClient(this.details.getClientId()); builder.secret(this.details.getClientSecret()) .resourceIds(this.details.getResourceIds().toArray(new String[0])) .authorizedGrantTypes( this.details.getAuthorizedGrantTypes().toArray(new String[0])) .authorities( AuthorityUtils.authorityListToSet(this.details.getAuthorities()) .toArray(new String[0])) .scopes(this.details.getScope().toArray(new String[0])); if (this.details.getAutoApproveScopes() != null) { builder.autoApprove( this.details.getAutoApproveScopes().toArray(new String[0])); } if (this.details.getAccessTokenValiditySeconds() != null) { builder.accessTokenValiditySeconds( this.details.getAccessTokenValiditySeconds()); } if (this.details.getRefreshTokenValiditySeconds() != null) { builder.refreshTokenValiditySeconds( this.details.getRefreshTokenValiditySeconds()); } if (this.details.getRegisteredRedirectUri() != null) { builder.redirectUris( this.details.getRegisteredRedirectUri().toArray(new String[0])); } } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { if (this.tokenConverter != null) { endpoints.accessTokenConverter(this.tokenConverter); } if (this.tokenStore != null) { endpoints.tokenStore(this.tokenStore); } if (this.details.getAuthorizedGrantTypes().contains("password")) { endpoints.authenticationManager(this.authenticationManager); } } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.passwordEncoder(NoOpPasswordEncoder.getInstance()); if (this.properties.getCheckTokenAccess() != null) { security.checkTokenAccess(this.properties.getCheckTokenAccess()); } if (this.properties.getTokenKeyAccess() != null) { security.tokenKeyAccess(this.properties.getTokenKeyAccess()); } if (this.properties.getRealm() != null) { security.realm(this.properties.getRealm()); } } @Configuration @ConditionalOnMissingBean(BaseClientDetails.class) protected static class BaseClientDetailsConfiguration { private final OAuth2ClientProperties client; protected BaseClientDetailsConfiguration(OAuth2ClientProperties client) { this.client = client; } /** 由此可知它会寻找security.oauth2.client的配置 */ @Bean @ConfigurationProperties(prefix = "security.oauth2.client") public BaseClientDetails oauth2ClientDetails() { BaseClientDetails details = new BaseClientDetails(); if (this.client.getClientId() == null) { this.client.setClientId(UUID.randomUUID().toString()); } details.setClientId(this.client.getClientId()); details.setClientSecret(this.client.getClientSecret()); details.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "password", "client_credentials", "implicit", "refresh_token")); details.setAuthorities( AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); details.setRegisteredRedirectUri(Collections.<String>emptySet()); return details; } } } 如果没有用spring-boot的用户,可以也可以参考上述的配置方法,自行配置 1.3、application.yml的配置 根据上述代码我们可以知道,springboot通过外部化配置的security.oauth2.client的前缀来配置客户端。那么因此我们不妨在外部化配置文件里做如下配置: server: port: 8080 security: oauth2: client: client-id: root client-secret: root scope: - email - username - face spring: security: user: name: root password: root roles: ADMIN 这里先做最基本的配置,配置client-id,client-secret,scope。特别注意oauth2.0一定要先经过springsecurity的auth认证,因此需要在这里配置一个内存用户名与密码为root与root 1.4、配置资源服务器 通过资源服务器来保护我们指定的资源,必须在获取授权认证的时候才能访问。在SpringBoot当中,我们可以通过@EnableResourceServer注解来开启此功能。该注解定义如下: @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(ResourceServerConfiguration.class) public @interface EnableResourceServer { } 我们可以看到这个注解导入了默认的资源配置信息:ResourceServerConfiguration,它的源代码如下: @Configuration public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered { //.... @Override protected void configure(HttpSecurity http) throws Exception { ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer(); ResourceServerTokenServices services = resolveTokenServices(); if (services != null) { resources.tokenServices(services); } else { if (tokenStore != null) { resources.tokenStore(tokenStore); } else if (endpoints != null) { resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore()); } } if (eventPublisher != null) { resources.eventPublisher(eventPublisher); } //配置资源 for (ResourceServerConfigurer configurer : configurers) { configurer.configure(resources); } // @formatter:off http.authenticationProvider(new AnonymousAuthenticationProvider("default")) // N.B. exceptionHandling is duplicated in resources.configure() so that // it works .exceptionHandling() .accessDeniedHandler(resources.getAccessDeniedHandler()).and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .csrf().disable(); // @formatter:on http.apply(resources); if (endpoints != null) { // Assume we are in an Authorization Server http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping())); } for (ResourceServerConfigurer configurer : configurers) { // Delegates can add authorizeRequests() here configurer.configure(http); } //如果没有任何配置资源,则所有请求保护 if (configurers.isEmpty()) { // Add anyRequest() last as a fall back. Spring Security would // replace an existing anyRequest() matcher with this one, so to // avoid that we only add it if the user hasn't configured anything. http.authorizeRequests().anyRequest().authenticated(); } } //.... } 在这里主要是配置资源服务器的配置,我们可以得到如下几点信息: 资源配置的核心ResourceServerConfigurer,在这里如果没有任何配置,则所有请求都要进行token认证 TokenStore 主要定义了对token的增删改查操作,用于持久化token ResourceServerTokenServices 资源服务的service(服务层),这里主要还是根据token来拿到OAuth2Authentication与OAuth2AccessToken 1.5、完整示例 1.5.1、资源认证配置 @Configuration @EnableResourceServer public class ResourceConfigure extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and().authorizeRequests().antMatchers("/free/**").permitAll().and() .authorizeRequests().anyRequest().authenticated() .and().formLogin().permitAll();//必须认证过后才可以访问 } } 在这里如果以/free/**请求路径的,都允许直接访问。否则,都必须携带access_token才能访问。 1.5.2 、授权认证配置 @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().requestMatchers().anyRequest().and().authorizeRequests() .antMatchers("/oauth/*").authenticated().and().formLogin().permitAll(); } } 根据上文所述,AuthorizationServerEndpoint与TokenEndpoint会开放/oauth/authorize与/oauth/token端点,因此我们必须保证访问端点进行授权认证前,通过springsecurity的用户认证,因此在这里配置了/oauth/* 1.5.3、启动类 @SpringBootApplication @EnableAuthorizationServer @Controller public class AuthorizationServer { @GetMapping("/order") public ResponseEntity<String> order() { ResponseEntity<String> responseEntity = new ResponseEntity("order", HttpStatus.OK); return responseEntity; } @GetMapping("/free/test") public ResponseEntity<String> test() { ResponseEntity<String> responseEntity = new ResponseEntity("free", HttpStatus.OK); return responseEntity; } public static void main(String[] args) { SpringApplication.run(AuthorizationServer.class, args); } } 1.5.4、访问请求 首先我们通过postman 访问http://localhost:8080/order会得到如下界面: 此时我们明显可以看到对应的资源需要携带有效的token才可以访问,那么我们此时要在postman的Authorization进行oauth2.0配置认证。截图如下: 在这里点击Get New Access Token 来从认证服务器获取token,点击后配置如下: ` scope配置对应application.yml中的配置信息,这里面可以放置用户的属性信息,比如说昵称 头像 电话等等 State代表状态码,设置一个State标志 回调地址这里必须配置,通过这个地址当同意授权后会返回一个认证的code给我们,我们根据这个code请求token 认证地址与获取token的地址请填写,相关Endpoint生成的地址 当经过一连串认证后,我们即可拿到token: 当我们获取到最新的token以后,我们即可访问到对应的请求资源:
在微服务体系当中,监控是必不可少的。当系统环境超过指定的阀值以后,需要提醒指定的运维人员或开发人员进行有效的防范,从而降低系统宕机的风险。在CNCF云计算平台中,Prometheus+Grafana是比较通用的解决方案,在SpringBoot2.0以后metrics默认与micrometer集成,而micrometer有关于Prometheus的MeterRegistry规范的实现,因此我们通过Micrometer这个桥梁能将SpringBoot的监控数据与Prometheus展示出来。然后通过Grafana提供的UI界面进行数据的实时展示。 Prometheus从对应的节点地址获取度量数据并在本地存储所有数据样例根据相关规则将现有数据聚合并记录新的时间序列(或者生成警报)。可以使用Grafana或其他API消费者来可视化收集的数据。 1. 编写SpringBoot项目并用docker启动 1.1 gradle依赖 compile 'org.springframework.boot:spring-boot-starter-actuator' compile 'io.micrometer:micrometer-registry-prometheus:latest.release' compile 'io.micrometer:micrometer-core:1.1.0' 1.2 开放prometheus的端点 在application.properties中编辑如下配置 management.endpoints.web.exposure.include=prometheus,health,info 1.3 配置与grafana集成所需的bean /** * 为了和grafana集成,因此必须配置这个bean * @return */ @Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() { return registry -> registry.config().commonTags("application", "MYAPPNAME"); } 具体可以参考:micrometer 1.4 编写docker-compose.yml文件启动spring-boot应用 docker-compose.yml version: '3' services: application: image: java working_dir: /apps volumes: - "${project.home}/build/libs:/apps" container_name: app networks: - node1 hostname: application entrypoint: java -jar spring-boot-project-0.0.1-SNAPSHOT.jar ports: - "8080:8080" networks: node1: 当启动完毕后,我们可以运行如下命令来查看,是否有对应的信息输出: curl -G http://localhost:8080/actuator/prometheus 当这里得到如下结果时,证明我们的SpringBoot项目提供好与prometheus所需的数据了: # HELP tomcat_global_request_max_seconds # TYPE tomcat_global_request_max_seconds gauge tomcat_global_request_max_seconds{application="spring-boot",name="http-nio-8080",} 0.0 # HELP tomcat_sessions_created_sessions_total # TYPE tomcat_sessions_created_sessions_total counter tomcat_sessions_created_sessions_total{application="spring-boot",} 0.0 # HELP jvm_gc_live_data_size_bytes Size of old generation memory pool after a full GC # TYPE jvm_gc_live_data_size_bytes gauge jvm_gc_live_data_size_bytes{application="spring-boot",} 1.9078616E7 # HELP process_files_open_files The open file descriptor count # TYPE process_files_open_files gauge process_files_open_files{application="spring-boot",} 38.0 # HELP tomcat_threads_current_threads # TYPE tomcat_threads_current_threads gauge tomcat_threads_current_threads{application="spring-boot",name="http-nio-8080",} 10.0 # HELP jvm_gc_max_data_size_bytes Max size of old generation memory pool # TYPE jvm_gc_max_data_size_bytes gauge jvm_gc_max_data_size_bytes{application="spring-boot",} 3.49700096E8 # HELP tomcat_servlet_request_seconds # TYPE tomcat_servlet_request_seconds summary tomcat_servlet_request_seconds_count{application="spring-boot",name="default",} 0.0 2 安装Prometheus 我们可以借助于docker进行安装,在这里由于springboot项目我也是使用docker进行部署的,因此我们保证Prometheus的容器与SpringBoot的容器保证在同一个网络下: docker run --name prom --link app:app --net spring-boot-project_node1 --hostname prom -p 9090:9090 -v /tmp/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus 安装完毕后,我们编辑/tmp/prometheus.yml文件进行配置: global: scrape_interval: 15s evaluation_interval: 15s rule_files: # - "first.rules" # - "second.rules" scrape_configs: - job_name: 'spring' metrics_path: '/actuator/prometheus' static_configs: - targets: ['application:8080'] 在这里我们指定一个job,这个job会去指定的地址获取监控数据,那么在这里targets指定的是我们SpringBoot项目的actutator的监控地址,application对应的是springboot容器的hostname。 启动以后,我们运行http://localhost:9090 可以访问Prometheus的界面: 当出现这种界面就说明 Prometheus已经可以监控到Springboot的提供的数据了 3 安装Grafana 虽然Prometheus提供的有一个简单的UI界面,但是使用起来不那么方便同时也并不好看。不过,我们可以利用Grafana来提供更直接更美观的UI展示界面。同时,Grafana能够提供多种数据源,让我们进行不同类型的中间件或服务器监控。官网地址。同样我们使用docker来构建grafana容器。grafana所有的配置都在conf/grafana.ini里进行设置。不过它能通过docker的环境变量参数-e进行覆盖,它的基本格式为:GF_<SectionName>_<KeyName>。例如: $ docker run \ -d \ -p 3000:3000 \ --name=grafana \ --net=spring-boot-project_node1 --link=prom -e "GF_SERVER_ROOT_URL=http://grafana.server.name" \ -e "GF_SECURITY_ADMIN_PASSWORD=secret" \ grafana/grafana 当启动完毕后,我们可以访问http://localhost:3000可以访问到对应的界面,其默认情况下都会跳转至登录界面,我们可以在登录界面上输入用户名admin与密码admin,这样会以管理员身份进行登录。接下来我们需要创建一个prometheus的数据源: 此处我们设置prometheus的地址,这里我的设置为http://prom:9090 用以连接prometheus的监控(注意prom是docker容器prometheus的hostname),剩余的一些设置比如说Auth,Advanced HTTP Settings请按需进行配置。此时我们需要在这个地址里下一个用于监控micrometer的Dashboard:我们需要下载这个dashboard对应的json数据: 然后我们在grafana上传json脚本: 当导入成功后我们即可以展示出如下监控界面: 3.1 报警信息配置 在这里我以qq邮箱为例,首先我们必须开启smtp许可,在这里我们会收到安全码用于将来的密码设置。那么我们可以在docker启动容器时设置环境变量来配置邮件服务的信息: docker run -p 3000:3000 --env GF_SMTP_ENABLED=true --env GF_SMTP_HOST=smtp.qq.com:465 --env GF_SMTP_USER=xxxx(用户名) --env GF_SMTP_PASSWORD=(安全码) --env GF_SMTP_SKIP_VERIFY=true --env GF_SMTP_FROM_ADDRESS=xxxxx@qq.com --name grafana grafana/grafana:latest 相关配置我们可以参考:地址,当我们启动成功以后,我们可以参考如下界面进行配置:收到测试邮件后,我们需要设置监控值,在这里我以内存监控举例:同时我们需要配置发送邮件具体内容: 最后我们保存我们设置的Dashboard即可
一、背景 随着业务复杂度的提升以及微服务的兴起,传统单一项目会被按照业务规则进行垂直拆分,另外为了防止单点故障我们也会将重要的服务模块进行集群部署,通过负载均衡进行服务的调用。那么随着节点的增多,各个服务的日志也会散落在各个服务器上。这对于我们进行日志分析带来了巨大的挑战,总不能一台一台的登录去下载日志吧。那么我们需要一种收集日志的工具将散落在各个服务器节点上的日志收集起来,进行统一的查询及管理统计。那么ELK就可以做到这一点。 ELK是ElasticSearch+Logstash+Kibana的简称,在这里我分别对如上几个组件做个简单的介绍: 1.1、ElasticSearch(简称ES) Elasticsearch是一个高度可扩展的开源全文搜索和分析引擎。它允许您快速、实时地存储、搜索和分析大量数据。它通常用作底层引擎/技术,为具有复杂搜索特性和需求的应用程序提供动力。我们可以借助如ElasticSearch完成诸如搜索,日志收集,反向搜索,智能分析等功能。ES设计的目标: 快速实时搜索 Elasticsearch是一个实时搜索平台。这意味着,从索引文档到可搜索文档,存在轻微的延迟(通常为一秒)。 集群 集群是一个或多个节点(服务器)的集合,这些节点(服务器)一起保存整个数据,并提供跨所有节点的联合索引和搜索功能。集群由一个惟一的名称来标识,默认情况下该名称为“elasticsearch”。这个名称很重要,因为节点只能是集群的一部分,如果节点被设置为通过其名称加入集群的话。确保不要在不同的环境中重用相同的集群名称,否则可能会导致节点加入错误的集群。例如,您可以使用logging-dev、logging-test和logging-prod开发、测试和生产集群。 节点 节点是单个服务器,它是集群的一部分,它用来存储数据,并参与集群的索引和搜索功能。与集群一样,节点的名称默认为在启动时分配给节点的随机惟一标识符(UUID)。如果不需要默认值,可以定义任何节点名称。这个名称对于管理非常重要,因为您想要确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。 索引 索引是具有类似特征的文档的集合。例如,您可以有一个客户数据索引、另一个产品目录索引和另一个订单数据索引。索引由一个名称标识(必须是小写的),该名称用于在对其中的文档执行索引、搜索、更新和删除操作时引用索引。在单个集群中,可以定义任意数量的索引。 文档 文档是可以建立索引的基本信息单元。例如,可以为单个客户提供一个文档,为单个产品提供一个文档,为单个订单提供另一个文档。这个文档用JSON (JavaScript对象符号)表示。在索引中,可以存储任意数量的文档。请注意,尽管文档在物理上驻留在索引中,但实际上文档必须被索引/分配到索引中的类型中。 1.2、Logstash Logstash是一个开源数据收集引擎,具有实时流水线功能。Logstash可以动态地将来自不同数据源的数据统一起来,并将数据规范化后(通过Filter过滤)传输到您选择的目标。 在这里inputs代表数据的输入通道,大家可以简单理解为来源。常见的可以从kafka,FileBeat, DB等获取日志数据,这些数据经过fliter过滤后(比如说:日志过滤,json格式解析等)通过outputs传输到指定的位置进行存储(Elasticsearch,Mogodb,Redis等) 简单的实例: cd logstash-6.4.1 bin/logstash -e 'input { stdin { } } output { stdout {} }' 1.3、Kibana kibana是用于Elasticsearch检索数据的开源分析和可视化平台。我们可以使用Kibana搜索、查看或者与存储在Elasticsearch索引中的数据交互。同时也可以轻松地执行高级数据分析并在各种图表、表和映射中可视化数据。基于浏览器的Kibana界面使您能够快速创建和共享动态仪表板,实时显示对Elasticsearch查询的更改。 1.4、处理方案 用户通过java应用程序的Slf4j写入日志,SpringBoot默认使用的是logback。我们通过实现自定义的Appender将日志写入kafka,同时logstash通过input插件操作kafka订阅其对应的主题。当有日志输出后被kafka的客户端logstash所收集,经过相关过滤操作后将日志写入Elasticsearch,此时用户可以通过kibana获取elasticsearch中的日志信息 二、SpringBoot中的配置 在SpringBoot当中,我们可以通过logback-srping.xml来扩展logback的配置。不过我们在此之前应当先添加logback对kafka的依赖,代码如下: compile group: 'com.github.danielwegener', name: 'logback-kafka-appender', version: '0.2.0-RC1' 添加好依赖之后我们需要在类路径下创建logback-spring.xml的配置文件并做如下配置(添加kafka的Appender): <configuration> <!-- springProfile用于指定当前激活的环境,如果spring.profile.active的值是哪个,就会激活对应节点下的配置 --> <springProfile name="default"> <!-- configuration to be enabled when the "staging" profile is active --> <springProperty scope="context" name="module" source="spring.application.name" defaultValue="undefinded"/> <!-- 该节点会读取Environment中配置的值,在这里我们读取application.yml中的值 --> <springProperty scope="context" name="bootstrapServers" source="spring.kafka.bootstrap-servers" defaultValue="localhost:9092"/> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> <encoder> <pattern>%boldYellow(${module}) | %d | %highlight(%-5level)| %cyan(%logger{15}) - %msg %n</pattern> </encoder> </appender> <!-- kafka的appender配置 --> <appender name="kafka" class="com.github.danielwegener.logback.kafka.KafkaAppender"> <encoder> <pattern>${module} | %d | %-5level| %logger{15} - %msg</pattern> </encoder> <topic>logger-channel</topic> <keyingStrategy class="com.github.danielwegener.logback.kafka.keying.NoKeyKeyingStrategy"/> <deliveryStrategy class="com.github.danielwegener.logback.kafka.delivery.AsynchronousDeliveryStrategy"/> <!-- Optional parameter to use a fixed partition --> <!-- <partition>0</partition> --> <!-- Optional parameter to include log timestamps into the kafka message --> <!-- <appendTimestamp>true</appendTimestamp> --> <!-- each <producerConfig> translates to regular kafka-client config (format: key=value) --> <!-- producer configs are documented here: https://kafka.apache.org/documentation.html#newproducerconfigs --> <!-- bootstrap.servers is the only mandatory producerConfig --> <producerConfig>bootstrap.servers=${bootstrapServers}</producerConfig> <!-- 如果kafka不可用则输出到控制台 --> <appender-ref ref="STDOUT"/> </appender> <!-- 指定项目中的logger --> <logger name="org.springframework.test" level="INFO" > <appender-ref ref="kafka" /> </logger> <root level="info"> <appender-ref ref="STDOUT" /> </root> </springProfile> </configuration> 在这里面我们主要注意以下几点: 日志输出的格式是为模块名 | 时间 | 日志级别 | 类的全名 | 日志内容 SpringProfile节点用于指定当前激活的环境,如果spring.profile.active的值是哪个,就会激活对应节点下的配置 springProperty可以读取Environment中的值 三、ELK搭建过程 3.1、检查环境 ElasticSearch需要jdk8,官方建议我们使用JDK的版本为1.8.0_131,原文如下: Elasticsearch requires at least Java 8. Specifically as of this writing, it is recommended that you use the Oracle JDK version 1.8.0_131 检查完毕后,我们可以分别在官网下载对应的组件 ElasticSearch Kibana Logstash kafka zookeeper 3.2、启动zookeeper 首先进入启动zookeeper的根目录下,将conf目录下的zoo_sample.cfg文件拷贝一份重新命名为zoo.cfg mv zoo_sample.cfg zoo.cfg 配置文件如下: # The number of milliseconds of each tick tickTime=2000 # The number of ticks that the initial # synchronization phase can take initLimit=10 # The number of ticks that can pass between # sending a request and getting an acknowledgement syncLimit=5 # the directory where the snapshot is stored. # do not use /tmp for storage, /tmp here is just # example sakes. dataDir=../zookeeper-data # the port at which the clients will connect clientPort=2181 # the maximum number of client connections. # increase this if you need to handle more clients #maxClientCnxns=60 # # Be sure to read the maintenance section of the # administrator guide before turning on autopurge. # # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance # # The number of snapshots to retain in dataDir #autopurge.snapRetainCount=3 # Purge task interval in hours # Set to "0" to disable auto purge feature #autopurge.purgeInterval=1 紧接着我们进入bin目录启动zookeeper: ./zkServer.sh start 3.3、启动kafka 在kafka根目录下运行如下命令启动kafka: ./bin/kafka-server-start.sh config/server.properties 启动完毕后我们需要创建一个logger-channel主题: ./bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic logger-channel 3.4、配置并启动logstash 进入logstash跟目录下的config目录,我们将logstash-sample.conf的配置文件拷贝到根目录下重新命名为core.conf,然后我们打开配置文件进行编辑: # Sample Logstash configuration for creating a simple # Beats -> Logstash -> Elasticsearch pipeline. input { kafka { id => "my_plugin_id" bootstrap_servers => "localhost:9092" topics => ["logger-channel"] auto_offset_reset => "latest" } } filter { grok { patterns_dir => ["./patterns"] match => { "message" => "%{WORD:module} \| %{LOGBACKTIME:timestamp} \| %{LOGLEVEL:level} \| %{JAVACLASS:class} - %{JAVALOGMESSAGE:logmessage}" } } } output { stdout { codec => rubydebug } elasticsearch { hosts =>["localhost:9200"] } } 我们分别配置logstash的input,filter和output(懂ruby的童鞋们肯定对语法结构不陌生吧): 在input当中我们指定日志来源为kafka,具体含义可以参考官网:kafka-input-plugin 在filter中我们配置grok插件,该插件可以利用正则分析日志内容,其中patterns_dir属性用于指定自定义的分析规则,我们可以在该文件下建立文件配置验证的正则规则。举例子说明:55.3.244.1 GET /index.html 15824 0.043的 日志内容经过如下配置解析: grok { match => { "message" => "%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}" } } 解析过后会变成: client: 55.3.244.1 method: GET request: /index.html bytes: 15824 duration: 0.043 这些属性都会在elasticsearch中存为对应的属性字段。更详细的介绍请参考官网:grok ,当然该插件已经帮我们定义好了好多种核心规则,我们可以在这里查看所有的规则。 在output当中我们将过滤过后的日志内容打印到控制台并传输到elasticsearch中,我们可以参考官网上关于该插件的属性说明:地址 另外我们在patterns文件夹中创建好自定义的规则文件logback,内容如下: # yyyy-MM-dd HH:mm:ss,SSS ZZZ eg: 2014-01-09 17:32:25,527 LOGBACKTIME 20%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{HOUR}:?%{MINUTE}(?::?%{SECOND}) 编辑好配置后我们运行如下命令启动logstash: bin/logstash -f first-pipeline.conf --config.reload.automatic 该命令会实时更新配置文件而不需启动 3.5、启动ElasticSearch 启动ElasticSearch很简单,我们可以运行如下命令: ./bin/elasticsearch 我们可以发送get请求来判断启动成功: GET http://localhost:9200 我们可以得到类似于如下的结果: { "name" : "Cp8oag6", "cluster_name" : "elasticsearch", "cluster_uuid" : "AT69_T_DTp-1qgIJlatQqA", "version" : { "number" : "6.4.0", "build_flavor" : "default", "build_type" : "zip", "build_hash" : "f27399d", "build_date" : "2016-03-30T09:51:41.449Z", "build_snapshot" : false, "lucene_version" : "7.4.0", "minimum_wire_compatibility_version" : "1.2.3", "minimum_index_compatibility_version" : "1.2.3" }, "tagline" : "You Know, for Search" } 3.5.1 配置IK分词器(可选) 我们可以在github上下载elasticsearch的IK分词器,地址如下:ik分词器,然后把它解压至your-es-root/plugins/ik的目录下,我们可以在{conf}/analysis-ik/config/IKAnalyzer.cfg.xmlor {plugins}/elasticsearch-analysis-ik-*/config/IKAnalyzer.cfg.xml 里配置自定义分词器: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>IK Analyzer 扩展配置</comment> <!--用户可以在这里配置自己的扩展字典 --> <entry key="ext_dict">custom/mydict.dic;custom/single_word_low_freq.dic</entry> <!--用户可以在这里配置自己的扩展停止词字典--> <entry key="ext_stopwords">custom/ext_stopword.dic</entry> <!--用户可以在这里配置远程扩展字典 --> <entry key="remote_ext_dict">location</entry> <!--用户可以在这里配置远程扩展停止词字典--> <entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry> </properties> 首先我们添加索引: curl -XPUT http://localhost:9200/my_index 我们可以把通过put请求来添加索引映射: PUT my_index { "mappings": { "doc": { "properties": { "title": { "type": "text" }, "name": { "type": "text" }, "age": { "type": "integer" }, "created": { "type": "date", "format": "strict_date_optional_time||epoch_millis" } "content": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" } } } } } 其中doc是映射名 my_index是索引名称 3.5.2 logstash与ElasticSearch logstash默认情况下会在ES中建立logstash-*的索引,*代表了yyyy-MM-dd的时间格式,根据上述logstash配置filter的示例,其会在ES中建立module ,logmessage,class,level等索引。(具体我们可以根据grok插件进行配置) 3.6 启动Kibana 在kibana的bin目录下运行./kibana即可启动。启动之后我们可以通过浏览器访问http://localhost:5601 来访问kibanaUI。我们可以看到如下界面:
雪崩效应即在多个服务节点当中,如果有一个服务不可用而这个不可用的服务导致整个应用资源都耗在这里,进而影响整个系统的崩溃。在分布式环境中,不可避免地会出现雪崩效应。Hystrix是一个netflix实现了 circuit breaker pattern模式的库,它通过处理并发量,降低延迟故障和优雅的容错处理来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停止跨服务的级联故障和优雅的降级来提高系统的整体弹性。 5.2.1 为什么需要Hystrix hystrix的作用: 通过第三方客户端库访问依赖项(通常是通过网络),以保护和控制延迟和故障。 防止雪崩效应 当遇到的问题 快速失败并优雅的恢复 有效的进行监控与警告 5.2.2 防止雪崩效应 Hystrix采用了如下方式来防止雪崩效应: 在HystrixCommand或HystrixObservableCommand对象中封装对外部系统(或“依赖项”)的所有调用,该对象通常在单独的线程中执行(这是命令模式的一个示例)。 为每个依赖项维护一个小的线程池(或信号量); 如果它被填满了,发送给该依赖项的请求将立即被拒绝,而不是排队等待。 Hystrix会根据根据标准衡量是否成功、失败(由客户端抛出的异常)、超时和拒绝再一次的请求 触发断路器,在一段时间内停止对特定服务的所有请求,如果服务的错误率超过阈值,则手动或自动停止。 当请求失败、被拒绝、超时或短路时,执行回退逻辑。 全方位的进行监控 5.2.3 工作流程 一、 构建HystrixCommand或者HystrixObservableCommand对象 第一步是构造一个HystrixCommand或HystrixObservableCommand对象,表示您正在向依赖项发出的请求。HystrixCommand用于对一个依赖项产生独立的响应,而HystrixObservableCommand拿到的是基于rxjava实现的响应式结果observerable 二、 执行Command命令 通过使用Hystrix命令对象的以下四种方法之一(前两种方法仅适用于简单的HystrixCommand对象,不适用于HystrixObservableCommand),有四种方法可以执行命令: execute:阻塞的,可以从依赖项返回单个结果(或者在出现错误时抛出异常) queue: 从依赖项返回Future对象 observer:订阅依赖项返回的结果,并返回复制该源头Observable做为返回值对象 toObservable: 返回Observable对象,只有订阅它的时候才会执行hystrix command命令 三、 查看缓存中是否有响应结果 如果开启了缓存,并且缓存中有针对于本次请求结果的缓存,那么将会读取缓存中的值 四、 断路器是否打开 当执行命令时,Hystrix会检查断路器是否打开。如果断路器的状态是open或者tripped,那么Hystrix不会执行相关命令,它会路由至第8步。如果断路器没有打开则会执行第5步 五、 信号量/队列/线程池是否填满 如果与命令关联的线程池和队列或信号量已经满了,那么Hystrix将不会执行命令,它会立即路由到(8)进行回退的操作。 六、 HystrixObservableCommand.construct() or HystrixCommand.run() Hystrix通过调用如下方法进行对依赖项的请求: HystrixCommand.run()— 返回单独的结果响应 HystrixObservableCommand.construct()— 返回一个Observable对象 如果run()或construct()方法超过命令的超时值,线程将抛出TimeoutException(如果命令本身不在自己的线程中运行,则单独的计时器线程将抛出该异常)。在这种情况下,Hystrix将响应传递到步骤8来获取回退,如果最终返回值run()或construct()方法没有取消/中断,它将丢弃。 七、 计算断路的健康值 Hystrix向断路器报告成功、故障、拒绝和超时等信息,断路器维护一组动态计数器,用于计算统计数据。它使用这些统计数据来确定电路什么时候应该“跳闸”,如果已经跳闸,它会短路任何后续请求,直到恢复周期结束,在此期间,它会在第一次检查某些健康检查后再次关闭电路 八、 调用fallback方法 当Command命令执行失败时,Hystrix会尝试进行回滚的操作,常见的失败可原因如下: construct() or run() 抛出异常时 当断路器被打开时 线程、队列或者信号量充满时 commnad执行超时 九、 成功的进行响应 5.2.4 跳闸原理 电路开闭的方式如下: 一、 当断路器满足某个阀值 HystrixCommandProperties.circuitBreakerRequestVolumeThreshold() 二、 当错误百分比超过某个阀值 HystrixCommandProperties.circuitBreakerErrorThresholdPercentage() 三、 而后断路器有关—>开 四、当断路器打开时,所有的请求都会进行短路操作,最常见的方式就是执行fallback方法 在一定时间以后,我们可以通过HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()来设置此值。下一个请求会被放行(此时断路器状态是half-open),如果这次请求失败了,仍会打开断路器。如果成功了,则断路器进行关闭 5.2.5 隔离策略 5.2.5.1 线程组隔离 客户机在单独的线程上执行。这将它们与调用线程(Tomcat线程池)隔离开来,以便调用者可以“避开”耗时太长的依赖项调用。官方推荐使用这种方式进行服务与依赖之间的隔离,官方解释的好处如下: The application is fully protected from runaway client libraries. The pool for a given dependency library can fill up without impacting the rest of the application. The application can accept new client libraries with far lower risk. If an issue occurs, it is isolated to the library and doesn’t affect everything else. When a failed client becomes healthy again, the thread pool will clear up and the application immediately resumes healthy performance, as opposed to a long recovery when the entire Tomcat container is overwhelmed. If a client library is misconfigured, the health of a thread pool will quickly demonstrate this (via increased errors, latency, timeouts, rejections, etc.) and you can handle it (typically in real-time via dynamic properties) without affecting application functionality. If a client service changes performance characteristics (which happens often enough to be an issue) which in turn cause a need to tune properties (increasing/decreasing timeouts, changing retries, etc.) this again becomes visible through thread pool metrics (errors, latency, timeouts, rejections) and can be handled without impacting other clients, requests, or users. Beyond the isolation benefits, having dedicated thread pools provides built-in concurrency which can be leveraged to build asynchronous facades on top of synchronous client libraries (similar to how the Netflix API built a reactive, fully-asynchronous Java API on top of Hystrix commands). 我认为其最主要的优点就是各个服务模块的“独立性”,不依赖与容器中(tomcat)的线程组,出了问题可以把风险降到最低同时也可以快速恢复。当然其主要缺点是增加了计算开销。每个命令执行都涉及到在单独的线程上运行命令所涉及的排队、调度和上下文切换。不过官方决定接受这种开销的成本以换取它所提供的好处,他们认为这种成本和性能影响不大。线程组隔离依赖的示例图如下: 5.2.5.2 信号量隔离 信号量隔离,通常通过设置一个值来限制针对一项依赖的并发请求数目,这种方式可以允许不适用线程池的方式下降低负载量,如果您信任客户端并且只希望减少负载,那么可以使用这种方法。一旦达到限制阀值,信号量拒绝其他线程的请求,但是填充信号量的线程不能离开。 如果使用ThreadLocal绑定变量或传递时,一定要使用信号量的隔离方式 5.2.6 单独使用Hystrix示例 package com.iteng.springcloud.hystrix; import com.netflix.hystrix.*; public class FirstHystrixExample extends HystrixCommand<String> { public FirstHystrixExample() { super( Setter. withGroupKey(HystrixCommandGroupKey.Factory.asKey(FirstHystrixExample.class.getSimpleName())). andCommandKey(HystrixCommandKey.Factory.asKey("test")). andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(30)). andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)). andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(1000)). andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationSemaphoreMaxConcurrentRequests(1)). andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withFallbackIsolationSemaphoreMaxConcurrentRequests(200)). andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerEnabled(true)) ); } @Override protected String run() throws Exception { // Thread.sleep(30000); return "hello"; //return "hello"; } @Override protected String getFallback() { return "error msg..."; } } 关于Hystrix的相关配置请参考官网:地址 ,这里需要指定hystrix的组和commandkey,我们可以通过以下方式来做: Setter. withGroupKey(HystrixCommandGroupKey.Factory.asKey(FirstHystrixExample.class.getSimpleName())). andCommandKey(HystrixCommandKey.Factory.asKey("test")) 在这里我们可以指定类名为组,方法名为key。利用动态代理来实现HystrixCommand 调用示例: static void execute() { FirstHystrixExample firstHystrixExample = new FirstHystrixExample(); System.out.println(firstHystrixExample.execute()); } static void future() { FirstHystrixExample firstHystrixExample = new FirstHystrixExample(); Future<String> future = firstHystrixExample.queue(); try { String s = future.get(2, TimeUnit.SECONDS); System.out.println(s); } catch (Exception e) { e.printStackTrace(); } } static void observer() { FirstHystrixExample firstHystrixExample = new FirstHystrixExample(); firstHystrixExample.observe().subscribeOn(Schedulers.newThread()).subscribe(s -> System.out.println(Thread.currentThread().getName() + ":" + s)); } 5.2.7 SpringCloud集成Hystrix 在SpringCloud中添加Hystrix的支持,我们需要在maven或者gradle里添加groupId为org.springframework.cloud,AffactId为spring-cloud-starter-netflix-hystrix的依赖。代码示例如下: @SpringBootApplication @EnableCircuitBreaker public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } } @Component public class StoreIntegration { @HystrixCommand(fallbackMethod = "defaultStores") public Object getStores(Map<String, Object> parameters) { //do stuff that might fail } public Object defaultStores(Map<String, Object> parameters) { return /* something useful */; } } 其中SpringCloud在这里会把@HystrixCommand标注的方法包装成代理对象连接至Hystrix circuit breaker,Hystrix断路保护器会计算什么时候开闭。 5.2.7.1 源码分析 我们可以来看一下它的源码: 在spring-cloud-netflix-core-2.0.1.RELEASE.jar中的META-INF/spring.factories里有如下配置: org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration,\ org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\ org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration 那么在HystrixCircuitBreakerConfiguration当中可以看到如下实现: /* * Copyright 2013-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.cloud.netflix.hystrix; import org.apache.catalina.core.ApplicationContext; import org.springframework.beans.factory.DisposableBean; import org.springframework.cloud.client.actuator.HasFeatures; import org.springframework.cloud.client.actuator.NamedFeature; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.netflix.hystrix.Hystrix; import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect; /** * @author Spencer Gibb * @author Christian Dupuis * @author Venil Noronha */ @Configuration public class HystrixCircuitBreakerConfiguration { @Bean public HystrixCommandAspect hystrixCommandAspect() { return new HystrixCommandAspect(); } @Bean public HystrixShutdownHook hystrixShutdownHook() { return new HystrixShutdownHook(); } @Bean public HasFeatures hystrixFeature() { return HasFeatures.namedFeatures(new NamedFeature("Hystrix", HystrixCommandAspect.class)); } // ...省略部分代码 /** * {@link DisposableBean} that makes sure that Hystrix internal state is cleared when * {@link ApplicationContext} shuts down. */ private class HystrixShutdownHook implements DisposableBean { @Override public void destroy() throws Exception { // Just call Hystrix to reset thread pool etc. Hystrix.reset(); } } } 在这里我们可以看到创建了一个HystrixCommandAspect的切面。在切面里有几行关键的代码: //定义寻找@HystrixCommand的切点 @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)") public void hystrixCommandAnnotationPointcut() { } @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)") public void hystrixCollapserAnnotationPointcut() { } //环绕通知 @Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()") public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable { //根据切点找到对应的执行方法 Method method = getMethodFromTarget(joinPoint); Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint); //如果方法上@HystrixCommand与@HystrixCollapser则扔出异常 if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) { throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " + "annotations at the same time"); } /* 根据Joinpoint切点拿到MetaHolder,该类封装了与Hystrix相关的要素,如配置等 根据metaHolder拿到HystrixInvokable对象,该对象定义了Hystrix的执行规范 */ MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method)); MetaHolder metaHolder = metaHolderFactory.create(joinPoint); HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder); ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ? metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType(); Object result; try { if (!metaHolder.isObservable()) { //执行hystrix的Command result = CommandExecutor.execute(invokable, executionType, metaHolder); } else { //通过Observable的方式执行 result = executeObservable(invokable, executionType, metaHolder); } } catch (HystrixBadRequestException e) { throw e.getCause() != null ? e.getCause() : e; } catch (HystrixRuntimeException e) { throw hystrixRuntimeExceptionToThrowable(metaHolder, e); } return result; } 5.2.7.2 Hystrix传播ThreadLocal 如果我们想传播ThreadLocal至@HystrixCommand中,只有设置默认策略为semaphore才可以,因为在默认情况下,Hystrix隔离策略是线程级别的,因此调用run方法时已经是Hystrix单独维护的线程了,示例: package com.iteng.springcloud.hystrix.service; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class HystrixService { private ThreadLocal<String> threadLocal = new ThreadLocal<>(); @Autowired private HystrixService hystrixService; @HystrixCommand(fallbackMethod = "fallback") @GetMapping("/sleep/{value}") public String index(@PathVariable Integer value) { String r = threadLocal.get(); return Thread.currentThread().getName() + ":" + r; } @GetMapping public String test() { threadLocal.set("test"); return hystrixService.index(1); } public String fallback(Integer value) { return "error msg..."; } } 另外我们可以通过扩展HystrixConcurrencyStrategy的方式来处理ThreadLocal的问题,在这里官方明确的告诉我们这个办法来解决: /** * Provides an opportunity to wrap/decorate a {@code Callable<T>} before execution. * <p> * This can be used to inject additional behavior such as copying of thread state (such as {@link ThreadLocal}). * <p> * <b>Default Implementation</b> * <p> * Pass-thru that does no wrapping. * * @param callable * {@code Callable<T>} to be executed via a {@link ThreadPoolExecutor} * @return {@code Callable<T>} either as a pass-thru or wrapping the one given */ public <T> Callable<T> wrapCallable(Callable<T> callable) { return callable; } 使用示例: package com.iteng.springcloud.hystrix.concurrencystrategy; import com.iteng.springcloud.hystrix.FirstHystrixExample; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import java.util.concurrent.Callable; public class TestConcurrencyStrategy extends HystrixConcurrencyStrategy { @Override public <T> Callable<T> wrapCallable(Callable<T> callable) { return new Callable<T>() { @Override public T call() throws Exception { FirstHystrixExample.test.set("333"); return callable.call(); } }; } } 由于默认情况下Hystrix加载HystrixProperties默认是用ServiceLoader,具体可见(HystrixPlugins)因此需创建META-INF/services/com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy文件里面做如下配置: com.iteng.springcloud.hystrix.concurrencystrategy.TestConcurrencyStrategy 那么改造5.2.6的示例: package com.iteng.springcloud.hystrix; import com.netflix.hystrix.*; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import java.util.concurrent.Callable; public class FirstHystrixExample extends HystrixCommand<String> { //....省略部分代码... public static ThreadLocal<String> test =new ThreadLocal<>(); // ..... @Override protected String run() throws Exception { System.out.println(Thread.currentThread().getName()+":"+test.get()); return "hello"; } } 那么在SpringCloud中我们可以将TestConcurrencyStrategy配置为一个bean就可以了 5.2.7.3 健康信息 hystrix提供了对健康信息的检查,我们可以通过/health端点进行有效的监控,例如: { "hystrix": { "openCircuitBreakers": [ "StoreIntegration::getStoresByLocationLink" ], "status": "CIRCUIT_OPEN" }, "status": "UP" }
SpringCloud带给我们的便利是有目共睹的,它能快速的帮助中小型企业构建微服务的程序架构。其背后有SpringBoot这员大将做底层支持,同时得力于Spring在java界的影响力和近几年micro service的流行,因此SpringCloud从2016年渐渐的走进人们的视野。可是便捷这个东西确实是一把双刃剑,我们在享受便捷的同时也要做好难以排错的准备,同时在版本升级的时候一定慎之又慎。 相关源码浅析 对于我们的feign项目,如果加上了spring-cloud-starter-netflix-hystrix,那么feign自动会将所有的方法用hystrix进行包装,这是怎么实现的呢?答案就是代理模式。 HystrixFeign 我们先来看看这个API文档注释: Allows Feign interfaces to return HystrixCommand or rx.Observable or rx.Single objects. Also decorates normal Feign methods with circuit breakers, but calls {@linkHystrixCommand#execute()} directly. 根据API文档解释,这个类允许Feign接口返回HystrixCommand或者rx.Observable或者rx.Single objects,同时将Feign的method通过断路器进行包装 public final class HystrixFeign { public static Builder builder() { return new Builder(); } public static final class Builder extends Feign.Builder { private Contract contract = new Contract.Default(); //设置HystrixCommand的Setter属性工厂 private SetterFactory setterFactory = new SetterFactory.Default(); /** 允许覆盖Hystrix的一些属性比如说threadpool和commandkey等 * Allows you to override hystrix properties such as thread pools and command keys. */ public Builder setterFactory(SetterFactory setterFactory) { this.setterFactory = setterFactory; return this; } //...省略其他代码 @Override public Feign build() { return build(null); } /** Configures components needed for hystrix integration. */ Feign build(final FallbackFactory<?> nullableFallbackFactory) { super.invocationHandlerFactory(new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { //实际创建的是HystrixInvocatonHandler代理对象 return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory); } }); super.contract(new HystrixDelegatingContract(contract)); return super.build(); } 根据上述代码,我们可以看到在build方法里实际创建的是HystrixInvocationHandler对象。 HystrixInvocationHandler /** 实现InvocationHandler接口,通过代理模式实现 */ final class HystrixInvocationHandler implements InvocationHandler { private final Target<?> target; private final Map<Method, MethodHandler> dispatch; private final FallbackFactory<?> fallbackFactory; // Nullable private final Map<Method, Method> fallbackMethodMap; private final Map<Method, Setter> setterMethodMap; HystrixInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch, SetterFactory setterFactory, FallbackFactory<?> fallbackFactory) { this.target = checkNotNull(target, "target"); this.dispatch = checkNotNull(dispatch, "dispatch"); this.fallbackFactory = fallbackFactory; this.fallbackMethodMap = toFallbackMethod(dispatch); this.setterMethodMap = toSetters(setterFactory, target, dispatch.keySet()); } //...省略部分代码 /** * 通过代码可知:每个method方法都对应独立的setter配置 * Process all methods in the target so that appropriate setters are created. */ static Map<Method, Setter> toSetters(SetterFactory setterFactory, Target<?> target, Set<Method> methods) { Map<Method, Setter> result = new LinkedHashMap<Method, Setter>(); for (Method method : methods) { method.setAccessible(true); result.put(method, setterFactory.create(target, method)); } return result; } @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { //解决 Object方法中 ReflectiveFeign.FeignInvocationHandler中的方法冲突问题 // early exit if the invoked method is from java.lang.Object // code is the same as ReflectiveFeign.FeignInvocationHandler if ("equals".equals(method.getName())) { try { Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) { return false; } } else if ("hashCode".equals(method.getName())) { return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); } //创建HystrixCommand对象 HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) { @Override protected Object run() throws Exception { try { //代理执行,用HystrixCommand包装Feign的请求 return HystrixInvocationHandler.this.dispatch.get(method).invoke(args); } catch (Exception e) { throw e; } catch (Throwable t) { throw (Error) t; } } //重写降级方法 @Override protected Object getFallback() { if (fallbackFactory == null) { return super.getFallback(); } try { Object fallback = fallbackFactory.create(getExecutionException()); Object result = fallbackMethodMap.get(method).invoke(fallback, args); if (isReturnsHystrixCommand(method)) { return ((HystrixCommand) result).execute(); } else if (isReturnsObservable(method)) { // Create a cold Observable return ((Observable) result).toBlocking().first(); } else if (isReturnsSingle(method)) { // Create a cold Observable as a Single return ((Single) result).toObservable().toBlocking().first(); } else if (isReturnsCompletable(method)) { ((Completable) result).await(); return null; } else { return result; } } catch (IllegalAccessException e) { // shouldn't happen as method is public due to being an interface throw new AssertionError(e); } catch (InvocationTargetException e) { // Exceptions on fallback are tossed by Hystrix throw new AssertionError(e.getCause()); } } }; if (isReturnsHystrixCommand(method)) { return hystrixCommand; } else if (isReturnsObservable(method)) { // Create a cold Observable return hystrixCommand.toObservable(); } else if (isReturnsSingle(method)) { // Create a cold Observable as a Single return hystrixCommand.toObservable().toSingle(); } else if (isReturnsCompletable(method)) { return hystrixCommand.toObservable().toCompletable(); } return hystrixCommand.execute(); } //省略部分代码... } 通过上述代码可以发现这里使用代理模式将Hystrix包装Feign。 SetterFactory SetterFactory是用于生成HystrixSetter配置项的工厂: public interface SetterFactory { /** * Returns a hystrix setter appropriate for the given target and method */ HystrixCommand.Setter create(Target<?> target, Method method); /** * Default behavior is to derive the group key from {@link Target#name()} and the command key from * {@link Feign#configKey(Class, Method)}. */ final class Default implements SetterFactory { @Override public HystrixCommand.Setter create(Target<?> target, Method method) { String groupKey = target.name(); String commandKey = Feign.configKey(target.type(), method); return HystrixCommand.Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); } } } 根据提示它对应的commandKey 通过Feign#configKey()生成,而groupKey是接口名下面我们就看看Feign怎么样生成key的: public abstract class Feign { /** 匹配基本规则:接口名#方法名(参数类型) Route53->route53.Route53 Route53#list() -> route53.Route53#list() Route53#listAt(Marker) -> route53.Route53#listAt(Marker) Route53#listByNameAndType(String, String) -> route53.Route53#listAt(String, String) */ public static String configKey(Class targetType, Method method) { StringBuilder builder = new StringBuilder(); builder.append(targetType.getSimpleName()); builder.append('#').append(method.getName()).append('('); for (Type param : method.getGenericParameterTypes()) { param = Types.resolve(targetType, targetType, param); builder.append(Types.getRawType(param).getSimpleName()).append(','); } if (method.getParameterTypes().length > 0) { builder.deleteCharAt(builder.length() - 1); } return builder.append(')').toString(); } } 到此处我们可以断定Feign与Hystrix集成时配置就可以这么写: hystrix: command: Timeout#timeout(): execution: isolation: thread: timeoutInMilliseconds: 20000 对应的接口: package com.zhshop.web; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; @FeignClient("purchase-service") public interface Timeout { @RequestMapping("/feignTest") public String timeout(); } Hystrix的配置类 HystrixCommandProperties基本上包含了所有Hystrix的默认配置,代码片段如下: public abstract class HystrixCommandProperties { //传入前缀hystrix protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder) { this(key, builder, "hystrix"); } // known that we're using deprecated HystrixPropertiesChainedServoProperty until ChainedDynamicProperty exists in Archaius protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix) { this.key = key; this.circuitBreakerEnabled = getProperty(propertyPrefix, key, "circuitBreaker.enabled", builder.getCircuitBreakerEnabled(), default_circuitBreakerEnabled); this.circuitBreakerRequestVolumeThreshold = getProperty(propertyPrefix, key, "circuitBreaker.requestVolumeThreshold", builder.getCircuitBreakerRequestVolumeThreshold(), default_circuitBreakerRequestVolumeThreshold); this.circuitBreakerSleepWindowInMilliseconds = getProperty(propertyPrefix, key, "circuitBreaker.sleepWindowInMilliseconds", builder.getCircuitBreakerSleepWindowInMilliseconds(), default_circuitBreakerSleepWindowInMilliseconds); this.circuitBreakerErrorThresholdPercentage = getProperty(propertyPrefix, key, "circuitBreaker.errorThresholdPercentage", builder.getCircuitBreakerErrorThresholdPercentage(), default_circuitBreakerErrorThresholdPercentage); this.circuitBreakerForceOpen = getProperty(propertyPrefix, key, "circuitBreaker.forceOpen", builder.getCircuitBreakerForceOpen(), default_circuitBreakerForceOpen); this.circuitBreakerForceClosed = getProperty(propertyPrefix, key, "circuitBreaker.forceClosed", builder.getCircuitBreakerForceClosed(), default_circuitBreakerForceClosed); this.executionIsolationStrategy = getProperty(propertyPrefix, key, "execution.isolation.strategy", builder.getExecutionIsolationStrategy(), default_executionIsolationStrategy); //this property name is now misleading. //TODO figure out a good way to deprecate this property name this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds); this.executionTimeoutEnabled = getProperty(propertyPrefix, key, "execution.timeout.enabled", builder.getExecutionTimeoutEnabled(), default_executionTimeoutEnabled); this.executionIsolationThreadInterruptOnTimeout = getProperty(propertyPrefix, key, "execution.isolation.thread.interruptOnTimeout", builder.getExecutionIsolationThreadInterruptOnTimeout(), default_executionIsolationThreadInterruptOnTimeout); this.executionIsolationThreadInterruptOnFutureCancel = getProperty(propertyPrefix, key, "execution.isolation.thread.interruptOnFutureCancel", builder.getExecutionIsolationThreadInterruptOnFutureCancel(), default_executionIsolationThreadInterruptOnFutureCancel); this.executionIsolationSemaphoreMaxConcurrentRequests = getProperty(propertyPrefix, key, "execution.isolation.semaphore.maxConcurrentRequests", builder.getExecutionIsolationSemaphoreMaxConcurrentRequests(), default_executionIsolationSemaphoreMaxConcurrentRequests); this.fallbackIsolationSemaphoreMaxConcurrentRequests = getProperty(propertyPrefix, key, "fallback.isolation.semaphore.maxConcurrentRequests", builder.getFallbackIsolationSemaphoreMaxConcurrentRequests(), default_fallbackIsolationSemaphoreMaxConcurrentRequests); this.fallbackEnabled = getProperty(propertyPrefix, key, "fallback.enabled", builder.getFallbackEnabled(), default_fallbackEnabled); this.metricsRollingStatisticalWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingStats.timeInMilliseconds", builder.getMetricsRollingStatisticalWindowInMilliseconds(), default_metricsRollingStatisticalWindow); this.metricsRollingStatisticalWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingStats.numBuckets", builder.getMetricsRollingStatisticalWindowBuckets(), default_metricsRollingStatisticalWindowBuckets); this.metricsRollingPercentileEnabled = getProperty(propertyPrefix, key, "metrics.rollingPercentile.enabled", builder.getMetricsRollingPercentileEnabled(), default_metricsRollingPercentileEnabled); this.metricsRollingPercentileWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingPercentile.timeInMilliseconds", builder.getMetricsRollingPercentileWindowInMilliseconds(), default_metricsRollingPercentileWindow); this.metricsRollingPercentileWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingPercentile.numBuckets", builder.getMetricsRollingPercentileWindowBuckets(), default_metricsRollingPercentileWindowBuckets); this.metricsRollingPercentileBucketSize = getProperty(propertyPrefix, key, "metrics.rollingPercentile.bucketSize", builder.getMetricsRollingPercentileBucketSize(), default_metricsRollingPercentileBucketSize); this.metricsHealthSnapshotIntervalInMilliseconds = getProperty(propertyPrefix, key, "metrics.healthSnapshot.intervalInMilliseconds", builder.getMetricsHealthSnapshotIntervalInMilliseconds(), default_metricsHealthSnapshotIntervalInMilliseconds); this.requestCacheEnabled = getProperty(propertyPrefix, key, "requestCache.enabled", builder.getRequestCacheEnabled(), default_requestCacheEnabled); this.requestLogEnabled = getProperty(propertyPrefix, key, "requestLog.enabled", builder.getRequestLogEnabled(), default_requestLogEnabled); // threadpool doesn't have a global override, only instance level makes sense this.executionIsolationThreadPoolKeyOverride = forString().add(propertyPrefix + ".command." + key.name() + ".threadPoolKeyOverride", null).build(); } //处理布尔类型的值 private static HystrixProperty<Boolean> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Boolean builderOverrideValue, Boolean defaultValue) { return forBoolean() .add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue) .add(propertyPrefix + ".command.default." + instanceProperty, defaultValue) .build(); } //处理Integer类型的值 private static HystrixProperty<Integer> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Integer builderOverrideValue, Integer defaultValue) { return forInteger() .add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue) .add(propertyPrefix + ".command.default." + instanceProperty, defaultValue) .build(); } //省略部分代码..... } 在这里我们可以看到默认情况下会传入前缀:hystrix,然后拼接command+default|key的方式,这里的key通常情况下传的是 Feign对应Feign#configKey()的方法 zuul通常情况下是serviceId即服务名称 那么最终Hystrix的默认配置为: hystrix: command: default: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 1000 FeignClientsProperties 根据官网描述,我们可以通过配置的方式来配置Feign的客户端了: feign: client: config: feignName: connectTimeout: 5000 readTimeout: 5000 loggerLevel: full errorDecoder: com.example.SimpleErrorDecoder retryer: com.example.SimpleRetryer requestInterceptors: - com.example.FooRequestInterceptor - com.example.BarRequestInterceptor decode404: false 那么与之对应的是在FeignClientProperties这个类里 package org.springframework.cloud.openfeign; import feign.Contract; import feign.Logger; import feign.RequestInterceptor; import feign.Retryer; import feign.codec.Decoder; import feign.codec.Encoder; import feign.codec.ErrorDecoder; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; /** * @author Eko Kurniawan Khannedy */ @ConfigurationProperties("feign.client") public class FeignClientProperties { private boolean defaultToProperties = true; //默认名称就是default private String defaultConfig = "default"; // feign.client.config.default.xxx的配置映射此处 这里的String通常是客户端名(@FeignClient(名称)) private Map<String, FeignClientConfiguration> config = new HashMap<>(); public boolean isDefaultToProperties() { return defaultToProperties; } public void setDefaultToProperties(boolean defaultToProperties) { this.defaultToProperties = defaultToProperties; } public String getDefaultConfig() { return defaultConfig; } public void setDefaultConfig(String defaultConfig) { this.defaultConfig = defaultConfig; } public Map<String, FeignClientConfiguration> getConfig() { return config; } public void setConfig(Map<String, FeignClientConfiguration> config) { this.config = config; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FeignClientProperties that = (FeignClientProperties) o; return defaultToProperties == that.defaultToProperties && Objects.equals(defaultConfig, that.defaultConfig) && Objects.equals(config, that.config); } @Override public int hashCode() { return Objects.hash(defaultToProperties, defaultConfig, config); } //最终的配置映射到该类上 public static class FeignClientConfiguration { private Logger.Level loggerLevel; private Integer connectTimeout; private Integer readTimeout; private Class<Retryer> retryer; private Class<ErrorDecoder> errorDecoder; private List<Class<RequestInterceptor>> requestInterceptors; private Boolean decode404; private Class<Decoder> decoder; private Class<Encoder> encoder; private Class<Contract> contract; public Logger.Level getLoggerLevel() { return loggerLevel; } public void setLoggerLevel(Logger.Level loggerLevel) { this.loggerLevel = loggerLevel; } public Integer getConnectTimeout() { return connectTimeout; } public void setConnectTimeout(Integer connectTimeout) { this.connectTimeout = connectTimeout; } public Integer getReadTimeout() { return readTimeout; } public void setReadTimeout(Integer readTimeout) { this.readTimeout = readTimeout; } public Class<Retryer> getRetryer() { return retryer; } public void setRetryer(Class<Retryer> retryer) { this.retryer = retryer; } public Class<ErrorDecoder> getErrorDecoder() { return errorDecoder; } public void setErrorDecoder(Class<ErrorDecoder> errorDecoder) { this.errorDecoder = errorDecoder; } public List<Class<RequestInterceptor>> getRequestInterceptors() { return requestInterceptors; } public void setRequestInterceptors(List<Class<RequestInterceptor>> requestInterceptors) { this.requestInterceptors = requestInterceptors; } public Boolean getDecode404() { return decode404; } public void setDecode404(Boolean decode404) { this.decode404 = decode404; } public Class<Decoder> getDecoder() { return decoder; } public void setDecoder(Class<Decoder> decoder) { this.decoder = decoder; } public Class<Encoder> getEncoder() { return encoder; } public void setEncoder(Class<Encoder> encoder) { this.encoder = encoder; } public Class<Contract> getContract() { return contract; } public void setContract(Class<Contract> contract) { this.contract = contract; } //省略部分代码... } FeignClientFactoryBean FeignClient最终是被FeignClientFactoryBean创建,我们可以看一下它的getObject()方法: @Override public Object getObject() throws Exception { FeignContext context = applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { String url; if (!this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } url += cleanPath(); return loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not lod balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient)client).getDelegate(); } builder.client(client); } Targeter targeter = get(context, Targeter.class); return targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); } 在这里Targeter接口是获取Feign的目标地址对象的,同时这里还会创建具备负载均衡能力的Feign ,而Feign要与Hystrix集成,HystrixTargeter起到了关键作用: /* * Copyright 2013-2016 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.cloud.openfeign; import org.springframework.util.Assert; import feign.Feign; import feign.Target; import feign.hystrix.FallbackFactory; import feign.hystrix.HystrixFeign; import feign.hystrix.SetterFactory; /** * @author Spencer Gibb * @author Erik Kringen */ @SuppressWarnings("unchecked") class HystrixTargeter implements Targeter { @Override public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) { if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { return feign.target(target); } feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign; SetterFactory setterFactory = getOptional(factory.getName(), context, SetterFactory.class); if (setterFactory != null) { builder.setterFactory(setterFactory); } Class<?> fallback = factory.getFallback(); if (fallback != void.class) { return targetWithFallback(factory.getName(), context, target, builder, fallback); } Class<?> fallbackFactory = factory.getFallbackFactory(); if (fallbackFactory != void.class) { return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory); } return feign.target(target); } private <T> T targetWithFallback(String feignClientName, FeignContext context, Target.HardCodedTarget<T> target, HystrixFeign.Builder builder, Class<?> fallback) { //.... } private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context, Target.HardCodedTarget<T> target, HystrixFeign.Builder builder, Class<?> fallbackFactoryClass) { //... } }
关于异步的好处我在这里就不多说了,自从servlet3.1规范发布以来,控制层的异步处理也越来越多的被人提及。而Spring5的webflux诞生也意味着Spring全方位对异步提供了支持。其实早在SpringMVC3.2版本就开始支持异步了,那么这篇文章我们就来探讨一下SpringMVC使用异步的方式。 一、DeferredResult DeferredResult这个类代表延迟结果,我们先看一看spring的API文档给我们的解释: {@code DeferredResult} provides an alternative to using a {@link Callable} for asynchronous request processing. While a {@code Callable} is executed concurrently on behalf of the application, with a {@code DeferredResult} the application can produce the result from a thread of its choice. 根据文档说明DeferredResult可以替代Callable来进行异步的请求处理。只不过这个类可以从其他线程里拿到对应的结果。当使用DeferredResult,我们可以将DefferedResult的类型并将其保存到可以获取到该对象的地方,比如说队列或者集合当中,这样方便其它线程能够取到并设置DefferedResult的值。 1.1、示例 我们先定义一个Controller,代码内容如下: package com.bdqn.lyrk.ssm.study.web.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; /** * 异步任务的控制器 * * @author chen.nie * @date 2018/8/2 **/ @RestController public class AsyncController { private BlockingQueue<DeferredResult<String>> blockingQueue = new ArrayBlockingQueue(1024); /** * 返回值是DeferredResult类型,如果没有结果请求阻塞 * * @return */ @GetMapping("/quotes") public DeferredResult<String> quotes() { //指定超时时间,及出错时返回的值 DeferredResult<String> result = new DeferredResult(3000L,"error"); blockingQueue.add(result); return result; } /** * 另外一个请求(新的线程)设置值 * * @throws InterruptedException */ @GetMapping("take") public void take() throws InterruptedException { DeferredResult<String> result = blockingQueue.take(); result.setResult("route"); } @GetMapping public Callable<String> callable() { return () -> "callable"; } } 控制器可以从不同的线程异步生成返回值,例如响应外部事件(JMS消息)、计划任务等,那么在这里我先使用另外一个请求来模拟这个过程 此时我们启动tomcat,先访问地址http://localhost:8080/quotes ,此时我们会看到发送的请求由于等待响应遭到了阻塞: 当在规定时间内访问http://localhost:8080/take 时,则能成功显示结果: 1.2、DeferredResult处理流程 根据官网描述: DeferredResult processing: Controller returns a DeferredResult and saves it in some in-memory queue or list where it can be accessed. Spring MVC calls request.startAsync(). Meanwhile the DispatcherServlet and all configured Filter’s exit the request processing thread but the response remains open. The application sets the DeferredResult from some thread and Spring MVC dispatches the request back to the Servlet container. The DispatcherServlet is invoked again and processing resumes with the asynchronously produced return value. 将Controller返回的DeferredResult值保存到内存队列或集合当中,紧接着SpringMVC调用HttpServletRequest的startAsync()方法,与此同时DispatcherServlet和所有配置的Filter退出当前的请求线程(不过响应时开放的),当其他线程里设置DeferredResult的值时将重新发送请求,此时DispatcherServlet使用异步生成的返回值继续处理。 在这里一切的一切还需要通过源代码来解释: 当一个请求被DispatcherServlet处理时,会试着获取一个WebAsyncManager对象 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { // ......省略部分代码 // 执行子控制器的方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //如果当前的请求需要异步处理,则终止当前请求,但是响应是开放的 if (asyncManager.isConcurrentHandlingStarted()) { return; } //....省略部分代码 } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } } 对于每一个子控制器的方法返回值,都是HandlerMethodReturnValueHandler接口处理的,其中有一个实现类是DeferredResultMethodReturnValueHandler,关键代码如下: package org.springframework.web.servlet.mvc.method.annotation; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; import org.springframework.core.MethodParameter; import org.springframework.lang.UsesJava8; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.ListenableFutureCallback; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; /** * Handler for return values of type {@link DeferredResult}, {@link ListenableFuture}, * {@link CompletionStage} and any other async type with a {@link #getAdapterMap() * registered adapter}. * * @author Rossen Stoyanchev * @since 3.2 */ @SuppressWarnings("deprecation") public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMethodReturnValueHandler { //存放DeferredResult的适配集合 private final Map<Class<?>, DeferredResultAdapter> adapterMap; public DeferredResultMethodReturnValueHandler() { this.adapterMap = new HashMap<Class<?>, DeferredResultAdapter>(5); this.adapterMap.put(DeferredResult.class, new SimpleDeferredResultAdapter()); this.adapterMap.put(ListenableFuture.class, new ListenableFutureAdapter()); if (ClassUtils.isPresent("java.util.concurrent.CompletionStage", getClass().getClassLoader())) { this.adapterMap.put(CompletionStage.class, new CompletionStageAdapter()); } } /** * Return the map with {@code DeferredResult} adapters. * <p>By default the map contains adapters for {@code DeferredResult}, which * simply downcasts, {@link ListenableFuture}, and {@link CompletionStage}. * @return the map of adapters * @deprecated in 4.3.8, see comments on {@link DeferredResultAdapter} */ @Deprecated public Map<Class<?>, DeferredResultAdapter> getAdapterMap() { return this.adapterMap; } private DeferredResultAdapter getAdapterFor(Class<?> type) { for (Class<?> adapteeType : getAdapterMap().keySet()) { if (adapteeType.isAssignableFrom(type)) { return getAdapterMap().get(adapteeType); } } return null; } @Override public boolean supportsReturnType(MethodParameter returnType) { return (getAdapterFor(returnType.getParameterType()) != null); } @Override public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) { return (returnValue != null && (getAdapterFor(returnValue.getClass()) != null)); } @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { mavContainer.setRequestHandled(true); return; } //根据返回值的类型获取对应的DeferredResult适配器 DeferredResultAdapter adapter = getAdapterFor(returnValue.getClass()); if (adapter == null) { throw new IllegalStateException( "Could not find DeferredResultAdapter for return value type: " + returnValue.getClass()); } DeferredResult<?> result = adapter.adaptToDeferredResult(returnValue); //开启异步请求 WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer); } } 在这里我们关注handleReturnValue的方法,在经过适配包装后获取DeferredResult开启了异步之旅 紧接着我们关注一下WebAsyncManager的startDeferredResultProcessing方法 /** * Start concurrent request processing and initialize the given * {@link DeferredResult} with a {@link DeferredResultHandler} that saves * the result and dispatches the request to resume processing of that * result. The {@code AsyncWebRequest} is also updated with a completion * handler that expires the {@code DeferredResult} and a timeout handler * assuming the {@code DeferredResult} has a default timeout result. * @param deferredResult the DeferredResult instance to initialize * @param processingContext additional context to save that can be accessed * via {@link #getConcurrentResultContext()} * @throws Exception if concurrent processing failed to start * @see #getConcurrentResult() * @see #getConcurrentResultContext() */ public void startDeferredResultProcessing( final DeferredResult<?> deferredResult, Object... processingContext) throws Exception { Assert.notNull(deferredResult, "DeferredResult must not be null"); Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null"); //设置超时时间 Long timeout = deferredResult.getTimeoutValue(); if (timeout != null) { this.asyncWebRequest.setTimeout(timeout); } //获取所有的延迟结果拦截器 List<DeferredResultProcessingInterceptor> interceptors = new ArrayList<DeferredResultProcessingInterceptor>(); interceptors.add(deferredResult.getInterceptor()); interceptors.addAll(this.deferredResultInterceptors.values()); interceptors.add(timeoutDeferredResultInterceptor); final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors); this.asyncWebRequest.addTimeoutHandler(new Runnable() { @Override public void run() { try { interceptorChain.triggerAfterTimeout(asyncWebRequest, deferredResult); } catch (Throwable ex) { setConcurrentResultAndDispatch(ex); } } }); this.asyncWebRequest.addCompletionHandler(new Runnable() { @Override public void run() { interceptorChain.triggerAfterCompletion(asyncWebRequest, deferredResult); } }); interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, deferredResult); //开始异步处理 startAsyncProcessing(processingContext); try { interceptorChain.applyPreProcess(this.asyncWebRequest, deferredResult); deferredResult.setResultHandler(new DeferredResultHandler() { @Override public void handleResult(Object result) { result = interceptorChain.applyPostProcess(asyncWebRequest, deferredResult, result); //设置结果并转发 setConcurrentResultAndDispatch(result); } }); } catch (Throwable ex) { setConcurrentResultAndDispatch(ex); } } private void startAsyncProcessing(Object[] processingContext) { clearConcurrentResult(); this.concurrentResultContext = processingContext; //实际上是执行的是HttpServletRequest对应方法 this.asyncWebRequest.startAsync(); if (logger.isDebugEnabled()) { HttpServletRequest request = this.asyncWebRequest.getNativeRequest(HttpServletRequest.class); String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Concurrent handling starting for " + request.getMethod() + " [" + requestUri + "]"); } } 在这里首先收集所有配置好的DeferredResultProcessingInterceptor ,然后设置asyncRequest的超时处理,完成时的处理等,同时会分阶段执行拦截器中的各个方法。在这里真的佩服Spring框架的扩展机制做的实在是太好了。最后我们关注一下如下代码: deferredResult.setResultHandler(new DeferredResultHandler() { @Override public void handleResult(Object result) { result = interceptorChain.applyPostProcess(asyncWebRequest, deferredResult, result); //设置结果并转发 setConcurrentResultAndDispatch(result); } }); 其最终还是要调用AsyncWebRequest接口中的dispatch方法进行转发,让DispatcherServlet重新处理异步结果: /** * Dispatch the request to the container in order to resume processing after * concurrent execution in an application thread. */ void dispatch(); 其实在这里都是封装自HttpServletRequest的异步操作,我们可以看一下StandardServletAsyncWebRequest的类结构图: 我们可以在其父类ServletRequestAttributes里找到对应的实现: private final HttpServletRequest request; /** * Exposes the native {@link HttpServletRequest} that we're wrapping. */ public final HttpServletRequest getRequest() { return this.request; } 最后我在贴出一段StandardServletAsyncWebRequest 代码,大家就应该知道整个异步是怎么执行的了: //java.servlet.AsnycContext private AsyncContext asyncContext; @Override public void startAsync() { Assert.state(getRequest().isAsyncSupported(), "Async support must be enabled on a servlet and for all filters involved " + "in async request processing. This is done in Java code using the Servlet API " + "or by adding \"<async-supported>true</async-supported>\" to servlet and " + "filter declarations in web.xml."); Assert.state(!isAsyncComplete(), "Async processing has already completed"); if (isAsyncStarted()) { return; } this.asyncContext = getRequest().startAsync(getRequest(), getResponse()); this.asyncContext.addListener(this); if (this.timeout != null) { this.asyncContext.setTimeout(this.timeout); } } @Override public void dispatch() { Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext"); this.asyncContext.dispatch(); } 二、使用Callable作为返回值 使用Callable作为返回值来实现异步与DeferredResult类似,我们先看一看官网描述的具体流程: Callable processing: Controller returns a Callable. Spring MVC calls request.startAsync() and submits the Callable to a TaskExecutor for processing in a separate thread. Meanwhile the DispatcherServlet and all Filter’s exit the Servlet container thread but the response remains open. Eventually the Callable produces a result and Spring MVC dispatches the request back to the Servlet container to complete processing. The DispatcherServlet is invoked again and processing resumes with the asynchronously produced return value from the Callable. 流程上大体与DeferredResult类似,只不过Callable是由TaskExecutor来处理的,而TaskExecutor继承自java.util.concurrent.Executor。我们来看一下它的源代码,它也是在WebAysncManager中处理的: /** * Use the given {@link WebAsyncTask} to configure the task executor as well as * the timeout value of the {@code AsyncWebRequest} before delegating to * {@link #startCallableProcessing(Callable, Object...)}. * @param webAsyncTask a WebAsyncTask containing the target {@code Callable} * @param processingContext additional context to save that can be accessed * via {@link #getConcurrentResultContext()} * @throws Exception if concurrent processing failed to start */ public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object... processingContext) throws Exception { Assert.notNull(webAsyncTask, "WebAsyncTask must not be null"); Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null"); Long timeout = webAsyncTask.getTimeout(); if (timeout != null) { this.asyncWebRequest.setTimeout(timeout); } AsyncTaskExecutor executor = webAsyncTask.getExecutor(); if (executor != null) { this.taskExecutor = executor; } List<CallableProcessingInterceptor> interceptors = new ArrayList<CallableProcessingInterceptor>(); interceptors.add(webAsyncTask.getInterceptor()); interceptors.addAll(this.callableInterceptors.values()); interceptors.add(timeoutCallableInterceptor); final Callable<?> callable = webAsyncTask.getCallable(); final CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors); this.asyncWebRequest.addTimeoutHandler(new Runnable() { @Override public void run() { logger.debug("Processing timeout"); Object result = interceptorChain.triggerAfterTimeout(asyncWebRequest, callable); if (result != CallableProcessingInterceptor.RESULT_NONE) { setConcurrentResultAndDispatch(result); } } }); this.asyncWebRequest.addCompletionHandler(new Runnable() { @Override public void run() { interceptorChain.triggerAfterCompletion(asyncWebRequest, callable); } }); interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, callable); startAsyncProcessing(processingContext); //启动线程池的异步处理 try { this.taskExecutor.submit(new Runnable() { @Override public void run() { Object result = null; try { interceptorChain.applyPreProcess(asyncWebRequest, callable); result = callable.call(); } catch (Throwable ex) { result = ex; } finally { result = interceptorChain.applyPostProcess(asyncWebRequest, callable, result); } //设置当前的结果并转发 setConcurrentResultAndDispatch(result); } }); } catch (RejectedExecutionException ex) { Object result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, ex); setConcurrentResultAndDispatch(result); throw ex; } } 对比DeferredResult,在这里刚开始也是添加拦截器,只不过拦截器的名称是CallableProcessingInterceptor ,同时也需要设置WebAsyncRequest的超时处理,完成时处理的响应操作。这其中最大的区别就是使用TaskExecutor来对Callable进行异步处理
我们知道在Spring及SpringBoot里按条件创建Bean的核心是Condition接口与Conditional注解,其实在SpringBoot里还有一种AutoConfigure也可以来过滤配置,只不过使用这种技术,能够让SpringBoot更快速的启动,那么下面我们就来看一下具体怎么实现的。 autoconfigure Module SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类。 演示示例 在我们创建好的SpringBoot项目里添加一个AutoConfiguration: package com.ys.zhshop.member.config; import com.ys.zhshop.member.service.MemberRegisterService; import org.springframework.context.annotation.Bean; public class MemberAutoConfiguration { @Bean public MemberRegisterService registerService() { return new MemberRegisterService(); } } 在MemberRegisterService里的构造函数输出一段内容看看Spring是否帮我们初始化 紧接着在META-INF/spring.factories里配置对应的引导: org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.ys.zhshop.member.config.MemberAutoConfiguration 随后我们需要在META-INF目录下创建一个spring-autoconfigure-metadata.properties 文件,内容如下: com.ys.zhshop.member.config.MemberAutoConfiguration.ConditionalOnClass=java.lang.Strings 格式:自动配置的类全名.条件=值 在这里我们先指定一个类路径下不存在的Java类,启动后并没有相关信息的输出,那么把其值改成java.land.String,那么我们启动可以发现: 在这里,我们可以在控制台看到构造函数输出的值,这就说明我们的Bean的的确确被创建了 下面我贴出一个spring-cloud-netflix-core下的配置,主要来看看这些条件该怎么写,大家如果想使用可以参考人家的来配置: #Mon Jun 18 19:13:37 UTC 2018 org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration.ConditionalOnClass=com.netflix.hystrix.Hystrix,org.springframework.boot.actuate.health.HealthIndicator org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration= org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration= org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration.Configuration= org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration= org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration.AutoConfigureAfter=org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration.Configuration= org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration.Configuration= org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration.ConditionalOnClass=com.netflix.hystrix.Hystrix,org.springframework.security.core.context.SecurityContext 根据官网说法,使用这种配置方式可以有效的降低SpringBoot的启动时间,因为通过这种过滤方式能减少@Configuration类的数量,从而降低初始化Bean时的耗时,官网原话描述如下: Spring Boot uses an annotation processor to collect the conditions on auto-configurations in a metadata file (META-INF/spring-autoconfigure-metadata.properties). If that file is present, it is used to eagerly filter auto-configurations that do not match, which will improve startup time. 源码追踪 我们再次打开自动化配置的核心类AutoConfigurationImportSelector,看看它的selectImport方法: @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader);//1 AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//2 configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata);//3 fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); } 在代码1处创建用于加载spring-autoconfigure-metadata.properties里的元数据,在这里我们可以看到其指定文件的位置: protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"; 2.在代码2处主要通过SpringFactoriesLoader加载spring.factories里的EnableAutoConfiguration3.根据AutoConfigurationMetaData进行一次过滤: private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); String[] candidates = StringUtils.toStringArray(configurations); boolean[] skip = new boolean[candidates.length]; boolean skipped = false; for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { invokeAwareMethods(filter); boolean[] match = filter.match(candidates, autoConfigurationMetadata);//代码1 for (int i = 0; i < match.length; i++) { if (!match[i]) { skip[i] = true; skipped = true; } } } if (!skipped) { return configurations; } List<String> result = new ArrayList<>(candidates.length); for (int i = 0; i < candidates.length; i++) { if (!skip[i]) { result.add(candidates[i]); } } if (logger.isTraceEnabled()) { int numberFiltered = configurations.size() - result.size(); logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); } return new ArrayList<>(result); } 我们在这里主要关注代码1。OnClassCondition是AutoConfigurationImportFilter接口的实现类,我这里贴一下与主题相关的代码: @Order(Ordered.HIGHEST_PRECEDENCE) class OnClassCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware { @Override public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) { ConditionEvaluationReport report = getConditionEvaluationReport(); ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata); boolean[] match = new boolean[outcomes.length]; for (int i = 0; i < outcomes.length; i++) { match[i] = (outcomes[i] == null || outcomes[i].isMatch()); if (!match[i] && outcomes[i] != null) { logOutcome(autoConfigurationClasses[i], outcomes[i]); if (report != null) { report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]); } } } return match; } //..... private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) { ConditionOutcome[] outcomes = new ConditionOutcome[end - start]; for (int i = start; i < end; i++) { String autoConfigurationClass = autoConfigurationClasses[i]; Set<String> candidates = autoConfigurationMetadata .getSet(autoConfigurationClass, "ConditionalOnClass"); if (candidates != null) { outcomes[i - start] = getOutcome(candidates); } } return outcomes; } } 上述代码虽然多,但最终还是要调用AutoConfigurationMetadata 的getSet方法,我们继续追踪一下这个接口的实现类,它是位于AutoConfigurationMetadataLoader的内部类: /** * {@link AutoConfigurationMetadata} implementation backed by a properties file. */ private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata { //....省略其他代码 @Override public Set<String> getSet(String className, String key) { return getSet(className, key, null); } @Override public Set<String> getSet(String className, String key, Set<String> defaultValue) { String value = get(className, key); return (value != null ? StringUtils.commaDelimitedListToSet(value) : defaultValue); } @Override public String get(String className, String key) { return get(className, key, null); } @Override public String get(String className, String key, String defaultValue) { String value = this.properties.getProperty(className + "." + key); return (value != null ? value : defaultValue); } } 最后我们可以发现其get方法还是通过java.util.Properties的getProperty方法来获取的值
上篇文章,我们简单的了解了WebFlux的一些基础与背景,并通过示例来写了一个demo。我们知道WebFlux是响应式的web框架,其特点之一就是可以通过函数式编程方式配置route。另外究竟什么是响应式编程呢?这篇文章我们就简单探讨一下 一、Java8中的函数式编程 百科中这样定义函数式编程: 函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。那么在Java8里怎么样来实现它呢? 示例一 在这里我先自己写一个例子 定义接口: package com.bdqn.lyrk.basic.java; /** * 函数式接口 * * @author chen.nie * @date 2018/7/18 **/ @FunctionalInterface public interface OperateNumberFunctions { void operate(Integer number); default void print() { } } 在定义的接口上添加@FunctionalInterface表明其是函数式接口,这个注解用于检测函数式接口规范,定义函数式接口时该接口内必须有且只有一个抽象的方法。 定义类: package com.bdqn.lyrk.basic.java; import java.util.Optional; import java.util.function.Predicate; /** * 定义函数式编程类 */ public class NumberFunctions { private Integer number; private NumberFunctions() { } private static NumberFunctions numberFunctions = new NumberFunctions(); public static NumberFunctions of(Integer number) { numberFunctions.number = number; return numberFunctions; } public NumberFunctions add(Integer number) { numberFunctions.number += number; return numberFunctions; } public NumberFunctions subtraction(Integer number) { numberFunctions.number -= number; return numberFunctions; } public Optional<NumberFunctions> filter(Predicate<Integer> predicate) { if (predicate.test(this.number)) return Optional.of(numberFunctions); return Optional.ofNullable(new NumberFunctions()); } public void operate(OperateNumberFunctions functions) { functions.operate(this.number); } } 在这里定义类进行简单的运算与过滤条件。那么在Main方法里可以这么写: package com.bdqn.lyrk.basic.java; public class Main { public static void main(String[] args) { NumberFunctions.of(10).add(30).subtraction(2).filter(number -> number>20).get().operate(System.out::println); } } 那么输出结果为38 示例二 在Java8里有一个类叫Stream。Stream是数据流的意思,这个类略微有点像Reactor中Flux,它提供了类似于操作符的功能,我们来看一个例子: Main方法 package com.bdqn.lyrk.basic.java; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; public class Main { public static void main(String[] args) { /* 在这里先将Stream里的内容做乘2的操作 然后在进行倒序排序 紧接着过滤出是4的倍数的数字 然后转换成集合在打印 */ Stream.of(15, 26, 34, 455, 5, 6).map(number -> number * 2).sorted((num1, num2) -> num2 - num1).filter(integer -> integer % 4 == 0).collect(toList()).forEach(System.out::println); } } 运行得到的结果:685212 关于::操作符 该操作符是lambda表达式的更特殊写法,使用此操作符可以简化函数式接口的实现,这个方法至少满足以下特定条件: 1)方法返回值与函数式接口相同 2)方法参数与函数式接口相同 举例说明 package java.util.function; /** * Represents a supplier of results. * * <p>There is no requirement that a new or distinct result be returned each * time the supplier is invoked. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #get()}. * * @param <T> the type of results supplied by this supplier * * @since 1.8 */ @FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); } java中Runnable接口: @FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); } java中的Predicate接口: package java.util.function; import java.util.Objects; /** * Represents a predicate (boolean-valued function) of one argument. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #test(Object)}. * * @param <T> the type of the input to the predicate * * @since 1.8 */ @FunctionalInterface public interface Predicate<T> { /** * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t); /** * Returns a composed predicate that represents a short-circuiting logical * AND of this predicate and another. When evaluating the composed * predicate, if this predicate is {@code false}, then the {@code other} * predicate is not evaluated. * * <p>Any exceptions thrown during evaluation of either predicate are relayed * to the caller; if evaluation of this predicate throws an exception, the * {@code other} predicate will not be evaluated. * * @param other a predicate that will be logically-ANDed with this * predicate * @return a composed predicate that represents the short-circuiting logical * AND of this predicate and the {@code other} predicate * @throws NullPointerException if other is null */ default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } /** * Returns a predicate that represents the logical negation of this * predicate. * * @return a predicate that represents the logical negation of this * predicate */ default Predicate<T> negate() { return (t) -> !test(t); } /** * Returns a composed predicate that represents a short-circuiting logical * OR of this predicate and another. When evaluating the composed * predicate, if this predicate is {@code true}, then the {@code other} * predicate is not evaluated. * * <p>Any exceptions thrown during evaluation of either predicate are relayed * to the caller; if evaluation of this predicate throws an exception, the * {@code other} predicate will not be evaluated. * * @param other a predicate that will be logically-ORed with this * predicate * @return a composed predicate that represents the short-circuiting logical * OR of this predicate and the {@code other} predicate * @throws NullPointerException if other is null */ default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } /** * Returns a predicate that tests if two arguments are equal according * to {@link Objects#equals(Object, Object)}. * * @param <T> the type of arguments to the predicate * @param targetRef the object reference with which to compare for equality, * which may be {@code null} * @return a predicate that tests if two arguments are equal according * to {@link Objects#equals(Object, Object)} */ static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } } 那么上述的接口分别可以使用如下写法,注意实现该接口的方法特点 package com.bdqn.lyrk.basic.java; import java.util.function.Predicate; import java.util.function.Supplier; public class Main { private static int i; public static void main(String[] args) { /* 创建对象的方式 */ Supplier<Object> supplier = Object::new; /* 调用方法的方式(无参数) */ Runnable runnable = Main::add; /* 调用方法的方式(有参数) */ Predicate<String> predicate = Main::filter; } public static void add() { i++; System.out.println("test" + i); } public static boolean filter(String test) { return test != null; } } 我们可以看到使用函数式编程借助于lambda表达式,使得代码更简洁清爽 二、Java中的响应式编程 关于响应式编程,百度百科是这么定义的: 简称RP(Reactive Programming) 响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。 在这里有两个关键词:数据流与变化传播。下面我们来通过代码来演示下响应式编程是怎么回事 Java8及以前版本 最典型的示例就是,JDK提供的观察者模式类Observer与Observable: package com.hzgj.lyrk.demo; import java.util.Observable; public class ObserverDemo extends Observable { public static void main(String[] args) { ObserverDemo observable = new ObserverDemo(); observable.addObserver((o, arg) -> { System.out.println("发生变化"); }); observable.addObserver((o, arg) -> { System.out.println("收到被观察者通知,准备改变"); }); observable.setChanged(); observable.notifyObservers(); } } 在上述代码示例中观察者并没有及时执行,而是在接受到被观察者发送信号的时候才有了“响应”。其中setChanged()与notifyObservers方法就对应响应式编程中定义的关键词--变化与传播。还有一个典型的示例就是Swing中的事件机制,有兴趣的朋友可以下去查阅相关资料,在这里就不再进行阐述。 Java9及其后版本 从java9开始,Observer与Observable已经被标记为过时的类了,取而代之的是Flow类。Flow才是真正意义上的响应式编程类,因为观察者Observer与Observable虽然能够响应,但是在数据流的体现并不是特别突出。Flow这个类,我们可以先看一下: public final class Flow { private Flow() {} // uninstantiable /** * A producer of items (and related control messages) received by * Subscribers. Each current {@link Subscriber} receives the same * items (via method {@code onNext}) in the same order, unless * drops or errors are encountered. If a Publisher encounters an * error that does not allow items to be issued to a Subscriber, * that Subscriber receives {@code onError}, and then receives no * further messages. Otherwise, when it is known that no further * messages will be issued to it, a subscriber receives {@code * onComplete}. Publishers ensure that Subscriber method * invocations for each subscription are strictly ordered in <a * href="package-summary.html#MemoryVisibility"><i>happens-before</i></a> * order. * * <p>Publishers may vary in policy about whether drops (failures * to issue an item because of resource limitations) are treated * as unrecoverable errors. Publishers may also vary about * whether Subscribers receive items that were produced or * available before they subscribed. * * @param <T> the published item type */ @FunctionalInterface public static interface Publisher<T> { /** * Adds the given Subscriber if possible. If already * subscribed, or the attempt to subscribe fails due to policy * violations or errors, the Subscriber's {@code onError} * method is invoked with an {@link IllegalStateException}. * Otherwise, the Subscriber's {@code onSubscribe} method is * invoked with a new {@link Subscription}. Subscribers may * enable receiving items by invoking the {@code request} * method of this Subscription, and may unsubscribe by * invoking its {@code cancel} method. * * @param subscriber the subscriber * @throws NullPointerException if subscriber is null */ public void subscribe(Subscriber<? super T> subscriber); } /** * A receiver of messages. The methods in this interface are * invoked in strict sequential order for each {@link * Subscription}. * * @param <T> the subscribed item type */ public static interface Subscriber<T> { /** * Method invoked prior to invoking any other Subscriber * methods for the given Subscription. If this method throws * an exception, resulting behavior is not guaranteed, but may * cause the Subscription not to be established or to be cancelled. * * <p>Typically, implementations of this method invoke {@code * subscription.request} to enable receiving items. * * @param subscription a new subscription */ public void onSubscribe(Subscription subscription); /** * Method invoked with a Subscription's next item. If this * method throws an exception, resulting behavior is not * guaranteed, but may cause the Subscription to be cancelled. * * @param item the item */ public void onNext(T item); /** * Method invoked upon an unrecoverable error encountered by a * Publisher or Subscription, after which no other Subscriber * methods are invoked by the Subscription. If this method * itself throws an exception, resulting behavior is * undefined. * * @param throwable the exception */ public void onError(Throwable throwable); /** * Method invoked when it is known that no additional * Subscriber method invocations will occur for a Subscription * that is not already terminated by error, after which no * other Subscriber methods are invoked by the Subscription. * If this method throws an exception, resulting behavior is * undefined. */ public void onComplete(); } /** * Message control linking a {@link Publisher} and {@link * Subscriber}. Subscribers receive items only when requested, * and may cancel at any time. The methods in this interface are * intended to be invoked only by their Subscribers; usages in * other contexts have undefined effects. */ public static interface Subscription { /** * Adds the given number {@code n} of items to the current * unfulfilled demand for this subscription. If {@code n} is * less than or equal to zero, the Subscriber will receive an * {@code onError} signal with an {@link * IllegalArgumentException} argument. Otherwise, the * Subscriber will receive up to {@code n} additional {@code * onNext} invocations (or fewer if terminated). * * @param n the increment of demand; a value of {@code * Long.MAX_VALUE} may be considered as effectively unbounded */ public void request(long n); /** * Causes the Subscriber to (eventually) stop receiving * messages. Implementation is best-effort -- additional * messages may be received after invoking this method. * A cancelled subscription need not ever receive an * {@code onComplete} or {@code onError} signal. */ public void cancel(); } /** * A component that acts as both a Subscriber and Publisher. * * @param <T> the subscribed item type * @param <R> the published item type */ public static interface Processor<T,R> extends Subscriber<T>, Publisher<R> { } static final int DEFAULT_BUFFER_SIZE = 256; /** * Returns a default value for Publisher or Subscriber buffering, * that may be used in the absence of other constraints. * * @implNote * The current value returned is 256. * * @return the buffer size value */ public static int defaultBufferSize() { return DEFAULT_BUFFER_SIZE; } } Flow这个类里定义最基本的Publisher与Subscribe,该模式就是发布订阅模式。我们来看一下代码示例: package com.hzgj.lyrk.demo; import java.util.concurrent.Flow; public class Main { public static void main(String[] args) { Flow.Publisher<String> publisher = subscriber -> { subscriber.onNext("1"); // 1 subscriber.onNext("2"); subscriber.onError(new RuntimeException("出错")); // 2 // subscriber.onComplete(); }; publisher.subscribe(new Flow.Subscriber<>() { @Override public void onSubscribe(Flow.Subscription subscription) { subscription.cancel(); } @Override public void onNext(String item) { System.out.println(item); } @Override public void onError(Throwable throwable) { System.out.println("出错了"); } @Override public void onComplete() { System.out.println("publish complete"); } }); } } 代码1 是一种数据流的体现,在Publisher中每次调用onNext的时候,在中都会在Subscribe的onNext方法进行消费代码2 同样是发送错误信号,等待订阅者进行消费 运行结果: 1 2 出错了 在上述代码中我们可以发现:Publisher在没有被订阅的时候,是不会触发任何行为的。每次调用Publisher的onNext方法的时候都像是在发信号,订阅者收到信号时执行相关内容,这就是典型的响应式编程的案例。不过java9提供的这个功能对异步的支持不太好,也不够强大。因此才会出现Reactor与RxJava等响应式框架
SpringBoot的自动装配是拆箱即用的基础,也是微服务化的前提。其实它并不那么神秘,我在这之前已经写过最基本的实现了,大家可以参考这篇文章。这次主要的议题是,来看看它是怎么样实现的,我们透过源代码来把握自动装配的来龙去脉。 一、自动装配过程分析 1.1、关于@SpringBootApplication 我们在编写SpringBoot项目时,@SpringBootApplication是最常见的注解了,我们可以看一下源代码: /* * 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; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.core.annotation.AliasFor; /** * Indicates a {@link Configuration configuration} class that declares one or more * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience * annotation that is equivalent to declaring {@code @Configuration}, * {@code @EnableAutoConfiguration} and {@code @ComponentScan}. * * @author Phillip Webb * @author Stephane Nicoll * @since 1.2.0 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude") Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName") String[] excludeName() default {}; /** * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses} * for a type-safe alternative to String-based package names. * @return base packages to scan * @since 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; /** * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to * scan for annotated components. The package of each class specified will be scanned. * <p> * Consider creating a special no-op marker class or interface in each package that * serves no purpose other than being referenced by this attribute. * @return base packages to scan * @since 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; } View Code 这里面包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan,此处@ComponentScan由于没有指定扫描包,因此它默认扫描的是与该类同级的类或者同级包下的所有类,另外@SpringBootConfiguration,通过源码得知它是一个@Configuration: /* * Copyright 2012-2016 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; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; /** * Indicates that a class provides Spring Boot application * {@link Configuration @Configuration}. Can be used as an alternative to the Spring's * standard {@code @Configuration} annotation so that configuration can be found * automatically (for example in tests). * <p> * Application should only ever include <em>one</em> {@code @SpringBootConfiguration} and * most idiomatic Spring Boot applications will inherit it from * {@code @SpringBootApplication}. * * @author Phillip Webb * @since 1.4.0 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { } View Code 由此我们可以推断出@SpringBootApplication等同于@Configuration @ComponentScan @EnableAutoConfiguration 1.2、@EnableAutoConfiguration 一旦加上此注解,那么将会开启自动装配功能,简单点讲,Spring会试图在你的classpath下找到所有配置的Bean然后进行装配。当然装配Bean时,会根据若干个(Conditional)定制规则来进行初始化。我们看一下它的源码: /* * 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; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; 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.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.io.support.SpringFactoriesLoader; /** * Enable auto-configuration of the Spring Application Context, attempting to guess and * configure beans that you are likely to need. Auto-configuration classes are usually * applied based on your classpath and what beans you have defined. For example, If you * have {@code tomcat-embedded.jar} on your classpath you are likely to want a * {@link TomcatEmbeddedServletContainerFactory} (unless you have defined your own * {@link EmbeddedServletContainerFactory} bean). * <p> * When using {@link SpringBootApplication}, the auto-configuration of the context is * automatically enabled and adding this annotation has therefore no additional effect. * <p> * Auto-configuration tries to be as intelligent as possible and will back-away as you * define more of your own configuration. You can always manually {@link #exclude()} any * configuration that you never want to apply (use {@link #excludeName()} if you don't * have access to them). You can also exclude them via the * {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied * after user-defined beans have been registered. * <p> * The package of the class that is annotated with {@code @EnableAutoConfiguration}, * usually via {@code @SpringBootApplication}, has specific significance and is often used * as a 'default'. For example, it will be used when scanning for {@code @Entity} classes. * It is generally recommended that you place {@code @EnableAutoConfiguration} (if you're * not using {@code @SpringBootApplication}) in a root package so that all sub-packages * and classes can be searched. * <p> * Auto-configuration classes are regular Spring {@link Configuration} beans. They are * located using the {@link SpringFactoriesLoader} mechanism (keyed against this class). * Generally auto-configuration beans are {@link Conditional @Conditional} beans (most * often using {@link ConditionalOnClass @ConditionalOnClass} and * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations). * * @author Phillip Webb * @author Stephane Nicoll * @see ConditionalOnBean * @see ConditionalOnMissingBean * @see ConditionalOnClass * @see AutoConfigureAfter * @see SpringBootApplication */ @SuppressWarnings("deprecation") @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ String[] excludeName() default {}; } View Code 虽然根据文档注释的说明它指点我们去看EnableAutoConfigurationImportSelector。但是该类在SpringBoot1.5.X版本已经过时了,因此我们看一下它的父类AutoConfigurationImportSelector: /* * 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; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.Aware; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.DeferredImportSelector; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration * auto-configuration}. This class can also be subclassed if a custom variant of * {@link EnableAutoConfiguration @EnableAutoConfiguration}. is needed. * * @author Phillip Webb * @author Andy Wilkinson * @author Stephane Nicoll * @author Madhura Bhave * @since 1.3.0 * @see EnableAutoConfiguration */ public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { private static final String[] NO_IMPORTS = {}; private static final Log logger = LogFactory .getLog(AutoConfigurationImportSelector.class); private ConfigurableListableBeanFactory beanFactory; private Environment environment; private ClassLoader beanClassLoader; private ResourceLoader resourceLoader; @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } try { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); configurations = sort(configurations, autoConfigurationMetadata); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return configurations.toArray(new String[configurations.size()]); } catch (IOException ex) { throw new IllegalStateException(ex); } } protected boolean isEnabled(AnnotationMetadata metadata) { return true; } /** * Return the appropriate {@link AnnotationAttributes} from the * {@link AnnotationMetadata}. By default this method will return attributes for * {@link #getAnnotationClass()}. * @param metadata the annotation metadata * @return annotation attributes */ protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) { String name = getAnnotationClass().getName(); AnnotationAttributes attributes = AnnotationAttributes .fromMap(metadata.getAnnotationAttributes(name, true)); Assert.notNull(attributes, "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?"); return attributes; } /** * Return the source annotation class used by the selector. * @return the annotation class */ protected Class<?> getAnnotationClass() { return EnableAutoConfiguration.class; } /** * Return the auto-configuration class names that should be considered. By default * this method will load candidates using {@link SpringFactoriesLoader} with * {@link #getSpringFactoriesLoaderFactoryClass()}. * @param metadata the source metadata * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation * attributes} * @return a list of candidate configurations */ protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; } /** * Return the class used by {@link SpringFactoriesLoader} to load configuration * candidates. * @return the factory class */ protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) { List<String> invalidExcludes = new ArrayList<String>(exclusions.size()); for (String exclusion : exclusions) { if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) { invalidExcludes.add(exclusion); } } if (!invalidExcludes.isEmpty()) { handleInvalidExcludes(invalidExcludes); } } /** * Handle any invalid excludes that have been specified. * @param invalidExcludes the list of invalid excludes (will always have at least one * element) */ protected void handleInvalidExcludes(List<String> invalidExcludes) { StringBuilder message = new StringBuilder(); for (String exclude : invalidExcludes) { message.append("\t- ").append(exclude).append(String.format("%n")); } throw new IllegalStateException(String .format("The following classes could not be excluded because they are" + " not auto-configuration classes:%n%s", message)); } /** * Return any exclusions that limit the candidate configurations. * @param metadata the source metadata * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation * attributes} * @return exclusions or an empty set */ protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) { Set<String> excluded = new LinkedHashSet<String>(); excluded.addAll(asList(attributes, "exclude")); excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName"))); excluded.addAll(getExcludeAutoConfigurationsProperty()); return excluded; } private List<String> getExcludeAutoConfigurationsProperty() { if (getEnvironment() instanceof ConfigurableEnvironment) { RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( this.environment, "spring.autoconfigure."); Map<String, Object> properties = resolver.getSubProperties("exclude"); if (properties.isEmpty()) { return Collections.emptyList(); } List<String> excludes = new ArrayList<String>(); for (Map.Entry<String, Object> entry : properties.entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); if (name.isEmpty() || name.startsWith("[") && value != null) { excludes.addAll(new HashSet<String>(Arrays.asList(StringUtils .tokenizeToStringArray(String.valueOf(value), ",")))); } } return excludes; } RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(getEnvironment(), "spring.autoconfigure."); String[] exclude = resolver.getProperty("exclude", String[].class); return (Arrays.asList(exclude == null ? new String[0] : exclude)); } private List<String> sort(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) throws IOException { configurations = new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata).getInPriorityOrder(configurations); return configurations; } private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); String[] candidates = configurations.toArray(new String[configurations.size()]); boolean[] skip = new boolean[candidates.length]; boolean skipped = false; for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { invokeAwareMethods(filter); boolean[] match = filter.match(candidates, autoConfigurationMetadata); for (int i = 0; i < match.length; i++) { if (!match[i]) { skip[i] = true; skipped = true; } } } if (!skipped) { return configurations; } List<String> result = new ArrayList<String>(candidates.length); for (int i = 0; i < candidates.length; i++) { if (!skip[i]) { result.add(candidates[i]); } } if (logger.isTraceEnabled()) { int numberFiltered = configurations.size() - result.size(); logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); } return new ArrayList<String>(result); } protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); } private MetadataReaderFactory getMetadataReaderFactory() { try { return getBeanFactory().getBean( SharedMetadataReaderFactoryContextInitializer.BEAN_NAME, MetadataReaderFactory.class); } catch (NoSuchBeanDefinitionException ex) { return new CachingMetadataReaderFactory(this.resourceLoader); } } protected final <T> List<T> removeDuplicates(List<T> list) { return new ArrayList<T>(new LinkedHashSet<T>(list)); } protected final List<String> asList(AnnotationAttributes attributes, String name) { String[] value = attributes.getStringArray(name); return Arrays.asList(value == null ? new String[0] : value); } private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) { List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners(); if (!listeners.isEmpty()) { AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions); for (AutoConfigurationImportListener listener : listeners) { invokeAwareMethods(listener); listener.onAutoConfigurationImportEvent(event); } } } protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader); } private void invokeAwareMethods(Object instance) { if (instance instanceof Aware) { if (instance instanceof BeanClassLoaderAware) { ((BeanClassLoaderAware) instance) .setBeanClassLoader(this.beanClassLoader); } if (instance instanceof BeanFactoryAware) { ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory); } if (instance instanceof EnvironmentAware) { ((EnvironmentAware) instance).setEnvironment(this.environment); } if (instance instanceof ResourceLoaderAware) { ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader); } } } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory); this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } protected final ConfigurableListableBeanFactory getBeanFactory() { return this.beanFactory; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } protected ClassLoader getBeanClassLoader() { return this.beanClassLoader; } @Override public void setEnvironment(Environment environment) { this.environment = environment; } protected final Environment getEnvironment() { return this.environment; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } protected final ResourceLoader getResourceLoader() { return this.resourceLoader; } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE - 1; } } View Code 首先该类实现了DeferredImportSelector接口,这个接口继承了ImportSelector: /* * Copyright 2002-2013 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.context.annotation; import org.springframework.core.type.AnnotationMetadata; /** * Interface to be implemented by types that determine which @{@link Configuration} * class(es) should be imported based on a given selection criteria, usually one or more * annotation attributes. * * <p>An {@link ImportSelector} may implement any of the following * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective * methods will be called prior to {@link #selectImports}: * <ul> * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li> * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li> * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li> * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li> * </ul> * * <p>ImportSelectors are usually processed in the same way as regular {@code @Import} * annotations, however, it is also possible to defer selection of imports until all * {@code @Configuration} classes have been processed (see {@link DeferredImportSelector} * for details). * * @author Chris Beams * @since 3.1 * @see DeferredImportSelector * @see Import * @see ImportBeanDefinitionRegistrar * @see Configuration */ public interface ImportSelector { /** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. */ String[] selectImports(AnnotationMetadata importingClassMetadata); } View Code 该接口主要是为了导入@Configuration的配置项,而DeferredImportSelector是延期导入,当所有的@Configuration都处理过后才会执行。 回过头来我们看一下AutoConfigurationImportSelector的selectImport方法: @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } try { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); configurations = sort(configurations, autoConfigurationMetadata); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return configurations.toArray(new String[configurations.size()]); } catch (IOException ex) { throw new IllegalStateException(ex); } } 该方法刚开始会先判断是否进行自动装配,而后会从META-INF/spring-autoconfigure-metadata.properties读取元数据与元数据的相关属性,紧接着会调用getCandidateConfigurations方法: /** * Return the auto-configuration class names that should be considered. By default * this method will load candidates using {@link SpringFactoriesLoader} with * {@link #getSpringFactoriesLoaderFactoryClass()}. * @param metadata the source metadata * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation * attributes} * @return a list of candidate configurations */ protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; } /** * Return the class used by {@link SpringFactoriesLoader} to load configuration * candidates. * @return the factory class */ protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } 在这里又遇到我们的老熟人了--SpringFactoryiesLoader, 它会读取META-INF/spring.factories下的EnableAutoConfiguration的配置,紧接着在进行排除与过滤,进而得到需要装配的类。最后让所有配置在META-INF/spring.factories下的AutoConfigurationImportListener执行AutoConfigurationImportEvent事件,代码如下: private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) { List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners(); if (!listeners.isEmpty()) { AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions); for (AutoConfigurationImportListener listener : listeners) { invokeAwareMethods(listener); listener.onAutoConfigurationImportEvent(event); } } } protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader); } 二、何时进行自动装配 在前面的环节里只是最终要确定哪些类需要被装配,在SpringBoot时何时处理这些自动装配的类呢?下面我们简要的分析一下: 2.1、AbstractApplicationContext的refresh方法: 这个方法老生常谈了其中请大家关注一下这个方法: // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); 在这里是处理BeanFactoryPostProcessor的,那么我们在来看一下这个接口BeanDefinitionRegistryPostProcessor: /* * Copyright 2002-2010 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.beans.factory.support; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; /** * Extension to the standard {@link BeanFactoryPostProcessor} SPI, allowing for * the registration of further bean definitions <i>before</i> regular * BeanFactoryPostProcessor detection kicks in. In particular, * BeanDefinitionRegistryPostProcessor may register further bean definitions * which in turn define BeanFactoryPostProcessor instances. * * @author Juergen Hoeller * @since 3.0.1 * @see org.springframework.context.annotation.ConfigurationClassPostProcessor */ public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { /** * Modify the application context's internal bean definition registry after its * standard initialization. All regular bean definitions will have been loaded, * but no beans will have been instantiated yet. This allows for adding further * bean definitions before the next post-processing phase kicks in. * @param registry the bean definition registry used by the application context * @throws org.springframework.beans.BeansException in case of errors */ void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException; } View Code 该接口继承了BeanFactoryPostProcessor。 2.2、ConfigurationClassPostProcessor 类 该类主要处理@Configuration注解的,它实现了BeanDefinitionRegistryPostProcessor, 那么也间接实现了BeanFactoryPostProcessor,关键代码如下: @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { int factoryId = System.identityHashCode(beanFactory); if (this.factoriesPostProcessed.contains(factoryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + beanFactory); } this.factoriesPostProcessed.add(factoryId); if (!this.registriesPostProcessed.contains(factoryId)) { // BeanDefinitionRegistryPostProcessor hook apparently not supported... // Simply call processConfigurationClasses lazily at this point then. processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); } enhanceConfigurationClasses(beanFactory); beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory)); } /** * Build and validate a configuration model based on the registry of * {@link Configuration} classes. */ public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { //.....省略部分代码 // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size()); do { parser.parse(candidates); parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); candidates.clear(); if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<String>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // ....省略部分代码 } 其实这里注释已经很清楚了,我们可以清楚的看到解析每一个@ConfigurationClass的关键类是:ConfigurationClassParser,那么我们继续看一看这个类的parse方法: public void parse(Set<BeanDefinitionHolder> configCandidates) { this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>(); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } processDeferredImportSelectors(); } 在这里大家留意一下最后一句processDeferredImportSelectors方法,在这里将会对DeferredImportSelector进行处理,这样我们就和AutoConfigurationSelectImporter结合到一起了: private void processDeferredImportSelectors() { List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors; this.deferredImportSelectors = null; Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR); for (DeferredImportSelectorHolder deferredImport : deferredImports) { ConfigurationClass configClass = deferredImport.getConfigurationClass(); try { String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata()); processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } } } 请大家关注这句代码:String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());在这里deferredImport的类型为DeferredImportSelectorHolder: private static class DeferredImportSelectorHolder { private final ConfigurationClass configurationClass; private final DeferredImportSelector importSelector; public DeferredImportSelectorHolder(ConfigurationClass configClass, DeferredImportSelector selector) { this.configurationClass = configClass; this.importSelector = selector; } public ConfigurationClass getConfigurationClass() { return this.configurationClass; } public DeferredImportSelector getImportSelector() { return this.importSelector; } } 在这个内部类里持有了一个DeferredImportSelector的引用,至此将会执行自动装配的所有操作 三、总结 1)自动装配还是利用了SpringFactoriesLoader来加载META-INF/spring.factoires文件里所有配置的EnableAutoConfgruation,它会经过exclude和filter等操作,最终确定要装配的类 2) 处理@Configuration的核心还是ConfigurationClassPostProcessor,这个类实现了BeanFactoryPostProcessor, 因此当AbstractApplicationContext执行refresh方法里的invokeBeanFactoryPostProcessors(beanFactory)方法时会执行自动装配
本篇文章主要带大家简单分析一下AOP的代理对象,至于AOP是什么,如何配置等基础性知识,不在这里讨论。阅读前请先参考:代理模式,在这之前我们需要了解springframework的三个核心接口与getBean方法 一、FactoryBean&BeanFactory&ObjectFactory 这三个接口都为Springframework的核心接口,虽然这三个名字很像,但是意义却千差万别。面试的时候也常问它们之间的区别。BeanFactory本身就是一个bean的工厂,同时也是我们的IOC容器,而FactoryBean是一个特殊的Bean,我们可以来看看这个接口: /* * Copyright 2002-2016 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.beans.factory; import org.springframework.lang.Nullable; /** * Interface to be implemented by objects used within a {@link BeanFactory} which * are themselves factories for individual objects. If a bean implements this * interface, it is used as a factory for an object to expose, not directly as a * bean instance that will be exposed itself. * * <p><b>NB: A bean that implements this interface cannot be used as a normal bean.</b> * A FactoryBean is defined in a bean style, but the object exposed for bean * references ({@link #getObject()}) is always the object that it creates. * * <p>FactoryBeans can support singletons and prototypes, and can either create * objects lazily on demand or eagerly on startup. The {@link SmartFactoryBean} * interface allows for exposing more fine-grained behavioral metadata. * * <p>This interface is heavily used within the framework itself, for example for * the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the * {@link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for * custom components as well; however, this is only common for infrastructure code. * * <p><b>{@code FactoryBean} is a programmatic contract. Implementations are not * supposed to rely on annotation-driven injection or other reflective facilities.</b> * {@link #getObjectType()} {@link #getObject()} invocations may arrive early in * the bootstrap process, even ahead of any post-processor setup. If you need access * other beans, implement {@link BeanFactoryAware} and obtain them programmatically. * * <p>Finally, FactoryBean objects participate in the containing BeanFactory's * synchronization of bean creation. There is usually no need for internal * synchronization other than for purposes of lazy initialization within the * FactoryBean itself (or the like). * * @author Rod Johnson * @author Juergen Hoeller * @since 08.03.2003 * @see org.springframework.beans.factory.BeanFactory * @see org.springframework.aop.framework.ProxyFactoryBean * @see org.springframework.jndi.JndiObjectFactoryBean */ public interface FactoryBean<T> { /** * Return an instance (possibly shared or independent) of the object * managed by this factory. * <p>As with a {@link BeanFactory}, this allows support for both the * Singleton and Prototype design pattern. * <p>If this FactoryBean is not fully initialized yet at the time of * the call (for example because it is involved in a circular reference), * throw a corresponding {@link FactoryBeanNotInitializedException}. * <p>As of Spring 2.0, FactoryBeans are allowed to return {@code null} * objects. The factory will consider this as normal value to be used; it * will not throw a FactoryBeanNotInitializedException in this case anymore. * FactoryBean implementations are encouraged to throw * FactoryBeanNotInitializedException themselves now, as appropriate. * @return an instance of the bean (can be {@code null}) * @throws Exception in case of creation errors * @see FactoryBeanNotInitializedException */ @Nullable T getObject() throws Exception; /** * Return the type of object that this FactoryBean creates, * or {@code null} if not known in advance. * <p>This allows one to check for specific types of beans without * instantiating objects, for example on autowiring. * <p>In the case of implementations that are creating a singleton object, * this method should try to avoid singleton creation as far as possible; * it should rather estimate the type in advance. * For prototypes, returning a meaningful type here is advisable too. * <p>This method can be called <i>before</i> this FactoryBean has * been fully initialized. It must not rely on state created during * initialization; of course, it can still use such state if available. * <p><b>NOTE:</b> Autowiring will simply ignore FactoryBeans that return * {@code null} here. Therefore it is highly recommended to implement * this method properly, using the current state of the FactoryBean. * @return the type of object that this FactoryBean creates, * or {@code null} if not known at the time of the call * @see ListableBeanFactory#getBeansOfType */ @Nullable Class<?> getObjectType(); /** * Is the object managed by this factory a singleton? That is, * will {@link #getObject()} always return the same object * (a reference that can be cached)? * <p><b>NOTE:</b> If a FactoryBean indicates to hold a singleton object, * the object returned from {@code getObject()} might get cached * by the owning BeanFactory. Hence, do not return {@code true} * unless the FactoryBean always exposes the same reference. * <p>The singleton status of the FactoryBean itself will generally * be provided by the owning BeanFactory; usually, it has to be * defined as singleton there. * <p><b>NOTE:</b> This method returning {@code false} does not * necessarily indicate that returned objects are independent instances. * An implementation of the extended {@link SmartFactoryBean} interface * may explicitly indicate independent instances through its * {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean} * implementations which do not implement this extended interface are * simply assumed to always return independent instances if the * {@code isSingleton()} implementation returns {@code false}. * <p>The default implementation returns {@code true}, since a * {@code FactoryBean} typically manages a singleton instance. * @return whether the exposed object is a singleton * @see #getObject() * @see SmartFactoryBean#isPrototype() */ default boolean isSingleton() { return true; } } View Code 这里面有三个方法,分别为:getObject,getObjectType,isSingleton。根据文档解释,它只是一个生产对象的工厂,被Spring管理 。这个工厂负责提供我们需要的对象。当需要特殊的方式创建Bean时,则考虑实现该接口。我举个例子来说明: package org.hzgj.spring.study; import org.springframework.beans.factory.FactoryBean; import org.springframework.stereotype.Component; @Component public class WaterFactoryBean implements FactoryBean<Water> { @Override public Water getObject() throws Exception { Water water=new Water(); water.setCapacity(20); return water; } @Override public Class<?> getObjectType() { return Water.class; } @Override public boolean isSingleton() { return true; } } //..... package org.hzgj.spring.study; @Deprecated public class Water { private int capacity; public int getCapacity() { return capacity; } public void setCapacity(int capacity) { this.capacity = capacity; } public void test() { System.out.println("test"); } @Deprecated public void test1() { System.out.println("test1"); } } //..... ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml"); Water water = applicationContext.getBean(Water.class); water.test1(); // ....能够成功获取到对象 在上面例子里,我们本身是要获得Water对象,那么此时Water对象实际上是通过FactoryBean创建的,因此我们在获取对象时可以添加我们自己的逻辑。 下面我们根据源代码来追溯一下getBean与BeanFactory关联,具体可以参考一下AbstractBeanFactory的doGetBean方法,那么在这里简单的说明一下执行过程: 1) 如果是单例对象的Bean会去缓存中获取 我们先看一下getSinglelone方法: /** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256); /** Cache of singleton factories: bean name --> ObjectFactory */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16); /** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16); /** * Return the (raw) singleton object registered under the given name. * <p>Checks already instantiated singletons and also allows for an early * reference to a currently created singleton (resolving a circular reference). * @param beanName the name of the bean to look for * @param allowEarlyReference whether early references should be created or not * @return the registered singleton object, or {@code null} if none found */ protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); } 在这里我们获取单例对象时一定和ObjectFactory有关系 2)从它的parentBeanFactory中获取 // Check if bean definition exists in this factory. BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // Not found -> check parent. String nameToLookup = originalBeanName(name); if (args != null) { // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); } else { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } } 3)处理bean的dependsOn final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); getBean(dep); } } 4)根据bean的scope类型来获取对应的bean // Create bean instance. if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } 5) 根据需要做类型转换 // Check if required type matches the type of the actual bean instance. if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) { try { return getTypeConverter().convertIfNecessary(bean, requiredType); } catch (TypeMismatchException ex) { if (logger.isDebugEnabled()) { logger.debug("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } 6)getObjectForBeanInstance 通过源代码我们可以发觉 getObjectForBeanInstance方法调用频率异常之高,那么我们就来看一看,它到底是做什么的: /** * Get the object for the given bean instance, either the bean * instance itself or its created object in case of a FactoryBean. * @param beanInstance the shared bean instance * @param name name that may include factory dereference prefix * @param beanName the canonical bean name * @param mbd the merged bean definition * @return the object to expose for the bean */ protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, RootBeanDefinition mbd) { // Don't let calling code try to dereference the factory if the bean isn't a factory. if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) { throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass()); } // Now we have the bean instance, which may be a normal bean or a FactoryBean. // If it's a FactoryBean, we use it to create a bean instance, unless the // caller actually wants a reference to the factory. if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { return beanInstance; } Object object = null; if (mbd == null) { object = getCachedObjectForFactoryBean(beanName); } if (object == null) { // Return bean instance from factory. FactoryBean<?> factory = (FactoryBean<?>) beanInstance; // Caches object obtained from FactoryBean if it is a singleton. if (mbd == null && containsBeanDefinition(beanName)) { mbd = getMergedLocalBeanDefinition(beanName); } boolean synthetic = (mbd != null && mbd.isSynthetic()); object = getObjectFromFactoryBean(factory, beanName, !synthetic); } return object; } 这段代码最主要是看看是否需要从FactoryBean获取对象的。 最后我们在聊聊ObjectFactory: public interface ObjectFactory<T> { /** * Return an instance (possibly shared or independent) * of the object managed by this factory. * @return an instance of the bean (should never be {@code null}) * @throws BeansException in case of creation errors */ T getObject() throws BeansException; } 该接口和FactoryBean很像,根据文档说明其getObject方法的返回值不建议为null,另外我们可以发现Bean为singlone时会大量的使用ObjectFactory处理,代码示例: package org.hzgj.spring.study; import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.stereotype.Component; @Component public class WaterFactory implements ObjectFactory { @Override public Object getObject() throws BeansException { return new Water(); } } //这样子无法获取water,它只单纯是个工厂 ObjectFactory更像是一个在BeanFactory通过Bean名称关联的对象,只不过它在运行时确定getObject()方法返回的对象内容,再者它不像BeanFactory一样能够制定Bean的类型 二、AOP的核心探究 2.1、核心接口初探 为什么刚开始要说FactoryBean,因为它的文档注释已经提醒我们去参考ProxyFactoryBean了,ProxyFactoryBean是生成目标对象代理的核心,那么我们在此先看一下类图: 我们可以得知ProxyFactoryBean实现了FactoryBean。 关于AOP的几个重要的核心接口和类如下: ProxyConfig: /* * Copyright 2002-2012 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.aop.framework; import java.io.Serializable; import org.springframework.util.Assert; /** * Convenience superclass for configuration used in creating proxies, * to ensure that all proxy creators have consistent properties. * * @author Rod Johnson * @author Juergen Hoeller * @see AdvisedSupport */ public class ProxyConfig implements Serializable { /** use serialVersionUID from Spring 1.2 for interoperability */ private static final long serialVersionUID = -8409359707199703185L; private boolean proxyTargetClass = false; private boolean optimize = false; boolean opaque = false; boolean exposeProxy = false; private boolean frozen = false; /** * Set whether to proxy the target class directly, instead of just proxying * specific interfaces. Default is "false". * <p>Set this to "true" to force proxying for the TargetSource's exposed * target class. If that target class is an interface, a JDK proxy will be * created for the given interface. If that target class is any other class, * a CGLIB proxy will be created for the given class. * <p>Note: Depending on the configuration of the concrete proxy factory, * the proxy-target-class behavior will also be applied if no interfaces * have been specified (and no interface autodetection is activated). * @see org.springframework.aop.TargetSource#getTargetClass() */ public void setProxyTargetClass(boolean proxyTargetClass) { this.proxyTargetClass = proxyTargetClass; } /** * Return whether to proxy the target class directly as well as any interfaces. */ public boolean isProxyTargetClass() { return this.proxyTargetClass; } /** * Set whether proxies should perform aggressive optimizations. * The exact meaning of "aggressive optimizations" will differ * between proxies, but there is usually some tradeoff. * Default is "false". * <p>For example, optimization will usually mean that advice changes won't * take effect after a proxy has been created. For this reason, optimization * is disabled by default. An optimize value of "true" may be ignored * if other settings preclude optimization: for example, if "exposeProxy" * is set to "true" and that's not compatible with the optimization. */ public void setOptimize(boolean optimize) { this.optimize = optimize; } /** * Return whether proxies should perform aggressive optimizations. */ public boolean isOptimize() { return this.optimize; } /** * Set whether proxies created by this configuration should be prevented * from being cast to {@link Advised} to query proxy status. * <p>Default is "false", meaning that any AOP proxy can be cast to * {@link Advised}. */ public void setOpaque(boolean opaque) { this.opaque = opaque; } /** * Return whether proxies created by this configuration should be * prevented from being cast to {@link Advised}. */ public boolean isOpaque() { return this.opaque; } /** * Set whether the proxy should be exposed by the AOP framework as a * ThreadLocal for retrieval via the AopContext class. This is useful * if an advised object needs to call another advised method on itself. * (If it uses {@code this}, the invocation will not be advised). * <p>Default is "false", in order to avoid unnecessary extra interception. * This means that no guarantees are provided that AopContext access will * work consistently within any method of the advised object. */ public void setExposeProxy(boolean exposeProxy) { this.exposeProxy = exposeProxy; } /** * Return whether the AOP proxy will expose the AOP proxy for * each invocation. */ public boolean isExposeProxy() { return this.exposeProxy; } /** * Set whether this config should be frozen. * <p>When a config is frozen, no advice changes can be made. This is * useful for optimization, and useful when we don't want callers to * be able to manipulate configuration after casting to Advised. */ public void setFrozen(boolean frozen) { this.frozen = frozen; } /** * Return whether the config is frozen, and no advice changes can be made. */ public boolean isFrozen() { return this.frozen; } /** * Copy configuration from the other config object. * @param other object to copy configuration from */ public void copyFrom(ProxyConfig other) { Assert.notNull(other, "Other ProxyConfig object must not be null"); this.proxyTargetClass = other.proxyTargetClass; this.optimize = other.optimize; this.exposeProxy = other.exposeProxy; this.frozen = other.frozen; this.opaque = other.opaque; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("proxyTargetClass=").append(this.proxyTargetClass).append("; "); sb.append("optimize=").append(this.optimize).append("; "); sb.append("opaque=").append(this.opaque).append("; "); sb.append("exposeProxy=").append(this.exposeProxy).append("; "); sb.append("frozen=").append(this.frozen); return sb.toString(); } } View Code 该类定义代理类最基本的代理配置 Advised: /* * Copyright 2002-2015 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.aop.framework; import org.aopalliance.aop.Advice; import org.springframework.aop.Advisor; import org.springframework.aop.TargetClassAware; import org.springframework.aop.TargetSource; /** * Interface to be implemented by classes that hold the configuration * of a factory of AOP proxies. This configuration includes the * Interceptors and other advice, Advisors, and the proxied interfaces. * * <p>Any AOP proxy obtained from Spring can be cast to this interface to * allow manipulation of its AOP advice. * * @author Rod Johnson * @author Juergen Hoeller * @since 13.03.2003 * @see org.springframework.aop.framework.AdvisedSupport */ public interface Advised extends TargetClassAware { /** * Return whether the Advised configuration is frozen, * in which case no advice changes can be made. */ boolean isFrozen(); /** * Are we proxying the full target class instead of specified interfaces? */ boolean isProxyTargetClass(); /** * Return the interfaces proxied by the AOP proxy. * <p>Will not include the target class, which may also be proxied. */ Class<?>[] getProxiedInterfaces(); /** * Determine whether the given interface is proxied. * @param intf the interface to check */ boolean isInterfaceProxied(Class<?> intf); /** * Change the {@code TargetSource} used by this {@code Advised} object. * <p>Only works if the configuration isn't {@linkplain #isFrozen frozen}. * @param targetSource new TargetSource to use */ void setTargetSource(TargetSource targetSource); /** * Return the {@code TargetSource} used by this {@code Advised} object. */ TargetSource getTargetSource(); /** * Set whether the proxy should be exposed by the AOP framework as a * {@link ThreadLocal} for retrieval via the {@link AopContext} class. * <p>It can be necessary to expose the proxy if an advised object needs * to invoke a method on itself with advice applied. Otherwise, if an * advised object invokes a method on {@code this}, no advice will be applied. * <p>Default is {@code false}, for optimal performance. */ void setExposeProxy(boolean exposeProxy); /** * Return whether the factory should expose the proxy as a {@link ThreadLocal}. * <p>It can be necessary to expose the proxy if an advised object needs * to invoke a method on itself with advice applied. Otherwise, if an * advised object invokes a method on {@code this}, no advice will be applied. * <p>Getting the proxy is analogous to an EJB calling {@code getEJBObject()}. * @see AopContext */ boolean isExposeProxy(); /** * Set whether this proxy configuration is pre-filtered so that it only * contains applicable advisors (matching this proxy's target class). * <p>Default is "false". Set this to "true" if the advisors have been * pre-filtered already, meaning that the ClassFilter check can be skipped * when building the actual advisor chain for proxy invocations. * @see org.springframework.aop.ClassFilter */ void setPreFiltered(boolean preFiltered); /** * Return whether this proxy configuration is pre-filtered so that it only * contains applicable advisors (matching this proxy's target class). */ boolean isPreFiltered(); /** * Return the advisors applying to this proxy. * @return a list of Advisors applying to this proxy (never {@code null}) */ Advisor[] getAdvisors(); /** * Add an advisor at the end of the advisor chain. * <p>The Advisor may be an {@link org.springframework.aop.IntroductionAdvisor}, * in which new interfaces will be available when a proxy is next obtained * from the relevant factory. * @param advisor the advisor to add to the end of the chain * @throws AopConfigException in case of invalid advice */ void addAdvisor(Advisor advisor) throws AopConfigException; /** * Add an Advisor at the specified position in the chain. * @param advisor the advisor to add at the specified position in the chain * @param pos position in chain (0 is head). Must be valid. * @throws AopConfigException in case of invalid advice */ void addAdvisor(int pos, Advisor advisor) throws AopConfigException; /** * Remove the given advisor. * @param advisor the advisor to remove * @return {@code true} if the advisor was removed; {@code false} * if the advisor was not found and hence could not be removed */ boolean removeAdvisor(Advisor advisor); /** * Remove the advisor at the given index. * @param index index of advisor to remove * @throws AopConfigException if the index is invalid */ void removeAdvisor(int index) throws AopConfigException; /** * Return the index (from 0) of the given advisor, * or -1 if no such advisor applies to this proxy. * <p>The return value of this method can be used to index into the advisors array. * @param advisor the advisor to search for * @return index from 0 of this advisor, or -1 if there's no such advisor */ int indexOf(Advisor advisor); /** * Replace the given advisor. * <p><b>Note:</b> If the advisor is an {@link org.springframework.aop.IntroductionAdvisor} * and the replacement is not or implements different interfaces, the proxy will need * to be re-obtained or the old interfaces won't be supported and the new interface * won't be implemented. * @param a the advisor to replace * @param b the advisor to replace it with * @return whether it was replaced. If the advisor wasn't found in the * list of advisors, this method returns {@code false} and does nothing. * @throws AopConfigException in case of invalid advice */ boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException; /** * Add the given AOP Alliance advice to the tail of the advice (interceptor) chain. * <p>This will be wrapped in a DefaultPointcutAdvisor with a pointcut that always * applies, and returned from the {@code getAdvisors()} method in this wrapped form. * <p>Note that the given advice will apply to all invocations on the proxy, * even to the {@code toString()} method! Use appropriate advice implementations * or specify appropriate pointcuts to apply to a narrower set of methods. * @param advice advice to add to the tail of the chain * @throws AopConfigException in case of invalid advice * @see #addAdvice(int, Advice) * @see org.springframework.aop.support.DefaultPointcutAdvisor */ void addAdvice(Advice advice) throws AopConfigException; /** * Add the given AOP Alliance Advice at the specified position in the advice chain. * <p>This will be wrapped in a {@link org.springframework.aop.support.DefaultPointcutAdvisor} * with a pointcut that always applies, and returned from the {@link #getAdvisors()} * method in this wrapped form. * <p>Note: The given advice will apply to all invocations on the proxy, * even to the {@code toString()} method! Use appropriate advice implementations * or specify appropriate pointcuts to apply to a narrower set of methods. * @param pos index from 0 (head) * @param advice advice to add at the specified position in the advice chain * @throws AopConfigException in case of invalid advice */ void addAdvice(int pos, Advice advice) throws AopConfigException; /** * Remove the Advisor containing the given advice. * @param advice the advice to remove * @return {@code true} of the advice was found and removed; * {@code false} if there was no such advice */ boolean removeAdvice(Advice advice); /** * Return the index (from 0) of the given AOP Alliance Advice, * or -1 if no such advice is an advice for this proxy. * <p>The return value of this method can be used to index into * the advisors array. * @param advice AOP Alliance advice to search for * @return index from 0 of this advice, or -1 if there's no such advice */ int indexOf(Advice advice); /** * As {@code toString()} will normally be delegated to the target, * this returns the equivalent for the AOP proxy. * @return a string description of the proxy configuration */ String toProxyConfigString(); } View Code 该接口主要定义了代理类的工厂基本的行为,比如说添加Advisor,添加Advise,删除与替换Adivsor等 Adivise: 通知接口,该接口没有方法定义,其常见的子接口有BeforeAdvise,AfterAdvise,MethodInterceptor等 PointCut: 切点接口,该接口定义如下: /* * Copyright 2002-2012 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.aop; /** * Core Spring pointcut abstraction. * * <p>A pointcut is composed of a {@link ClassFilter} and a {@link MethodMatcher}. * Both these basic terms and a Pointcut itself can be combined to build up combinations * (e.g. through {@link org.springframework.aop.support.ComposablePointcut}). * * @author Rod Johnson * @see ClassFilter * @see MethodMatcher * @see org.springframework.aop.support.Pointcuts * @see org.springframework.aop.support.ClassFilters * @see org.springframework.aop.support.MethodMatchers */ public interface Pointcut { /** * Return the ClassFilter for this pointcut. * @return the ClassFilter (never {@code null}) */ ClassFilter getClassFilter(); /** * Return the MethodMatcher for this pointcut. * @return the MethodMatcher (never {@code null}) */ MethodMatcher getMethodMatcher(); /** * Canonical Pointcut instance that always matches. */ Pointcut TRUE = TruePointcut.INSTANCE; } View Code Pointcut由ClassFilter和MethodMatcher构成。它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就拥有了描述某些类的某些特定方法的能力。 Advisor: 代表一般切面,它仅包含一个Advice,我们说过,因为Advice包含了横切代码和连接点的信息,所以Advior本身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为这个横切面太宽泛,所以一般不会直接使用。 2.2、源码分析 我们先来看看ProxyFactoryBean的相关方法 getObject方法: /** * Return a proxy. Invoked when clients obtain beans from this factory bean. * Create an instance of the AOP proxy to be returned by this factory. * The instance will be cached for a singleton, and create on each call to * {@code getObject()} for a proxy. * @return a fresh AOP proxy reflecting the current state of this factory */ @Override public Object getObject() throws BeansException { initializeAdvisorChain(); if (isSingleton()) { return getSingletonInstance(); } else { if (this.targetName == null) { logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " + "Enable prototype proxies by setting the 'targetName' property."); } return newPrototypeInstance(); } } 这里面我们关注一下getSingletonInstance方法: /** * Return the singleton instance of this class's proxy object, * lazily creating it if it hasn't been created already. * @return the shared singleton proxy */ private synchronized Object getSingletonInstance() { if (this.singletonInstance == null) { this.targetSource = freshTargetSource(); if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { // Rely on AOP infrastructure to tell us what interfaces to proxy. Class<?> targetClass = getTargetClass(); if (targetClass == null) { throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy"); } setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader)); } // Initialize the shared singleton instance. super.setFrozen(this.freezeProxy); this.singletonInstance = getProxy(createAopProxy()); } return this.singletonInstance; } 在这里我们关在关注一下getProxy(AopProxy aopProxy)方法,AopProxy是一个接口: /* * Copyright 2002-2012 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.aop.framework; /** * Delegate interface for a configured AOP proxy, allowing for the creation * of actual proxy objects. * * <p>Out-of-the-box implementations are available for JDK dynamic proxies * and for CGLIB proxies, as applied by {@link DefaultAopProxyFactory}. * * @author Rod Johnson * @author Juergen Hoeller * @see DefaultAopProxyFactory */ public interface AopProxy { /** * Create a new proxy object. * <p>Uses the AopProxy's default class loader (if necessary for proxy creation): * usually, the thread context class loader. * @return the new proxy object (never {@code null}) * @see Thread#getContextClassLoader() */ Object getProxy(); /** * Create a new proxy object. * <p>Uses the given class loader (if necessary for proxy creation). * {@code null} will simply be passed down and thus lead to the low-level * proxy facility's default, which is usually different from the default chosen * by the AopProxy implementation's {@link #getProxy()} method. * @param classLoader the class loader to create the proxy with * (or {@code null} for the low-level proxy facility's default) * @return the new proxy object (never {@code null}) */ Object getProxy(ClassLoader classLoader); } View Code 该接口有如下实现类:JdkDynamicAopProxy , CglibAopProxy , ObjenesisCglibAopProxy。 那么在这里我们看一下JdkDynamicAopProxy的源码,我只贴出其中一个关键部分: @Override public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } 那么我们可以看出:JDK的动态代理是AOP的实现方式之一 三、基于AOP的核心类与接口实现代理 1、先定义基本的JavaBean: package org.hzgj.spring.study; @Aop public class Water { private int capacity; public int getCapacity() { return capacity; } public void setCapacity(int capacity) { this.capacity = capacity; } public void test() { System.out.println("test"); } @Aop public void test1() { System.out.println("test1"); } } View Code 2、自定义注解 package org.hzgj.spring.study; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.TYPE}) public @interface Aop { } View Code 3、定义JavaBean的代理 package org.hzgj.spring.study; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.framework.AbstractSingletonProxyFactoryBean; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; import org.springframework.stereotype.Component; /** * */ @Component public class WaterProxyFactoryBean extends AbstractSingletonProxyFactoryBean { public WaterProxyFactoryBean() { super.setTarget(new Water()); } @Override protected Object createMainInterceptor() { AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(Aop.class, Aop.class); DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, (MethodInterceptor) invocation -> { System.out.println(1); return invocation.proceed(); }); return advisor; } } View Code 该类继承AbstractSingletonProxyFactoryBean,然后需要重写createMainInterceptor,我在这里定义了一个DefaultPointcutAdvisor与扫描注解的PointCut,至此切点,通知,代理都有了,那么AOP最基本的条件也就具备了 4、主程序 package org.hzgj.spring.study; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml"); Water water = applicationContext.getBean(Water.class); water.test1(); } } View Code 运行成功时会得到如下结果:
SpringApplication是SpringBoot的启动程序,我们通过它的run方法可以快速启动一个SpringBoot应用。可是这里面到底发生了什么?它是处于什么样的机制简化我们程序启动的?接下来我们就带着这两个问题来揭开SpringBoot启动过程的神秘面纱。 一、基于Springframework的事件机制 事件是SpringBoot的启动核心之一。对于事件我想大家都不陌生,在javaAWT中事件是在常见不过的了。 1.1、JDK中的事件接口与类 首先我们看一下EventObject,这个类定义了一个事件,该类中的source属性可以用来表示事件源(哪个对象触发的事件) /* * Copyright (c) 1996, 2003, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package java.util; /** * <p> * The root class from which all event state objects shall be derived. * <p> * All Events are constructed with a reference to the object, the "source", * that is logically deemed to be the object upon which the Event in question * initially occurred upon. * * @since JDK1.1 */ public class EventObject implements java.io.Serializable { private static final long serialVersionUID = 5516075349620653480L; /** * The object on which the Event initially occurred. */ protected transient Object source; /** * Constructs a prototypical Event. * * @param source The object on which the Event initially occurred. * @exception IllegalArgumentException if source is null. */ public EventObject(Object source) { if (source == null) throw new IllegalArgumentException("null source"); this.source = source; } /** * The object on which the Event initially occurred. * * @return The object on which the Event initially occurred. */ public Object getSource() { return source; } /** * Returns a String representation of this EventObject. * * @return A a String representation of this EventObject. */ public String toString() { return getClass().getName() + "[source=" + source + "]"; } } View Code 我们看一下在AWT中很经典的MouseEvent的类关系图: 其次我们需要了解一下关于事件监听的接口EventListener: package java.util; /** * A tagging interface that all event listener interfaces must extend. * @since JDK1.1 */ public interface EventListener { } 这个接口很简单,没有任何方法,但是JDK文档已经明确告诉我们:所有事件的监听必须继承此接口,那么我在贴出来一个MouseListener接口示例: /* * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package java.awt.event; import java.util.EventListener; /** * The listener interface for receiving "interesting" mouse events * (press, release, click, enter, and exit) on a component. * (To track mouse moves and mouse drags, use the * <code>MouseMotionListener</code>.) * <P> * The class that is interested in processing a mouse event * either implements this interface (and all the methods it * contains) or extends the abstract <code>MouseAdapter</code> class * (overriding only the methods of interest). * <P> * The listener object created from that class is then registered with a * component using the component's <code>addMouseListener</code> * method. A mouse event is generated when the mouse is pressed, released * clicked (pressed and released). A mouse event is also generated when * the mouse cursor enters or leaves a component. When a mouse event * occurs, the relevant method in the listener object is invoked, and * the <code>MouseEvent</code> is passed to it. * * @author Carl Quinn * * @see MouseAdapter * @see MouseEvent * @see <a href="https://docs.oracle.com/javase/tutorial/uiswing/events/mouselistener.html">Tutorial: Writing a Mouse Listener</a> * * @since 1.1 */ public interface MouseListener extends EventListener { /** * Invoked when the mouse button has been clicked (pressed * and released) on a component. */ public void mouseClicked(MouseEvent e); /** * Invoked when a mouse button has been pressed on a component. */ public void mousePressed(MouseEvent e); /** * Invoked when a mouse button has been released on a component. */ public void mouseReleased(MouseEvent e); /** * Invoked when the mouse enters a component. */ public void mouseEntered(MouseEvent e); /** * Invoked when the mouse exits a component. */ public void mouseExited(MouseEvent e); } View Code 我们可以看到MouseListener继承了EventListener接口,接口中的方法参数都为MouseEvent。 1.2、spring中的事件类 Spring中也给我们提供了一套事件处理机制,其中几个较为关键的接口和类分别是: ApplicationEvent ApplicationListener ApplicationEventPublisher ApplicationEventMulticaster 下面我们来依次看一下这几个类与接口: ApplicationEvent: /* * Copyright 2002-2015 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.context; import java.util.EventObject; /** * Class to be extended by all application events. Abstract as it * doesn't make sense for generic events to be published directly. * * @author Rod Johnson * @author Juergen Hoeller */ public abstract class ApplicationEvent extends EventObject { /** use serialVersionUID from Spring 1.2 for interoperability */ private static final long serialVersionUID = 7099057708183571937L; /** System time when the event happened */ private final long timestamp; /** * Create a new ApplicationEvent. * @param source the object on which the event initially occurred (never {@code null}) */ public ApplicationEvent(Object source) { super(source); this.timestamp = System.currentTimeMillis(); } /** * Return the system time in milliseconds when the event happened. */ public final long getTimestamp() { return this.timestamp; } } View Code 在这里我们可以明确看到该类直接继承EventObject ApplicationListener: /* * Copyright 2002-2015 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.context; import java.util.EventListener; /** * Interface to be implemented by application event listeners. * Based on the standard {@code java.util.EventListener} interface * for the Observer design pattern. * * <p>As of Spring 3.0, an ApplicationListener can generically declare the event type * that it is interested in. When registered with a Spring ApplicationContext, events * will be filtered accordingly, with the listener getting invoked for matching event * objects only. * * @author Rod Johnson * @author Juergen Hoeller * @param <E> the specific ApplicationEvent subclass to listen to * @see org.springframework.context.event.ApplicationEventMulticaster */ public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /** * Handle an application event. * @param event the event to respond to */ void onApplicationEvent(E event); } View Code 我们可以看到该接口继承EventListener ApplicationEventPublisher: /* * Copyright 2002-2015 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.context; /** * Interface that encapsulates event publication functionality. * Serves as super-interface for {@link ApplicationContext}. * * @author Juergen Hoeller * @author Stephane Nicoll * @since 1.1.1 * @see ApplicationContext * @see ApplicationEventPublisherAware * @see org.springframework.context.ApplicationEvent * @see org.springframework.context.event.EventPublicationInterceptor */ public interface ApplicationEventPublisher { /** * Notify all <strong>matching</strong> listeners registered with this * application of an application event. Events may be framework events * (such as RequestHandledEvent) or application-specific events. * @param event the event to publish * @see org.springframework.web.context.support.RequestHandledEvent */ void publishEvent(ApplicationEvent event); /** * Notify all <strong>matching</strong> listeners registered with this * application of an event. * <p>If the specified {@code event} is not an {@link ApplicationEvent}, * it is wrapped in a {@link PayloadApplicationEvent}. * @param event the event to publish * @since 4.2 * @see PayloadApplicationEvent */ void publishEvent(Object event); } View Code 这个接口比较重要,它使用来触发一个事件的(虽然方法的名称为发布事件),调用方法publishEvent过后,事件对应的listener将会执行相应的内容 ApplicationEventMulticaster 该接口管理ApplicationListener的同时可以执行listener监听事件的方法: /* * Copyright 2002-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.context.event; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.ResolvableType; /** * Interface to be implemented by objects that can manage a number of * {@link ApplicationListener} objects, and publish events to them. * * <p>An {@link org.springframework.context.ApplicationEventPublisher}, typically * a Spring {@link org.springframework.context.ApplicationContext}, can use an * ApplicationEventMulticaster as a delegate for actually publishing events. * * @author Rod Johnson * @author Juergen Hoeller * @author Stephane Nicoll */ public interface ApplicationEventMulticaster { /** * Add a listener to be notified of all events. * @param listener the listener to add */ void addApplicationListener(ApplicationListener<?> listener); /** * Add a listener bean to be notified of all events. * @param listenerBeanName the name of the listener bean to add */ void addApplicationListenerBean(String listenerBeanName); /** * Remove a listener from the notification list. * @param listener the listener to remove */ void removeApplicationListener(ApplicationListener<?> listener); /** * Remove a listener bean from the notification list. * @param listenerBeanName the name of the listener bean to add */ void removeApplicationListenerBean(String listenerBeanName); /** * Remove all listeners registered with this multicaster. * <p>After a remove call, the multicaster will perform no action * on event notification until new listeners are being registered. */ void removeAllListeners(); /** * Multicast the given application event to appropriate listeners. * <p>Consider using {@link #multicastEvent(ApplicationEvent, ResolvableType)} * if possible as it provides a better support for generics-based events. * @param event the event to multicast */ void multicastEvent(ApplicationEvent event); /** * Multicast the given application event to appropriate listeners. * <p>If the {@code eventType} is {@code null}, a default type is built * based on the {@code event} instance. * @param event the event to multicast * @param eventType the type of event (can be null) * @since 4.2 */ void multicastEvent(ApplicationEvent event, ResolvableType eventType); } View Code 我们可以看一下其子类SimpleApplicationEventMulticaster 的源码: /* * Copyright 2002-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.context.event; import java.util.concurrent.Executor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.ResolvableType; import org.springframework.util.ErrorHandler; /** * Simple implementation of the {@link ApplicationEventMulticaster} interface. * * <p>Multicasts all events to all registered listeners, leaving it up to * the listeners to ignore events that they are not interested in. * Listeners will usually perform corresponding {@code instanceof} * checks on the passed-in event object. * * <p>By default, all listeners are invoked in the calling thread. * This allows the danger of a rogue listener blocking the entire application, * but adds minimal overhead. Specify an alternative task executor to have * listeners executed in different threads, for example from a thread pool. * * @author Rod Johnson * @author Juergen Hoeller * @author Stephane Nicoll * @see #setTaskExecutor */ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster { private Executor taskExecutor; private ErrorHandler errorHandler; /** * Create a new SimpleApplicationEventMulticaster. */ public SimpleApplicationEventMulticaster() { } /** * Create a new SimpleApplicationEventMulticaster for the given BeanFactory. */ public SimpleApplicationEventMulticaster(BeanFactory beanFactory) { setBeanFactory(beanFactory); } /** * Set a custom executor (typically a {@link org.springframework.core.task.TaskExecutor}) * to invoke each listener with. * <p>Default is equivalent to {@link org.springframework.core.task.SyncTaskExecutor}, * executing all listeners synchronously in the calling thread. * <p>Consider specifying an asynchronous task executor here to not block the * caller until all listeners have been executed. However, note that asynchronous * execution will not participate in the caller's thread context (class loader, * transaction association) unless the TaskExecutor explicitly supports this. * @see org.springframework.core.task.SyncTaskExecutor * @see org.springframework.core.task.SimpleAsyncTaskExecutor */ public void setTaskExecutor(Executor taskExecutor) { this.taskExecutor = taskExecutor; } /** * Return the current task executor for this multicaster. */ protected Executor getTaskExecutor() { return this.taskExecutor; } /** * Set the {@link ErrorHandler} to invoke in case an exception is thrown * from a listener. * <p>Default is none, with a listener exception stopping the current * multicast and getting propagated to the publisher of the current event. * If a {@linkplain #setTaskExecutor task executor} is specified, each * individual listener exception will get propagated to the executor but * won't necessarily stop execution of other listeners. * <p>Consider setting an {@link ErrorHandler} implementation that catches * and logs exceptions (a la * {@link org.springframework.scheduling.support.TaskUtils#LOG_AND_SUPPRESS_ERROR_HANDLER}) * or an implementation that logs exceptions while nevertheless propagating them * (e.g. {@link org.springframework.scheduling.support.TaskUtils#LOG_AND_PROPAGATE_ERROR_HANDLER}). * @since 4.1 */ public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } /** * Return the current error handler for this multicaster. * @since 4.1 */ protected ErrorHandler getErrorHandler() { return this.errorHandler; } @Override public void multicastEvent(ApplicationEvent event) { multicastEvent(event, resolveDefaultEventType(event)); } @Override public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(new Runnable() { @Override public void run() { invokeListener(listener, event); } }); } else { invokeListener(listener, event); } } } private ResolvableType resolveDefaultEventType(ApplicationEvent event) { return ResolvableType.forInstance(event); } /** * Invoke the given listener with the given event. * @param listener the ApplicationListener to invoke * @param event the current event to propagate * @since 4.1 */ protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { ErrorHandler errorHandler = getErrorHandler(); if (errorHandler != null) { try { doInvokeListener(listener, event); } catch (Throwable err) { errorHandler.handleError(err); } } else { doInvokeListener(listener, event); } } @SuppressWarnings({"unchecked", "rawtypes"}) private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { try { listener.onApplicationEvent(event); } catch (ClassCastException ex) { String msg = ex.getMessage(); if (msg == null || msg.startsWith(event.getClass().getName())) { // Possibly a lambda-defined listener which we could not resolve the generic event type for Log logger = LogFactory.getLog(getClass()); if (logger.isDebugEnabled()) { logger.debug("Non-matching event type for listener: " + listener, ex); } } else { throw ex; } } } } View Code 请大家看一下 doInvokeListener方法,该方法用于执行事件的监听方法 1.3、基于Spring的自定义事件 在这里我们模拟一个场景,当感到饥饿时,通知厨师做饭 定义事件: package org.hzgj.spring.study.event; import org.springframework.context.ApplicationEvent; /** * 定义一个描饥饿状态的事件 * * @author chen.nie * @date 2018/4/26 **/ public class HungryEvent extends ApplicationEvent { /** * Create a new ApplicationEvent. * * @param source the object on which the event initially occurred (never {@code null}) */ public HungryEvent(Object source) { super(source); } } View Code 定义Person: package org.hzgj.spring.study.event; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.stereotype.Component; /** * Person类,如果属性hungry的值为0,则通知厨师做饭吃。 */ @Component public class Person implements ApplicationEventPublisherAware { private int hungry; private String name; public int getHungry() { return hungry; } public void setHungry(int hungry) { this.hungry = hungry; } public String getName() { return name; } public void setName(String name) { this.name = name; } private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } public void isNeedEat() { if (this.hungry == 0) { System.out.println("太饿了,需要吃东西"); new Thread(() -> this.applicationEventPublisher.publishEvent(new HungryEvent(this))).start(); System.out.println("通知完毕"); } } } View Code 注意这里面利用spring的aware模式拿到ApplicationEventPublisher对象,在Spring里有若干个Aware,比如说ApplicationContextAware BeanFactoryAware等。 定义厨师类: package org.hzgj.spring.study.event; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; /** * 厨师类用于对饥饿事件的监听... * * @author chen.nie * @date 2018/4/26 **/ @Component public class Chef implements ApplicationListener<HungryEvent> { @Override public void onApplicationEvent(HungryEvent event) { if (event.getSource() instanceof Person) { Person person = (Person) event.getSource(); System.out.println(person.getName() + "饿了,开始做饭"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("做饭完毕....开始吃吧"); } } } View Code spring-config.xml: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.hzgj"/> </beans> View Code Main方法: package org.hzgj.spring.study; import org.hzgj.spring.study.event.Person; import org.springframework.context.support.ClassPathXmlApplicationContext; import javax.naming.NamingException; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException, NamingException { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml"); Person person = applicationContext.getBean(Person.class); person.setHungry(0); person.setName("admin"); person.isNeedEat(); } } View Code 执行Main方法后,我们可以看到如下结果: 二、SpringApplication启动分析 2.1、SpringApplication初始化分析 在这里我们先追踪一下SpringApplication.run的方法: /** * Static helper that can be used to run a {@link SpringApplication} from the * specified sources using default settings and user supplied arguments. * @param sources the sources to load * @param args the application arguments (usually passed from a Java main method) * @return the running {@link ApplicationContext} */ public static ConfigurableApplicationContext run(Object[] sources, String[] args) { return new SpringApplication(sources).run(args); } 该方法会创建SpringApplication对象,我们继续看一下关键代码: //...... /** * Create a new {@link SpringApplication} instance. The application context will load * beans from the specified sources (see {@link SpringApplication class-level} * documentation for details. The instance can be customized before calling * {@link #run(String...)}. * @param sources the bean sources * @see #run(Object, String[]) * @see #SpringApplication(ResourceLoader, Object...) */ public SpringApplication(Object... sources) { initialize(sources); } //...... @SuppressWarnings({ "unchecked", "rawtypes" }) private void initialize(Object[] sources) { if (sources != null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } this.webEnvironment = deduceWebEnvironment(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); } 这里大家重点关注一下源代码中getSpringFacoriesInstances方法, ApplicationListener接口,ApplicationContextInitializer接口,这些接口都是通过SpringFactoriesLoader从META-INF/spring.factories文件里加载的 其中getSpringFactoriesInstances的关键代码: private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; } 这里面有一个关键类叫做SpringFactoriesLoader 该类的主要作用是读取META-INF/spring.factories配置文件里配置的引导对象,我们来看一下代码: /* * Copyright 2002-2016 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.core.io.support; import java.io.IOException; import java.lang.reflect.Constructor; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.io.UrlResource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** * General purpose factory loading mechanism for internal use within the framework. * * <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates * factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which * may be present in multiple JAR files in the classpath. The {@code spring.factories} * file must be in {@link Properties} format, where the key is the fully qualified * name of the interface or abstract class, and the value is a comma-separated list of * implementation class names. For example: * * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre> * * where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1} * and {@code MyServiceImpl2} are two implementations. * * @author Arjen Poutsma * @author Juergen Hoeller * @author Sam Brannen * @since 3.2 */ public abstract class SpringFactoriesLoader { private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class); /** * The location to look for factories. * <p>Can be present in multiple JAR files. */ public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; /** * Load and instantiate the factory implementations of the given type from * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader. * <p>The returned factories are sorted in accordance with the {@link AnnotationAwareOrderComparator}. * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames} * to obtain all registered factory names. * @param factoryClass the interface or abstract class representing the factory * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default) * @see #loadFactoryNames * @throws IllegalArgumentException if any factory implementation class cannot * be loaded or if an error occurs while instantiating any factory */ public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) { Assert.notNull(factoryClass, "'factoryClass' must not be null"); ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == 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<T>(factoryNames.size()); for (String factoryName : factoryNames) { result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse)); } AnnotationAwareOrderComparator.sort(result); return result; } /** * Load the fully qualified class names of factory implementations of the * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given * class loader. * @param factoryClass the interface or abstract class representing the factory * @param classLoader the ClassLoader to use for loading resources; can be * {@code null} to use the default * @see #loadFactories * @throws IllegalArgumentException if an error occurs while loading factory names */ public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); String factoryClassNames = properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } @SuppressWarnings("unchecked") 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() + "]"); } Constructor<?> constructor = instanceClass.getDeclaredConstructor(); ReflectionUtils.makeAccessible(constructor); return (T) constructor.newInstance(); } catch (Throwable ex) { throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex); } } } View Code 这里面的ApplicationListener略微特殊,它被定义到META-INF/spring.factories里,该监听器主要监听SpringApplicationEvent事件,SpringApplicationEvent有如下子类: 1. ApplicationStartingEvent 2. ApplicationEnvironmentPreparedEvent 3. ApplicationPreparedEvent 4. ApplicationFailedEvent 5. ApplicationReadyEvent ApplicationContextInitializer该接口 /* * Copyright 2002-2011 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.context; /** * Callback interface for initializing a Spring {@link ConfigurableApplicationContext} * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}. * * <p>Typically used within web applications that require some programmatic initialization * of the application context. For example, registering property sources or activating * profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment() * context's environment}. See {@code ContextLoader} and {@code FrameworkServlet} support * for declaring a "contextInitializerClasses" context-param and init-param, respectively. * * <p>{@code ApplicationContextInitializer} processors are encouraged to detect * whether Spring's {@link org.springframework.core.Ordered Ordered} interface has been * implemented or if the @{@link org.springframework.core.annotation.Order Order} * annotation is present and to sort instances accordingly if so prior to invocation. * * @author Chris Beams * @since 3.1 * @see org.springframework.web.context.ContextLoader#customizeContext * @see org.springframework.web.context.ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM * @see org.springframework.web.servlet.FrameworkServlet#setContextInitializerClasses * @see org.springframework.web.servlet.FrameworkServlet#applyInitializers */ public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> { /** * Initialize the given application context. * @param applicationContext the application to configure */ void initialize(C applicationContext); } View Code 该接口doc文档上描述很清楚了,在调用ConfigurableApplicationContext的refresh()之前进行的初始化操作,比如说:激活profile , 注册PropertySource , FrameworkServlet(DispacherServlet的父类)设置 contextConfigLocation等。 2.2、SpringApplication的run方法分析 这里我贴一下关键代码: /** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Banner printedBanner = printBanner(environment); context = createApplicationContext(); analyzers = new FailureAnalyzers(context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } } 1. 获取SpringApplicationRunListener private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); } 该接口首先从META-INF/spring.factories文件里获取所有配置的SpringApplicationRunner ,那么这个接口时干啥的呢?我们来看一下源代码: /* * Copyright 2012-2016 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; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.support.SpringFactoriesLoader; /** * Listener for the {@link SpringApplication} {@code run} method. * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader} * and should declare a public constructor that accepts a {@link SpringApplication} * instance and a {@code String[]} of arguments. A new * {@link SpringApplicationRunListener} instance will be created for each run. * * @author Phillip Webb * @author Dave Syer */ public interface SpringApplicationRunListener { /** * Called immediately when the run method has first started. Can be used for very * early initialization. */ void starting(); /** * Called once the environment has been prepared, but before the * {@link ApplicationContext} has been created. * @param environment the environment */ void environmentPrepared(ConfigurableEnvironment environment); /** * Called once the {@link ApplicationContext} has been created and prepared, but * before sources have been loaded. * @param context the application context */ void contextPrepared(ConfigurableApplicationContext context); /** * Called once the application context has been loaded but before it has been * refreshed. * @param context the application context */ void contextLoaded(ConfigurableApplicationContext context); /** * Called immediately before the run method finishes. * @param context the application context or null if a failure occurred before the * context was created * @param exception any run exception or null if run completed successfully. */ void finished(ConfigurableApplicationContext context, Throwable exception); } View Code 其实简单点来说就是在SpringBoot启动过程中各个阶段需要做的事情,阶段包括:程序准备启动,准备环境,ApplicationContext准备加载,程序启动完成等等。 其中该接口默认有一个实现类EventPublishingRunListener至关重要大家需要了解一下: /* * 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.context.event; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplicationRunListener; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ApplicationEventMulticaster; import org.springframework.context.event.SimpleApplicationEventMulticaster; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.util.ErrorHandler; /** * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s. * <p> * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired * before the context is actually refreshed. * * @author Phillip Webb * @author Stephane Nicoll */ 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(); for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } } @Override public int getOrder() { return 0; } @Override @SuppressWarnings("deprecation") public void starting() { this.initialMulticaster .multicastEvent(new ApplicationStartedEvent(this.application, this.args)); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( this.application, this.args, environment)); } @Override public void contextPrepared(ConfigurableApplicationContext context) { } @Override public void contextLoaded(ConfigurableApplicationContext context) { for (ApplicationListener<?> listener : this.application.getListeners()) { if (listener instanceof ApplicationContextAware) { ((ApplicationContextAware) listener).setApplicationContext(context); } context.addApplicationListener(listener); } this.initialMulticaster.multicastEvent( new ApplicationPreparedEvent(this.application, this.args, context)); } @Override public void finished(ConfigurableApplicationContext context, Throwable exception) { SpringApplicationEvent event = getFinishedEvent(context, exception); if (context != null && context.isActive()) { // Listeners have been registered to the application context so we should // use it at this point if we can context.publishEvent(event); } else { // An inactive context may not have a multicaster so we use our multicaster to // call all of the context's listeners instead if (context instanceof AbstractApplicationContext) { for (ApplicationListener<?> listener : ((AbstractApplicationContext) context) .getApplicationListeners()) { this.initialMulticaster.addApplicationListener(listener); } } if (event instanceof ApplicationFailedEvent) { this.initialMulticaster.setErrorHandler(new LoggingErrorHandler()); } this.initialMulticaster.multicastEvent(event); } } private SpringApplicationEvent getFinishedEvent( ConfigurableApplicationContext context, Throwable exception) { if (exception != null) { return new ApplicationFailedEvent(this.application, this.args, context, exception); } return new ApplicationReadyEvent(this.application, this.args, context); } private static class LoggingErrorHandler implements ErrorHandler { private static Log logger = LogFactory.getLog(EventPublishingRunListener.class); @Override public void handleError(Throwable throwable) { logger.warn("Error calling ApplicationEventListener", throwable); } } } View Code 这个类里面有一个SimpleApplicationEventMulticaster的属性,根据前面分析,该属性就是执行关于SpringApplicationEvent的事件监听方法的。该类最主要作用就是通知各个阶段的listener处理对应阶段的事件 2、调用所有的SpringApplicationRunListenner的start方法 我们可以看一下SpringApplicationRunListeners类里的方法: public void starting() { for (SpringApplicationRunListener listener : this.listeners) { listener.starting(); } } 3、执行prepareEnvironment方法 public void environmentPrepared(ConfigurableEnvironment environment) { for (SpringApplicationRunListener listener : this.listeners) { listener.environmentPrepared(environment); } } 4、根据当前的Environment打印Banner 5、创建ConfigurableApplicationContext对象与FailureAnalyzers 在这里会根据this.webEnvironment的属性值来确定创建的ApplicationContext对象: /** * Strategy method used to create the {@link ApplicationContext}. By default this * method will respect any explicitly set application context or application context * class before falling back to a suitable default. * @return the application context (not yet refreshed) * @see #setApplicationContextClass(Class) */ protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { contextClass = Class.forName(this.webEnvironment ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); } 如果是web环境那就创建org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext ,否则就创建org.springframework.context.annotation.AnnotationConfigApplicationContext 6、调用prepareContext方法 public void contextPrepared(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.contextPrepared(context); } } 7、调用 refreshContext方法 该方法最终会执行AbstractApplicationContext的refresh()方法,我在这里贴一下源代码 @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } } View Code 在这个方法里会初始化BeanFactory 初始化BeanFactoryPostProcessor 注册BeanPostProcessor 初始化MessageSource 注册事件监听器等操作。建议大家深入了解Spring的IOC加载原理 8、执行afterRefresh() /** * Called after the context has been refreshed. * @param context the application context * @param args the application arguments */ protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { callRunners(context, args); } private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<Object>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet<Object>(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } } private void callRunner(ApplicationRunner runner, ApplicationArguments args) { try { (runner).run(args); } catch (Exception ex) { throw new IllegalStateException("Failed to execute ApplicationRunner", ex); }} 该方法会从IOC容器里找到ApplicationRunner或者CommandLineRunner并执行其run方法,当我们需要在SpringBoot程序启动时处理我们自己的逻辑,那么就可以实现上述接口 9、调用 listeners.finished方法 public void finished(ConfigurableApplicationContext context, Throwable exception) { for (SpringApplicationRunListener listener : this.listeners) { callFinishedListener(listener, context, exception); } } private void callFinishedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context, Throwable exception) { try { listener.finished(context, exception); } catch (Throwable ex) { if (exception == null) { ReflectionUtils.rethrowRuntimeException(ex); } if (this.log.isDebugEnabled()) { this.log.error("Error handling failed", ex); } else { String message = ex.getMessage(); message = (message == null ? "no error message" : message); this.log.warn("Error handling failed (" + message + ")"); } } } 10、启动时的异常处理 private void handleRunFailure(ConfigurableApplicationContext context, SpringApplicationRunListeners listeners, FailureAnalyzers analyzers, Throwable exception) { try { try { handleExitCode(context, exception); listeners.finished(context, exception); } finally { reportFailure(analyzers, exception); if (context != null) { context.close(); } } } catch (Exception ex) { logger.warn("Unable to close ApplicationContext", ex); } ReflectionUtils.rethrowRuntimeException(exception); } 我们可以看到在SpringApplicationRunnerListener的作用至关重要,几乎每做一件事情都涉及到此接口的方法 ,另外 EventPublishingRunListener会在各个阶段通知各个listener处理启动周期内各个阶段性事件 三、测试示例 通过在META-INF/spring.factories里配置引导类,来验证一下我们上述分析的启动过程 1、创建MyBootStrapApplicationListener示例: package com.hzgj.lyrk.member.applicationlistener; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.context.ApplicationListener; /** * META-INF/spring.factories 配置的listener测试 * @author chen.nie * @date 2018/4/26 **/ public class MyBootStrapApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> { @Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { System.out.println("BootStrapApplicationListener"); } } View Code 2、创建MyCommandRunner package com.hzgj.lyrk.member.commandlinerunner; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; /** * commandLineRunner测试 * * @author chen.nie * @date 2018/4/26 **/ @Component public class MyCommandRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("MyCommandRunner execute ....."); } } View Code 3、在spring.factories配置ApplicationListener org.springframework.context.ApplicationListener=\ com.hzgj.lyrk.member.applicationlistener.MyBootStrapApplicationListener 当我们启动SpringBoot项目时,可以发现如下结果: 我们可以发现我们配置的ApplicationListener在最开始就会执行,而CommandLineRunner在最后才执行 四、SpringBoot启动总结 1. SpringBoot启动时SpringApplicationRunListener接口的相关方法至关重要,它定义了启动时的各个“时间点”。 2. SpringBoot可以从spring.factoies文件里读取配置的ApplicationListener 3. META-INF文件夹下的spring.factoies文件是SpringBoot启动的核心文件,SpringFatoriesLoader会读取该文件夹下的相关配置作为引导 4. SpringBoot启动时利用了事件机制,来发送启动时各个周期阶段的事件
当我们使用@DiscoveryClient注解的时候,会不会有如下疑问:它为什么会进行注册服务的操作,它不是应该用作服务发现的吗?下面我们就来深入的探究一下其源码。 一、Springframework的LifeCycle接口 要搞明白这个问题我们需要了解一下这个重要的接口: /* * Copyright 2002-2015 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.context; /** * A common interface defining methods for start/stop lifecycle control. * The typical use case for this is to control asynchronous processing. * <b>NOTE: This interface does not imply specific auto-startup semantics. * Consider implementing {@link SmartLifecycle} for that purpose.</b> * * <p>Can be implemented by both components (typically a Spring bean defined in a * Spring context) and containers (typically a Spring {@link ApplicationContext} * itself). Containers will propagate start/stop signals to all components that * apply within each container, e.g. for a stop/restart scenario at runtime. * * <p>Can be used for direct invocations or for management operations via JMX. * In the latter case, the {@link org.springframework.jmx.export.MBeanExporter} * will typically be defined with an * {@link org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler}, * restricting the visibility of activity-controlled components to the Lifecycle * interface. * * <p>Note that the Lifecycle interface is only supported on <b>top-level singleton * beans</b>. On any other component, the Lifecycle interface will remain undetected * and hence ignored. Also, note that the extended {@link SmartLifecycle} interface * provides integration with the application context's startup and shutdown phases. * * @author Juergen Hoeller * @since 2.0 * @see SmartLifecycle * @see ConfigurableApplicationContext * @see org.springframework.jms.listener.AbstractMessageListenerContainer * @see org.springframework.scheduling.quartz.SchedulerFactoryBean */ public interface Lifecycle { /** * Start this component. * <p>Should not throw an exception if the component is already running. * <p>In the case of a container, this will propagate the start signal to all * components that apply. * @see SmartLifecycle#isAutoStartup() */ void start(); /** * Stop this component, typically in a synchronous fashion, such that the component is * fully stopped upon return of this method. Consider implementing {@link SmartLifecycle} * and its {@code stop(Runnable)} variant when asynchronous stop behavior is necessary. * <p>Note that this stop notification is not guaranteed to come before destruction: On * regular shutdown, {@code Lifecycle} beans will first receive a stop notification before * the general destruction callbacks are being propagated; however, on hot refresh during a * context's lifetime or on aborted refresh attempts, only destroy methods will be called. * <p>Should not throw an exception if the component isn't started yet. * <p>In the case of a container, this will propagate the stop signal to all components * that apply. * @see SmartLifecycle#stop(Runnable) * @see org.springframework.beans.factory.DisposableBean#destroy() */ void stop(); /** * Check whether this component is currently running. * <p>In the case of a container, this will return {@code true} only if <i>all</i> * components that apply are currently running. * @return whether the component is currently running */ boolean isRunning(); } View Code 该接口定义启动/停止生命周期控制方法,当spring ioc容器启动或停止时将发送一个启动或者停止的信号通知到各个组件,因此我们可以在对应的方法里做我们想要的事情。我们可以通过类图发现我们常用的ClasspathXmlApplicationContext类就实现了该接口 下面我们来简单演示一下案例,创建类MyLifeCycle: package org.hzgj.spring.study.context; import org.springframework.context.SmartLifecycle; public class MyLifeCycle implements SmartLifecycle { @Override public void start() { System.out.println("MyLifeCycle start ...."); } @Override public void stop() { System.out.println("MyLifeCycle stop ....."); } @Override public boolean isRunning() { return false; } @Override public boolean isAutoStartup() { return true; } @Override public void stop(Runnable callback) { } @Override public int getPhase() { System.out.println("phase"); return 10; } } View Code 在这里我们继承SmartLifeCycle该接口继承了LifeCycle, isRunning方法用于检测当前的组件是否处在运行状态,注意只有当isRunning返回值为false才可以运行 我们把MyLifeCycle配置到spring配置文件里,通过ClassPathXmlApplicationContext运行 会得到如下结果: 另外在这里的getPhase方法,这个是定义阶段值(可以理解为优先级,值越小对应的LifeCycle越先执行) 二、DiscoveryClient源码探究 @EnableDiscoveyClient /* * Copyright 2013-2015 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.cloud.client.discovery; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; /** * Annotation to enable a DiscoveryClient implementation. * @author Spencer Gibb */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(EnableDiscoveryClientImportSelector.class) public @interface EnableDiscoveryClient { /** * If true, the ServiceRegistry will automatically register the local server. */ boolean autoRegister() default true; } View Code 请注意 @Import(EnableDiscoveryClientImportSelector.class) 我们可以参考一下这个类: /* * Copyright 2013-2015 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.cloud.client.discovery; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.cloud.commons.util.SpringFactoryImportSelector; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.Order; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.type.AnnotationMetadata; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; /** * @author Spencer Gibb */ @Order(Ordered.LOWEST_PRECEDENCE - 100) public class EnableDiscoveryClientImportSelector extends SpringFactoryImportSelector<EnableDiscoveryClient> { @Override public String[] selectImports(AnnotationMetadata metadata) { String[] imports = super.selectImports(metadata); AnnotationAttributes attributes = AnnotationAttributes.fromMap( metadata.getAnnotationAttributes(getAnnotationClass().getName(), true)); boolean autoRegister = attributes.getBoolean("autoRegister"); if (autoRegister) { List<String> importsList = new ArrayList<>(Arrays.asList(imports)); importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration"); imports = importsList.toArray(new String[0]); } else { Environment env = getEnvironment(); if(ConfigurableEnvironment.class.isInstance(env)) { ConfigurableEnvironment configEnv = (ConfigurableEnvironment)env; LinkedHashMap<String, Object> map = new LinkedHashMap<>(); map.put("spring.cloud.service-registry.auto-registration.enabled", false); MapPropertySource propertySource = new MapPropertySource( "springCloudDiscoveryClient", map); configEnv.getPropertySources().addLast(propertySource); } } return imports; } @Override protected boolean isEnabled() { return new RelaxedPropertyResolver(getEnvironment()).getProperty( "spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE); } @Override protected boolean hasDefaultFactory() { return true; } } View Code 这个类重写的方法来自于接口 ImportSelector,我们可以根据 if(autoRegister)下的代码追踪到类:org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration,我们来看一下结构图: 我们可以得知这个类实现了Lifecycle接口,那么我们看一看start方法,此方法在它的父类AbstractDiscoveryLifecycle里: /* * Copyright 2013-2015 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.cloud.client.discovery; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.PreDestroy; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; import org.springframework.cloud.client.serviceregistry.ServiceRegistry; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.core.env.Environment; /** * Lifecycle methods that may be useful and common to various DiscoveryClient implementations. * * @deprecated use {@link org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration} instead. This class will be removed in the next release train. * * @author Spencer Gibb */ @Deprecated public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle, ApplicationContextAware, ApplicationListener<EmbeddedServletContainerInitializedEvent> { private static final Log logger = LogFactory.getLog(AbstractDiscoveryLifecycle.class); private boolean autoStartup = true; private AtomicBoolean running = new AtomicBoolean(false); private int order = 0; private ApplicationContext context; private Environment environment; private AtomicInteger port = new AtomicInteger(0); protected ApplicationContext getContext() { return context; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; this.environment = this.context.getEnvironment(); } @Deprecated protected Environment getEnvironment() { return environment; } @Deprecated protected AtomicInteger getPort() { return port; } @Override public boolean isAutoStartup() { return this.autoStartup; } @Override public void stop(Runnable callback) { try { stop(); } catch (Exception e) { logger.error("A problem occurred attempting to stop discovery lifecycle", e); } callback.run(); } @Override public void start() { if (!isEnabled()) { if (logger.isDebugEnabled()) { logger.debug("Discovery Lifecycle disabled. Not starting"); } return; } // only set the port if the nonSecurePort is 0 and this.port != 0 if (this.port.get() != 0 && getConfiguredPort() == 0) { setConfiguredPort(this.port.get()); } // only initialize if nonSecurePort is greater than 0 and it isn't already running // because of containerPortInitializer below if (!this.running.get() && getConfiguredPort() > 0) { register(); if (shouldRegisterManagement()) { registerManagement(); } this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration())); this.running.compareAndSet(false, true); } } @Deprecated protected abstract int getConfiguredPort(); @Deprecated protected abstract void setConfiguredPort(int port); /** * @return if the management service should be registered with the {@link ServiceRegistry} */ protected boolean shouldRegisterManagement() { return getManagementPort() != null && ManagementServerPortUtils.isDifferent(this.context); } /** * @return the object used to configure the registration */ @Deprecated protected abstract Object getConfiguration(); /** * Register the local service with the DiscoveryClient */ protected abstract void register(); /** * Register the local management service with the DiscoveryClient */ protected void registerManagement() { } /** * De-register the local service with the DiscoveryClient */ protected abstract void deregister(); /** * De-register the local management service with the DiscoveryClient */ protected void deregisterManagement() { } /** * @return true, if the {@link DiscoveryLifecycle} is enabled */ protected abstract boolean isEnabled(); /** * @return the serviceId of the Management Service */ @Deprecated protected String getManagementServiceId() { // TODO: configurable management suffix return this.context.getId() + ":management"; } /** * @return the service name of the Management Service */ @Deprecated protected String getManagementServiceName() { // TODO: configurable management suffix return getAppName() + ":management"; } /** * @return the management server port */ @Deprecated protected Integer getManagementPort() { return ManagementServerPortUtils.getPort(this.context); } /** * @return the app name, currently the spring.application.name property */ @Deprecated protected String getAppName() { return this.environment.getProperty("spring.application.name", "application"); } @Override public void stop() { if (this.running.compareAndSet(true, false) && isEnabled()) { deregister(); if (shouldRegisterManagement()) { deregisterManagement(); } } } @PreDestroy public void destroy() { stop(); } @Override public boolean isRunning() { return this.running.get(); } protected AtomicBoolean getRunning() { return running; } @Override public int getOrder() { return this.order; } @Override public int getPhase() { return 0; } @Override @Deprecated public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) { // TODO: take SSL into account // Don't register the management port as THE port if (!"management".equals(event.getApplicationContext().getNamespace())) { this.port.compareAndSet(0, event.getEmbeddedServletContainer().getPort()); this.start(); } } } View Code 注意在start方法里有一段这个代码: if (!this.running.get() && getConfiguredPort() > 0) { register(); if (shouldRegisterManagement()) { registerManagement(); } this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration())); this.running.compareAndSet(false, true); } 请注意register() 这个方法是本类里的抽象方法。那么我们回过头看一下AbstractAutoServiceRegistration类里的代码,我这里只贴出关键部分: //..... protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry, AutoServiceRegistrationProperties properties) { this.serviceRegistry = serviceRegistry; this.properties = properties; } //...... /** * Register the local service with the {@link ServiceRegistry} */ @Override protected void register() { this.serviceRegistry.register(getRegistration()); } View Code 我们可以发现在构造函数里传了一个ServiceRegistry类型,这个接口是SpringCloud给我们提供用于服务注册的接口。在这里EurekaServiceRegistry就是实现了此接口: /* * Copyright 2013-2016 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.cloud.netflix.eureka.serviceregistry; import java.util.HashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.client.serviceregistry.ServiceRegistry; import com.netflix.appinfo.InstanceInfo; /** * @author Spencer Gibb */ public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration> { private static final Log log = LogFactory.getLog(EurekaServiceRegistry.class); @Override public void register(EurekaRegistration reg) { maybeInitializeClient(reg); if (log.isInfoEnabled()) { log.info("Registering application " + reg.getInstanceConfig().getAppname() + " with eureka with status " + reg.getInstanceConfig().getInitialStatus()); } reg.getApplicationInfoManager() .setInstanceStatus(reg.getInstanceConfig().getInitialStatus()); if (reg.getHealthCheckHandler() != null) { reg.getEurekaClient().registerHealthCheck(reg.getHealthCheckHandler()); } } private void maybeInitializeClient(EurekaRegistration reg) { // force initialization of possibly scoped proxies reg.getApplicationInfoManager().getInfo(); reg.getEurekaClient().getApplications(); } @Override public void deregister(EurekaRegistration reg) { if (reg.getApplicationInfoManager().getInfo() != null) { if (log.isInfoEnabled()) { log.info("Unregistering application " + reg.getInstanceConfig().getAppname() + " with eureka with status DOWN"); } reg.getApplicationInfoManager().setInstanceStatus(InstanceInfo.InstanceStatus.DOWN); //shutdown of eureka client should happen with EurekaRegistration.close() //auto registration will create a bean which will be properly disposed //manual registrations will need to call close() } } @Override public void setStatus(EurekaRegistration registration, String status) { InstanceInfo info = registration.getApplicationInfoManager().getInfo(); //TODO: howto deal with delete properly? if ("CANCEL_OVERRIDE".equalsIgnoreCase(status)) { registration.getEurekaClient().cancelOverrideStatus(info); return; } //TODO: howto deal with status types across discovery systems? InstanceInfo.InstanceStatus newStatus = InstanceInfo.InstanceStatus.toEnum(status); registration.getEurekaClient().setStatus(newStatus, info); } @Override public Object getStatus(EurekaRegistration registration) { HashMap<String, Object> status = new HashMap<>(); InstanceInfo info = registration.getApplicationInfoManager().getInfo(); status.put("status", info.getStatus().toString()); status.put("overriddenStatus", info.getOverriddenStatus().toString()); return status; } public void close() { } } View Code 那么至此我们可以总结如下几点: 1、使用@DiscoveryClient注册服务是利用了LifeCycle机制,在容器启动时会执行ServiceRegistry的register()方法。 2、使用@DiscoveryClient要比@EnableEurekaClient与@EnableEurekaServer更灵活,因为它屏蔽了对服务注册的实现,我们甚至可以自定义注册中心。 3、它还会自动去寻找DiscoveryClient接口的实现用作服务发现 三、Discoveryclient实战之redis注册中心 下面我们实现一个基于redis为注册中心的需求,来理解一下Discoveryclient。顺便理解一下Springcloud重要的接口:ServiceRegistry,ServiceInstance,再此之前我们先添加对redis的支持: compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis' 1、实现Registration接口 package com.hzgj.lyrk.member; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.stereotype.Component; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.URI; import java.util.Enumeration; import java.util.Map; @Component public class RedisRegistration implements Registration { @Value("${server.port}") private Integer port; @Value("${spring.application.name}") private String applicationName; private String host; public void setHost(String host) { this.host = host; } public void setPort(Integer port) { this.port = port; } public void setApplicationName(String applicationName) { this.applicationName = applicationName; } @Override public String getServiceId() { return applicationName + ":" + getHost() + ":" + getPort(); } @Override public String getHost() { try { if (host == null) return getLocalHostLANAddress().getHostAddress(); else return host; } catch (Exception e) { e.printStackTrace(); } return null; } @Override public int getPort() { return port; } @Override public boolean isSecure() { return false; } @Override public URI getUri() { return null; } @Override public Map<String, String> getMetadata() { return null; } public String getServiceName() { return this.applicationName; } public InetAddress getLocalHostLANAddress() throws Exception { try { InetAddress candidateAddress = null; // 遍历所有的网络接口 for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) { NetworkInterface iface = (NetworkInterface) ifaces.nextElement(); // 在所有的接口下再遍历IP for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) { InetAddress inetAddr = (InetAddress) inetAddrs.nextElement(); if (!inetAddr.isLoopbackAddress()) {// 排除loopback类型地址 if (inetAddr.isSiteLocalAddress()) { // 如果是site-local地址,就是它了 return inetAddr; } else if (candidateAddress == null) { // site-local类型的地址未被发现,先记录候选地址 candidateAddress = inetAddr; } } } } if (candidateAddress != null) { return candidateAddress; } // 如果没有发现 non-loopback地址.只能用最次选的方案 InetAddress jdkSuppliedAddress = InetAddress.getLocalHost(); return jdkSuppliedAddress; } catch (Exception e) { e.printStackTrace(); } return null; } } View Code 该接口继承了ServiceIntance,那么此接口最主要作用就是定义了一个服务实例的规范,比如说它的serviceId是什么,端口号是什么等 2、实现ServiceRegistry的接口 package com.hzgj.lyrk.member; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.serviceregistry.ServiceRegistry; import org.springframework.data.redis.core.StringRedisTemplate; public class RedisServiceRegistry implements ServiceRegistry<RedisRegistration> { @Autowired private StringRedisTemplate redisTemplate; @Override public void register(RedisRegistration registration) { String serviceId = registration.getServiceId(); redisTemplate.opsForList().leftPush(serviceId, registration.getHost() + ":" + registration.getPort()); } @Override public void deregister(RedisRegistration registration) { redisTemplate.opsForList().remove(registration.getServiceId(), 1, registration.getHost() + ":" + registration.getPort()); } @Override public void close() { //redisTemplate.d System.out.println("closed ..."); } @Override public void setStatus(RedisRegistration registration, String status) { } @Override public <T> T getStatus(RedisRegistration registration) { return null; } } View Code 该接口主要作用是定义如何进行服务注册 ,服务注销,设置与获取服务状态等操作 3、继承 AbstractAutoServiceRegistration抽象类 package com.hzgj.lyrk.member; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; import org.springframework.cloud.client.serviceregistry.ServiceRegistry; public class RedisAutoServiceRegistration extends AbstractAutoServiceRegistration<RedisRegistration> { @Autowired private RedisRegistration redisRegistration; protected RedisAutoServiceRegistration(ServiceRegistry<RedisRegistration> serviceRegistry, AutoServiceRegistrationProperties properties) { super(serviceRegistry, properties); // serviceRegistry.register(getRegistration()); } @Override protected int getConfiguredPort() { return redisRegistration.getPort(); } @Override protected void setConfiguredPort(int port) { } @Override protected Object getConfiguration() { return null; } @Override protected boolean isEnabled() { return true; } @Override protected RedisRegistration getRegistration() { return redisRegistration; } @Override protected RedisRegistration getManagementRegistration() { return null; } } View Code 4、定义DiscoveryClient的实现类RedisDiscoveryClient package com.hzgj.lyrk.member; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.data.redis.core.StringRedisTemplate; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; public class RedisDiscoveryClient implements DiscoveryClient { @Autowired private StringRedisTemplate redisTemplate; @Override public String description() { return "redis注册中心的服务发现"; } @Override public ServiceInstance getLocalServiceInstance() { return null; } @Override public List<ServiceInstance> getInstances(String serviceId) { return redisTemplate.opsForList().range(serviceId, 0, -1). parallelStream().map((Function<String, ServiceInstance>) s -> { RedisRegistration redisRegistration = new RedisRegistration(); redisRegistration.setApplicationName(serviceId); String hostName = StringUtils.split(s, ":")[0]; String port = StringUtils.split(s, ":")[1]; redisRegistration.setHost(hostName); redisRegistration.setPort(Integer.parseInt(port)); //redisRegistration return redisRegistration; }).collect(Collectors.toList()); } @Override public List<String> getServices() { List<String> list = new ArrayList<>(); list.addAll(redisTemplate.keys("*")); return list; } } View Code 该类主要是针对于redis注册中心的服务发现 5、定义自动装配的类用以创建对应的bean package com.hzgj.lyrk.member; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @Configuration @EnableConfigurationProperties(RedisConfig.class) @ConditionalOnProperty(value = "spring.redis.registry.enabled", matchIfMissing = true) public class RedisRegistryAutoConfiguration { @Bean RedisServiceRegistry redisServiceRegistry(RedisConfig redisConfig) { System.out.println(redisConfig.getHost()); return new RedisServiceRegistry(); } @Bean RedisAutoServiceRegistration redisAutoServiceRegistration(RedisServiceRegistry redisServiceRegistry) { return new RedisAutoServiceRegistration(redisServiceRegistry, new AutoServiceRegistrationProperties()); } @Bean @Primary RedisDiscoveryClient redisDiscoveryClient() { return new RedisDiscoveryClient(); } } View Code 6、定义启动类 package com.hzgj.lyrk.member; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration; import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; @EnableDiscoveryClient @SpringBootApplication(exclude = {SimpleDiscoveryClientAutoConfiguration.class, CompositeDiscoveryClientAutoConfiguration.class}) public class MemberApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(MemberApplication.class, args); DiscoveryClient discoveryClient = applicationContext.getBean(DiscoveryClient.class); discoveryClient.getServices().forEach(action -> { System.out.println(action); }); } } View Code 这里在SpringbootApplication注解里排除DiscoveryClient的默认装配。 当我们启动成功后可以发现,控制台已经输出对应的服务名称与地址: 我们再次通过gradle打包生成jar文件并运行: java -jar member-server-0.0.1-SNAPSHOT.jar --server.port=8800 我们可以看到redis里已经缓存的有服务注册的值了:
截止到目前JDK的版本已经更新到10了,虽然java9的生命周期才半年,但是我认为这个版本带来的变革是不可磨灭的,它是第一次深层次的针对架构以及依赖上的革新。下面我们就来学习一下。 一、模块化项目构建 其实模块化本身不难理解,我们先前使用maven或者gradle就构建过多模块的项目。那么我们在java9里依然可以照猫画虎来构建一下我们的模块化项目工程。如图所示: 注意以下几点: 1.请在每个模块下创建一个叫做module-info.java的模块化描述文件 2.在idea里配置一下模块依赖,在这里我们的project.portal模块如果依赖student.service模块,我们可以这么来设置: 找到这个选项图标:,然后这样设置来添加依赖: 如果需要设置其他项目的依赖项,也请按照此方式设置。 二、实现步骤 2.1、Student.Service模块 2.1.1、编写StudentService的module-info.java 示例代码: import com.bdqn.lyrk.student.service.SecondStudentService; import com.bdqn.lyrk.student.service.api.IStudentService; /** * 模块化描述类,统一建立在各个模块的源文件根目录 名字为:module-info.java * 模块化常见的语法结构: * * import xxxx.xxxx; * .... * * [open] module 模块名 { * requires [static|transitive] 模块名; * exports 包名 [to 模块名] * providers 接口名 with [接口实现类,....] * uses 接口名 * * } * * * @author chen.nie * @date 2018/4/18 **/ module student.service { exports com.bdqn.lyrk.student.service.api; provides IStudentService with SecondStudentService; } View Code 2.1.2、定义接口 package com.bdqn.lyrk.student.service.api; public interface IStudentService { void study(); } View Code 2.1.3、定义实现类 package com.bdqn.lyrk.student.service; import com.bdqn.lyrk.student.service.api.IStudentService; public class SecondStudentService implements IStudentService { @Override public void study() { System.out.println("second study"); } } View Code 2.2、project.portal 模块 2.2.1、编写module-info.java import com.bdqn.lyrk.student.service.api.IStudentService; module project.portal { uses IStudentService; requires transitive student.service; } View Code 2.2.2、编写Main方法 package com.bdqn.lyrk.portal; import com.bdqn.lyrk.student.service.api.IStudentService; import java.util.ServiceLoader; public class Main { public static void main(String[] args) { ServiceLoader<IStudentService> studentServices = ServiceLoader.load(IStudentService.class); studentServices.findFirst().get().study(); } } View Code 我们运行后既可以拿到对应的结果: 三、module-info.java文件常见配置 3.1、关于open关键字 open:该关键字如果加载模块上,那么通过exports的导出包下的类可见度是最高的,我们可以通过反射的方式来创建对对象和访问属性。 3.2、关于exports关键字 当我们定义好模块后,我们可以指定该模块下的哪些包可以被其他模块所访问,exports关键字就起到该作用。我们也可以配合to来指定哪些模块可以访问该包的内容 语法 exports 包名 [to] 模块名 exports <package>; exports <package> to <module1>, <module2>...; 3.3、opens关键字 opens类似于open,如果open关键字加在module上,那么模块里默认导出的exports包都为open形式的 module N { exports com.jdojo.claim.model; opens com.jdojo.claim.model; } 3.4、requires关键字 该关键字声明当前模块与另一个模块的依赖关系。有点类似于maven中的dependecies。 requires <module>; requires transitive <module>; requires static <module>; requires transitive static <module>; require语句中也可以加静态修饰符,这样的话表示在编译时的依赖是强制的,但在运行时是可选的。require语句中的transitive修饰符会导致依赖于当前模块的其他模块具有隐式依赖性,请看下图: 在这里我们可以看看java.se模块下的module-info.class文件: /* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ /** * Defines the core Java SE API. * <P> * The modules defining the CORBA and Java EE APIs are not required by * this module, but they are required by the * <a href="java.se.ee-summary.html">{@code java.se.ee}</a> module. * * <dl> * <dt class="simpleTagLabel" style="font-family:'DejaVu Sans', Arial, Helvetica, sans serif">Optional for the Java SE Platform:</dt> * <dd> * <a href="../specs/jni/index.html">Java Native Interface (JNI)</a><br> * <a href="../specs/jvmti.html">Java Virtual Machine Tool Interface (JVM TI)</a><br> * <a href="../specs/jdwp/jdwp-spec.html">Java Debug Wire Protocol (JDWP)</a><br> * </dd> * </dl> * * @moduleGraph * @since 9 */ module java.se { requires transitive java.compiler; requires transitive java.datatransfer; requires transitive java.desktop; requires transitive java.instrument; requires transitive java.logging; requires transitive java.management; requires transitive java.management.rmi; requires transitive java.naming; requires transitive java.prefs; requires transitive java.rmi; requires transitive java.scripting; requires transitive java.security.jgss; requires transitive java.security.sasl; requires transitive java.sql; requires transitive java.sql.rowset; requires transitive java.xml; requires transitive java.xml.crypto; } View Code 此时我们只要requires java.se,那么该模块下的所有依赖我们就间接的引入了 3.5、uses与provider关键字 Java允许使用服务提供者和服务使用者分离的服务提供者机制。 JDK 9允许使用语句(uses statement)和提供语句(provides statement)实现其服务。使用语句可以指定服务接口的名字,当前模块就会发现它,使用 java.util.ServiceLoader类进行加载。代码请参考前面的例子,注意:provider提供的类必须在同一个模块下,当前不能引用其他模块的实现,比如说:前面的例子StudentServiceImpl只能存在student.service模块下,student.service模块provider其他的模块下的接口实现是不允许的。
一、Filter中统一异常处理 其实在SpringCloud的Edgware SR2版本中对于ZuulFilter中的错误有统一的处理,但是在实际开发当中对于错误的响应方式,我想每个团队都有自己的处理规范。那么如何做到自定义的异常处理呢? 我们可以先参考一下SpringCloud提供的SendErrorFilter: /* * Copyright 2013-2015 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.cloud.netflix.zuul.filters.post; import javax.servlet.RequestDispatcher; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ERROR_TYPE; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SEND_ERROR_FILTER_ORDER; /** * Error {@link ZuulFilter} that forwards to /error (by default) if {@link RequestContext#getThrowable()} is not null. * * @author Spencer Gibb */ //TODO: move to error package in Edgware public class SendErrorFilter extends ZuulFilter { private static final Log log = LogFactory.getLog(SendErrorFilter.class); protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran"; @Value("${error.path:/error}") private String errorPath; @Override public String filterType() { return ERROR_TYPE; } @Override public int filterOrder() { return SEND_ERROR_FILTER_ORDER; } @Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); // only forward to errorPath if it hasn't been forwarded to already return ctx.getThrowable() != null && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false); } @Override public Object run() { try { RequestContext ctx = RequestContext.getCurrentContext(); ZuulException exception = findZuulException(ctx.getThrowable()); HttpServletRequest request = ctx.getRequest(); request.setAttribute("javax.servlet.error.status_code", exception.nStatusCode); log.warn("Error during filtering", exception); request.setAttribute("javax.servlet.error.exception", exception); if (StringUtils.hasText(exception.errorCause)) { request.setAttribute("javax.servlet.error.message", exception.errorCause); } RequestDispatcher dispatcher = request.getRequestDispatcher( this.errorPath); if (dispatcher != null) { ctx.set(SEND_ERROR_FILTER_RAN, true); if (!ctx.getResponse().isCommitted()) { ctx.setResponseStatusCode(exception.nStatusCode); dispatcher.forward(request, ctx.getResponse()); } } } catch (Exception ex) { ReflectionUtils.rethrowRuntimeException(ex); } return null; } ZuulException findZuulException(Throwable throwable) { if (throwable.getCause() instanceof ZuulRuntimeException) { // this was a failure initiated by one of the local filters return (ZuulException) throwable.getCause().getCause(); } if (throwable.getCause() instanceof ZuulException) { // wrapped zuul exception return (ZuulException) throwable.getCause(); } if (throwable instanceof ZuulException) { // exception thrown by zuul lifecycle return (ZuulException) throwable; } // fallback, should never get here return new ZuulException(throwable, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null); } public void setErrorPath(String errorPath) { this.errorPath = errorPath; } } View Code 在这里我们可以找到几个关键点: 1)在上述代码中,我们可以发现filter已经将相关的错误信息放到request当中了: request.setAttribute("javax.servlet.error.status_code", exception.nStatusCode); request.setAttribute("javax.servlet.error.exception", exception); request.setAttribute("javax.servlet.error.message", exception.errorCause); 2)错误处理完毕后,会转发到 xxx/error的地址来处理 那么我们可以来做个试验,我们在gateway-service项目模块里,创建一个会抛出异常的filter: package com.hzgj.lyrk.springcloud.gateway.server.filter; import com.netflix.zuul.ZuulFilter; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component @Slf4j public class MyZuulFilter extends ZuulFilter { @Override public String filterType() { return "post"; } @Override public int filterOrder() { return 9; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { log.info("run error test ..."); throw new RuntimeException(); // return null; } } View Code 紧接着我们定义一个控制器,来做错误处理: package com.hzgj.lyrk.springcloud.gateway.server.filter; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController public class ErrorHandler { @GetMapping(value = "/error") public ResponseEntity<ErrorBean> error(HttpServletRequest request) { String message = request.getAttribute("javax.servlet.error.message").toString(); ErrorBean errorBean = new ErrorBean(); errorBean.setMessage(message); errorBean.setReason("程序出错"); return new ResponseEntity<>(errorBean, HttpStatus.BAD_GATEWAY); } private static class ErrorBean { private String message; private String reason; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getReason() { return reason; } public void setReason(String reason) { this.reason = reason; } } } View Code 启动项目后,我们通过网关访问一下试试: 二、关于zuul回退的问题 1、关于zuul的超时问题: 这个问题网上有很多解决方案,但是我还要贴一下源代码,请关注这个类 AbstractRibbonCommand,在这个类里集成了hystrix与ribbon。 /* * Copyright 2013-2016 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.cloud.netflix.zuul.filters.route.support; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration; import org.springframework.cloud.netflix.ribbon.RibbonHttpResponse; import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient; import org.springframework.cloud.netflix.ribbon.support.ContextAwareRequest; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; import org.springframework.http.client.ClientHttpResponse; import com.netflix.client.AbstractLoadBalancerAwareClient; import com.netflix.client.ClientRequest; import com.netflix.client.config.DefaultClientConfigImpl; import com.netflix.client.config.IClientConfig; import com.netflix.client.config.IClientConfigKey; import com.netflix.client.http.HttpResponse; import com.netflix.config.DynamicIntProperty; import com.netflix.config.DynamicPropertyFactory; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandGroupKey; import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixCommandProperties; import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; import com.netflix.hystrix.HystrixThreadPoolKey; import com.netflix.zuul.constants.ZuulConstants; import com.netflix.zuul.context.RequestContext; /** * @author Spencer Gibb */ public abstract class AbstractRibbonCommand<LBC extends AbstractLoadBalancerAwareClient<RQ, RS>, RQ extends ClientRequest, RS extends HttpResponse> extends HystrixCommand<ClientHttpResponse> implements RibbonCommand { private static final Log LOGGER = LogFactory.getLog(AbstractRibbonCommand.class); protected final LBC client; protected RibbonCommandContext context; protected ZuulFallbackProvider zuulFallbackProvider; protected IClientConfig config; public AbstractRibbonCommand(LBC client, RibbonCommandContext context, ZuulProperties zuulProperties) { this("default", client, context, zuulProperties); } public AbstractRibbonCommand(String commandKey, LBC client, RibbonCommandContext context, ZuulProperties zuulProperties) { this(commandKey, client, context, zuulProperties, null); } public AbstractRibbonCommand(String commandKey, LBC client, RibbonCommandContext context, ZuulProperties zuulProperties, ZuulFallbackProvider fallbackProvider) { this(commandKey, client, context, zuulProperties, fallbackProvider, null); } public AbstractRibbonCommand(String commandKey, LBC client, RibbonCommandContext context, ZuulProperties zuulProperties, ZuulFallbackProvider fallbackProvider, IClientConfig config) { this(getSetter(commandKey, zuulProperties, config), client, context, fallbackProvider, config); } protected AbstractRibbonCommand(Setter setter, LBC client, RibbonCommandContext context, ZuulFallbackProvider fallbackProvider, IClientConfig config) { super(setter); this.client = client; this.context = context; this.zuulFallbackProvider = fallbackProvider; this.config = config; } protected static HystrixCommandProperties.Setter createSetter(IClientConfig config, String commandKey, ZuulProperties zuulProperties) { int hystrixTimeout = getHystrixTimeout(config, commandKey); return HystrixCommandProperties.Setter().withExecutionIsolationStrategy( zuulProperties.getRibbonIsolationStrategy()).withExecutionTimeoutInMilliseconds(hystrixTimeout); } protected static int getHystrixTimeout(IClientConfig config, String commandKey) { int ribbonTimeout = getRibbonTimeout(config, commandKey); DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance(); int defaultHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 0).get(); int commandHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command." + commandKey + ".execution.isolation.thread.timeoutInMilliseconds", 0).get(); int hystrixTimeout; if(commandHystrixTimeout > 0) { hystrixTimeout = commandHystrixTimeout; } else if(defaultHystrixTimeout > 0) { hystrixTimeout = defaultHystrixTimeout; } else { hystrixTimeout = ribbonTimeout; } if(hystrixTimeout < ribbonTimeout) { LOGGER.warn("The Hystrix timeout of " + hystrixTimeout + "ms for the command " + commandKey + " is set lower than the combination of the Ribbon read and connect timeout, " + ribbonTimeout + "ms."); } return hystrixTimeout; } protected static int getRibbonTimeout(IClientConfig config, String commandKey) { int ribbonTimeout; if (config == null) { ribbonTimeout = RibbonClientConfiguration.DEFAULT_READ_TIMEOUT + RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT; } else { int ribbonReadTimeout = getTimeout(config, commandKey, "ReadTimeout", IClientConfigKey.Keys.ReadTimeout, RibbonClientConfiguration.DEFAULT_READ_TIMEOUT); int ribbonConnectTimeout = getTimeout(config, commandKey, "ConnectTimeout", IClientConfigKey.Keys.ConnectTimeout, RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT); int maxAutoRetries = getTimeout(config, commandKey, "MaxAutoRetries", IClientConfigKey.Keys.MaxAutoRetries, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES); int maxAutoRetriesNextServer = getTimeout(config, commandKey, "MaxAutoRetriesNextServer", IClientConfigKey.Keys.MaxAutoRetriesNextServer, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER); ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1); } return ribbonTimeout; } private static int getTimeout(IClientConfig config, String commandKey, String property, IClientConfigKey<Integer> configKey, int defaultValue) { DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance(); return dynamicPropertyFactory.getIntProperty(commandKey + "." + config.getNameSpace() + "." + property, config.get(configKey, defaultValue)).get(); } @Deprecated //TODO remove in 2.0.x protected static Setter getSetter(final String commandKey, ZuulProperties zuulProperties) { return getSetter(commandKey, zuulProperties, null); } protected static Setter getSetter(final String commandKey, ZuulProperties zuulProperties, IClientConfig config) { // @formatter:off Setter commandSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RibbonCommand")) .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); final HystrixCommandProperties.Setter setter = createSetter(config, commandKey, zuulProperties); if (zuulProperties.getRibbonIsolationStrategy() == ExecutionIsolationStrategy.SEMAPHORE){ final String name = ZuulConstants.ZUUL_EUREKA + commandKey + ".semaphore.maxSemaphores"; // we want to default to semaphore-isolation since this wraps // 2 others commands that are already thread isolated final DynamicIntProperty value = DynamicPropertyFactory.getInstance() .getIntProperty(name, zuulProperties.getSemaphore().getMaxSemaphores()); setter.withExecutionIsolationSemaphoreMaxConcurrentRequests(value.get()); } else if (zuulProperties.getThreadPool().isUseSeparateThreadPools()) { final String threadPoolKey = zuulProperties.getThreadPool().getThreadPoolKeyPrefix() + commandKey; commandSetter.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(threadPoolKey)); } return commandSetter.andCommandPropertiesDefaults(setter); // @formatter:on } @Override protected ClientHttpResponse run() throws Exception { final RequestContext context = RequestContext.getCurrentContext(); RQ request = createRequest(); RS response; boolean retryableClient = this.client instanceof AbstractLoadBalancingClient && ((AbstractLoadBalancingClient)this.client).isClientRetryable((ContextAwareRequest)request); if (retryableClient) { response = this.client.execute(request, config); } else { response = this.client.executeWithLoadBalancer(request, config); } context.set("ribbonResponse", response); // Explicitly close the HttpResponse if the Hystrix command timed out to // release the underlying HTTP connection held by the response. // if (this.isResponseTimedOut()) { if (response != null) { response.close(); } } return new RibbonHttpResponse(response); } @Override protected ClientHttpResponse getFallback() { if(zuulFallbackProvider != null) { return getFallbackResponse(); } return super.getFallback(); } protected ClientHttpResponse getFallbackResponse() { if (zuulFallbackProvider instanceof FallbackProvider) { Throwable cause = getFailedExecutionException(); cause = cause == null ? getExecutionException() : cause; if (cause == null) { zuulFallbackProvider.fallbackResponse(); } else { return ((FallbackProvider) zuulFallbackProvider).fallbackResponse(cause); } } return zuulFallbackProvider.fallbackResponse(); } public LBC getClient() { return client; } public RibbonCommandContext getContext() { return context; } protected abstract RQ createRequest() throws Exception; } View Code 请注意:getRibbonTimeout方法与getHystrixTimeout方法,其中这两个方法 commandKey的值为路由的名称,比如说我们访问:http://localhost:8088/order-server/xxx来访问order-server服务, 那么commandKey 就为order-server 根据源代码,我们先设置gateway-server的超时参数: #全局的ribbon设置 ribbon: ConnectTimeout: 3000 ReadTimeout: 3000 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000 zuul: host: connectTimeoutMillis: 10000 View Code 当然也可以单独为order-server设置ribbon的超时参数:order-server.ribbon.xxxx=xxx , 为了演示zuul中的回退效果,我在这里把Hystrix超时时间设置短一点。当然最好不要将Hystrix默认的超时时间设置的比Ribbon的超时时间短,源码里遇到此情况已经给与我们警告了。 那么我们在order-server下添加如下方法: @GetMapping("/sleep/{sleepTime}") public String sleep(@PathVariable Long sleepTime) throws InterruptedException { TimeUnit.SECONDS.sleep(sleepTime); return "SUCCESS"; } 2、zuul的回退方法 我们可以实现ZuulFallbackProvider接口,实现代码: package com.hzgj.lyrk.springcloud.gateway.server.filter; import com.google.common.collect.ImmutableMap; import com.google.gson.GsonBuilder; import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.time.LocalDateTime; import java.time.LocalTime; @Component public class FallBackHandler implements ZuulFallbackProvider { @Override public String getRoute() { //代表所有的路由都适配该设置 return "*"; } @Override public ClientHttpResponse fallbackResponse() { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "OK"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { String result = new GsonBuilder().create().toJson(ImmutableMap.of("errorCode", 500, "content", "请求失败", "time", LocalDateTime.now())); return new ByteArrayInputStream(result.getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } } View Code 此时我们访问:http://localhost:8088/order-server/sleep/6 得到如下结果: 当我们访问:http://localhost:8088/order-server/sleep/1 就得到如下结果:
原先在公司做项目时,写了一个简单的基于gradle部署项目的脚本,今天翻出来记录一下 一、build.gradle buildscript { ext { env = System.getProperty("env") ?: "test" jvmArgs = "-server -Xms128m -Xmx128m -XX:NewRatio=4 -XX:SurvivorRatio=16 -XX:MaxTenuringThreshold=15 -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+ExplicitGCInvokesConcurrent -XX:+DoEscapeAnalysis -XX:-HeapDumpOnOutOfMemoryError" if (env == "prod") { jvmArgs = "-server -Xms2g -Xmx2g -XX:NewRatio=4 -XX:SurvivorRatio=16 -XX:MaxTenuringThreshold=15 -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+ExplicitGCInvokesConcurrent -XX:+DoEscapeAnalysis -XX:-HeapDumpOnOutOfMemoryError" } userHome = System.getProperty("user.home") osName = System.getProperty("os.name") } repositories { jcenter() } dependencies { classpath 'org.hidetake:gradle-ssh-plugin:2.7.0' classpath 'co.tomlee.gradle.plugins:gradle-thrift-plugin:0.0.6' } } allprojects { apply plugin: 'idea' apply plugin: 'eclipse' apply plugin: 'org.hidetake.ssh' group = 'com.mwee.information.core' version = '1.0-SNAPSHOT' ssh.settings { timeoutSec = 60 knownHosts = allowAnyHosts } defaultTasks 'clean', 'copyPartDependencies' //排除Log4j依赖 configurations { compile.exclude module: 'slf4j-log4j12' compile.exclude module: 'org.apache.logging.log4j' compile.exclude module: 'log4j' all*.exclude group: 'org.apache.logging.log4j' all*.exclude group: 'log4j' } } subprojects { apply plugin: 'java' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenLocal() maven { url "http://114.80.88.52:9001/nexus/content/groups/public/" } } sourceSets { main { java { srcDirs = ['src/main/java'] } resources { srcDirs = ["src/main/resources", "src/main/profile/$env"] } } } dependencies { compile("org.codehaus.groovy:groovy-all:2.2.1") compile 'org.codehaus.groovy:groovy-backports-compat23:2.4.5' compile("org.springframework.boot:spring-boot-starter-web:1.4.2.RELEASE") compile("org.apache.commons:commons-lang3:3.4") compile("org.apache.commons:commons-collections4:4.1") compile "org.apache.commons:commons-pool2:2.4.2" compile group: 'com.alibaba', name: 'fastjson', version: '1.2.12' // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.6' // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.6' compile group: 'org.aspectj', name: 'aspectjrt', version: '1.8.7' compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.8.7' compile group: 'com.thoughtworks.xstream', name: 'xstream', version: '1.4.1' compile(group: 'org.mortbay.jetty', name: 'jetty', version: '6.1.26') compile group: 'org.projectlombok', name: 'lombok', version: '1.16.8' compile group: 'com.squareup.okhttp', name: 'okhttp', version: '2.7.5' compile group: 'com.google.guava', name: 'guava', version: '18.0' compile group: 'commons-lang', name: 'commons-lang', version: '2.6' compile group: 'com.jcraft', name: 'jsch', version: '0.1.53' testCompile group: 'junit', name: 'junit', version: '4.12' testCompile "org.springframework:spring-test:4.3.4.RELEASE" compile "javax.validation:validation-api:1.1.0.Final" compile "org.hibernate:hibernate-validator:5.2.4.Final" } //gradle utf-8 compile tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } task copyAllDependencies(type: Copy, dependsOn: jar) { description = "拷贝全部依赖的jar包" from configurations.runtime into 'build/libs' } task copyPartDependencies(type: Copy, dependsOn: jar) { description = "拷贝部分依赖的jar" from configurations.runtime into 'build/libs' doLast { file("build/libs").listFiles({ !it.name.endsWith("-SNAPSHOT.jar") } as FileFilter).each { it.delete() } } } } View Code 二、对应模块下的build.gradle def mainClass = "com.hzgj.information.rest.user.run.UserServiceProvider" def appHome = "/home/appsvr/apps/rest_user" def javaCommand = "nohup java $jvmArgs -Djava.ext.dirs=$appHome/libs -Denv=$env $mainClass >$appHome/shell.log 2>&1 &" def index = System.getProperty("index") def remote = remotes { test_0 { role 'test_0' host = '10.0.21.152' if (file("$userHome/.ssh/id_rsa").exists()) { user = 'appsvr' identity = file("$userHome/.ssh/id_rsa") } else { user = 'appsvr' password = 'xxx' } } test_1 { role 'test_1' host = '10.0.146.20' if (file("$userHome/.ssh/id_rsa").exists()) { user = 'appsvr' identity = file("$userHome/.ssh/id_rsa") } else { user = 'appsvr' password = 'xxx' } } home { role 'home' host = '192.168.109.130' user = 'appsvr' password = 'xxx' // identity = file('id_rsa') } } task deploy << { description = "拷贝jar包并启动java服务" def roles = remote.findAll { def currentEnv = index == null ? "$env" : "$env" + "_" + index it['roles'][0].toString().contains(currentEnv) } ssh.run { roles.each { def role = it['roles'][0].toString() session(remotes.role(role)) { try { execute("ls $appHome") } catch (Exception e) { println("#############目录[$appHome]不存在,将自动创建############") execute("mkdir -p $appHome") } finally { def r = '$1' def pid = execute("jps -l |grep '$mainClass' |awk \'{print $r}\'") if (pid) { execute("kill -9 $pid") } put from: 'build/libs', into: "$appHome" println("###############准备启动java服务[$javaCommand]####################") execute("$javaCommand") sleep(10000) pid = execute("jps -l |grep '$mainClass' |awk \'{print $r}\'") if (pid) { println("#####$mainClass [$pid] 启动成功...######") execute("rm -f $appHome/shell.log") } else { println("#$mainClass 启动失败...输出日志如下:#") execute("cat $appHome/shell.log") } } } } } } task stop << { def roles = remote.findAll { def currentEnv = index == null ? "$env" : "$env" + "_" + index it['roles'][0].toString().contains(currentEnv) } ssh.run { roles.each { session(remotes.role("$env")) { def r = '$1' def pid = execute("jps -l |grep '$mainClass' |awk \'{print $r}\'") if (pid) { execute("kill -9 $pid") } } } } } task start << { def roles = remote.findAll { def currentEnv = index == null ? "$env" : "$env" + "_" + index it['roles'][0].toString().contains(currentEnv) } ssh.run { roles.each { def role = it['roles'][0].toString() session(remotes.role(role)) { def r = '$1' def pid = execute("jps -l |grep '$mainClass' |awk \'{print $r}\'") if (pid) { execute("kill -9 $pid") } println("###############准备启动java服务[$javaCommand]####################") execute("$javaCommand") sleep(10000) pid = execute("jps -l |grep '$main Class' |awk \'{print $r}\'") if (pid) { println("#$mainClass [$pid] 启动成功...#") execute("rm -f $appHome/shell.log") } else { println("#$mainClass 启动失败...输出日志如下:#") execute("cat $appHome/shell.log") } } } } } View Code 三、使用方式 1.先运行gradle copyAll -x test 进行打包操作,该操作会将该模块所有的依赖的jar 2.进入到对应的模块下 运行gradle deploy -Denv=xxx -Dindex=xxx ,什么意思呢?-Denv代表哪一个环境 -Dindex指定该环境下哪个节点进行发布 3.gradle start -Denv=xxx -Dindex=xxx 运行当前环境下的应用
这篇文章我们解决上篇链路跟踪的遗留问题 一、将追踪数据存放到MySQL数据库中 默认情况下zipkin将收集到的数据存放在内存中(In-Memeroy),但是不可避免带来了几个问题: 在服务重新启动后,历史数据丢失。 在数据量过大的时候容易造成OOM错误 通常做法是与mysql或者ElasticSearch结合使用,那么我们先把收集到的数据先存到Mysql数据库中 1、改造zipkin-server的依赖 gradle配置: dependencies { compile('org.springframework.cloud:spring-cloud-starter-eureka') compile('org.springframework.cloud:spring-cloud-starter-config') // compile('io.zipkin.java:zipkin-server') compile 'org.springframework.cloud:spring-cloud-starter-sleuth' compile('io.zipkin.java:zipkin-autoconfigure-ui') runtime('mysql:mysql-connector-java') compile('org.springframework.boot:spring-boot-starter-jdbc') compile('org.springframework.cloud:spring-cloud-sleuth-zipkin-stream') compile('org.springframework.cloud:spring-cloud-stream') compile('org.springframework.cloud:spring-cloud-stream-binder-kafka') } View Code 这里将原先的 io.zipkin.java:zipkin-server 替换为 spring-cloud-sleuth-zipkin-stream 该依赖项包含了对mysql存储的支持,同时添加spring-boot-starter-jdbc与mysql的依赖,顺便把kafka的支持也加进来 注意:此处脚本最好在数据库中执行一下,当然我们也可以在下面的配置文件中做初始化的相关配置 2、YAML中的关键配置项: spring: datasource: username: root password: root url: jdbc:mysql://localhost:3306/myschool?characterEncoding=utf-8&useSSL=false initialize: true continue-on-error: true kafka: bootstrap-servers: localhost:9092 server: port: 9000 zipkin: storage: type: mysql View Code 注意zipkin.storage.type 指定为mysql 3、更改启动类 package com.hzgj.lyrk.zipkin.server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.sleuth.zipkin.stream.EnableZipkinStreamServer; @EnableZipkinStreamServer @SpringBootApplication public class ZipkinServerApplication { public static void main(String[] args) { SpringApplication.run(ZipkinServerApplication.class, args); } } View Code 这里注意将@EnableZipkinServer改成@EnableZipkinStreamServer 二、将收集信息改成异步发送 这步改造主要用以提高性能与稳定性,服务将收集到的span无脑的往消息中间件上丢就可以了,不用管zipkin的地址在哪里。 1、改造Order-Server依赖: gradle: compile('org.springframework.cloud:spring-cloud-starter-eureka-server') // compile('org.springframework.cloud:spring-cloud-sleuth-zipkin') compile 'org.springframework.cloud:spring-cloud-starter-sleuth' compile 'org.springframework.cloud:spring-cloud-sleuth-stream' compile('org.springframework.cloud:spring-cloud-starter-config') compile('org.springframework.cloud:spring-cloud-stream') compile('org.springframework.cloud:spring-cloud-stream-binder-kafka') compile('org.springframework.kafka:spring-kafka') compile('org.springframework.cloud:spring-cloud-starter-bus-kafka') View Code 这里把原先的spring-cloud-sleuth-zipkin改成spring-cloud-sleuth-stream,不用猜里面一定是基于spring-cloud-stream实现的 2、YAML关键属性配置: server: port: 8100 logging: level: org.springframework.cloud.sleuth: DEBUG spring: sleuth: sampler: percentage: 1.0 View Code 注意:这里设置低采样率会导致span的丢弃。我们同时设置sleuth的日志输出为debug 3、同理改造其他的微服务 三、验证结果 数据库里的相关数据:
今天在看JDBC4.2新规范,然后无意之间就碰到了这个东西QueryObjectFactory, 市面上orm框架有很多,在这里我就不一一列举了。那么今天我来记录一下QueryObjectFactory。官网地址:点这里 一、快速入门 1、将下载好的jar包加入项目 2、我们利用一下jdbc4.0的规范在项目中建立如下文件 在这里我们指定要加载的驱动:com.mysql.jdbc.Driver 3、编写接口: package com.bdqn.lyrk.java.study; import sf.qof.BaseQuery; import sf.qof.Insert; import sf.qof.Query; import java.util.List; public interface StudentDao extends BaseQuery { @Query(sql = "select stuName {%%.stuName},id {%%.id} from student_0") List<StudentEntity> listStudents(); @Insert(sql = "INSERT INTO student_0 (id,stuName,password) values({%1.id},{%1.stuName},{%1.password})") void save(List<StudentEntity> studentEntities); } View Code 这里面我们使用注解的方式配置SQL语句。在这里{}表示占位符,{%%.属性} 通常用于设置查询结果对应的对象属性上,{%1} 通常代表方法内的参数 4、Main方法 package com.bdqn.lyrk.java.study; import sf.qof.QueryObjectFactory; import sf.qof.dialect.HSQLDbDialect; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) throws SQLException { Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/MySchool?characterEncoding=utf-8&useSSL=false", "root", "xxx"); StudentDao studentDao = QueryObjectFactory.createQueryObject(StudentDao.class); QueryObjectFactory.setSQLDialect(new HSQLDbDialect()); studentDao.setConnection(connection); studentDao.setBatchSize(3); studentDao.listStudents().forEach(s -> System.out.println(s.getStuName())); List<StudentEntity> studentEntities = new ArrayList<>(); for (int i = 0; i < 5; i++) { StudentEntity studentEntity = new StudentEntity(); studentEntity.setId(i + 10); studentEntity.setStuName("test3"); studentEntity.setPassword("abcd"); studentEntities.add(studentEntity); } studentDao.save(studentEntities); } } View Code 执行成功后,拿到如下结果: 5、 设置参数的方式与坑 在这里面QueryFactory给我们设置了如下几种数据类型 registerResultMapper("string", AbstractCharacterMapping.StringMapping.class, AbstractCharacterMapping.StringMapping.getTypes()); registerResultMapper("char", AbstractCharacterMapping.CharacterMapping.class, AbstractCharacterMapping.CharacterMapping.getTypes()); registerResultMapper("boolean", AbstractNumberMapping.BooleanMapping.class, AbstractNumberMapping.BooleanMapping.getTypes()); registerResultMapper("byte", AbstractNumberMapping.ByteMapping.class, AbstractNumberMapping.ByteMapping.getTypes()); registerResultMapper("short", AbstractNumberMapping.ShortMapping.class, AbstractNumberMapping.ShortMapping.getTypes()); registerResultMapper("int", AbstractNumberMapping.IntegerMapping.class, AbstractNumberMapping.IntegerMapping.getTypes()); registerResultMapper("integer", AbstractNumberMapping.IntegerMapping.class, AbstractNumberMapping.IntegerMapping.getTypes()); registerResultMapper("long", AbstractNumberMapping.LongMapping.class, AbstractNumberMapping.LongMapping.getTypes()); registerResultMapper("float", AbstractNumberMapping.FloatMapping.class, AbstractNumberMapping.FloatMapping.getTypes()); registerResultMapper("double", AbstractNumberMapping.DoubleMapping.class, AbstractNumberMapping.DoubleMapping.getTypes()); registerResultMapper("date", AbstractDateTimeMapping.DateMapping.class, AbstractDateTimeMapping.DateMapping.getTypes()); registerResultMapper("time", AbstractDateTimeMapping.TimeMapping.class, AbstractDateTimeMapping.TimeMapping.getTypes()); registerResultMapper("timestamp", AbstractDateTimeMapping.TimestampMapping.class, AbstractDateTimeMapping.TimestampMapping.getTypes()); registerParameterMapper("string", AbstractCharacterMapping.StringMapping.class, AbstractCharacterMapping.StringMapping.getTypes()); registerParameterMapper("char", AbstractCharacterMapping.CharacterMapping.class, AbstractCharacterMapping.CharacterMapping.getTypes()); registerParameterMapper("boolean", AbstractNumberMapping.BooleanMapping.class, AbstractNumberMapping.BooleanMapping.getTypes()); registerParameterMapper("byte", AbstractNumberMapping.ByteMapping.class, AbstractNumberMapping.ByteMapping.getTypes()); registerParameterMapper("short", AbstractNumberMapping.ShortMapping.class, AbstractNumberMapping.ShortMapping.getTypes()); registerParameterMapper("int", AbstractNumberMapping.IntegerMapping.class, AbstractNumberMapping.IntegerMapping.getTypes()); registerParameterMapper("integer", AbstractNumberMapping.IntegerMapping.class, AbstractNumberMapping.IntegerMapping.getTypes()); registerParameterMapper("long", AbstractNumberMapping.LongMapping.class, AbstractNumberMapping.LongMapping.getTypes()); registerParameterMapper("float", AbstractNumberMapping.FloatMapping.class, AbstractNumberMapping.FloatMapping.getTypes()); registerParameterMapper("double", AbstractNumberMapping.DoubleMapping.class, AbstractNumberMapping.DoubleMapping.getTypes()); registerParameterMapper("date", AbstractDateTimeMapping.DateMapping.class, AbstractDateTimeMapping.DateMapping.getTypes()); registerParameterMapper("time", AbstractDateTimeMapping.TimeMapping.class, AbstractDateTimeMapping.TimeMapping.getTypes()); registerParameterMapper("timestamp", AbstractDateTimeMapping.TimestampMapping.class, AbstractDateTimeMapping.TimestampMapping.getTypes()); View Code 注意我们通过以下几种方式设置数据类型: {数据类型%1} 或者 {数据类型%%.属性的方式} 坑:注意以下关于日期数据类型代码: /* * Copyright 2007 brunella ltd * * Licensed under the LGPL Version 3 (the "License"); * you may not use this file except in compliance with the License. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ package sf.qof.mapping; import java.util.Date; import java.util.HashSet; import java.util.Set; public abstract class AbstractDateTimeMapping extends AbstractBaseMapping implements Mapping, ParameterMapping, ResultMapping { private static final Set<Class<?>> types = new HashSet<Class<?>>(); static { types.add(Date.class); } public static Set<Class<?>> getTypes() { return types; } public void accept(Mapper mapper, MappingVisitor visitor) { visitor.visit(mapper, this); } public abstract void accept(Mapper mapper, DateTimeMappingVisitor visitor); public static class DateMapping extends AbstractDateTimeMapping { public void accept(Mapper mapper, DateTimeMappingVisitor visitor) { visitor.visit(mapper, this); } } public static class TimeMapping extends AbstractDateTimeMapping { public void accept(Mapper mapper, DateTimeMappingVisitor visitor) { visitor.visit(mapper, this); } } public static class TimestampMapping extends AbstractDateTimeMapping { public void accept(Mapper mapper, DateTimeMappingVisitor visitor) { visitor.visit(mapper, this); } } } View Code 在静态代码块中所有的日期类型都对应的是java.util.Date ,因此我们不能用Timestamp或者java.sql.Date作为JavaBean的数据类型 二、总结与展望 1. 使用注解通常情况下适配一些比较简单的查询,复杂的查询不建议使用 2.这个适合于小型项目的迅速开发与上线,大型项目建议选择成熟的orm框架 3.虽然说select * 不建议使用的,但是如何解决select * 下映射关系?
一、redis快速入门 1、redis简介 在java领域,常见的四大缓存分别是ehcache,memcached,redis,guava-cache,其中redis与其他类型缓存相比,有着得天独厚的优势: 它是基于内存的数据库,什么意思呢?由于受磁盘IO影响,它所有操作都在内存当中,用以提高性能,同时采用异步的方式将数据保存在硬盘当中。 与memcached相比,redis支持多种数据类型,string,list,set,sorted set,hash。让我们使用起来更加灵活 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行 丰富的特性:可用于缓存,消息,可以设置key的过期时间,过期后将会自动删除对应的记录 redis是单线程单进程的,它利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。 2、redis常见的数据类型 2.1 redis的key: 我们可以使用任何二进制序列当我们的键,过长过短的键都不建议使用,在这里建议大家使用尝试使用一个模式。例如,“object-type:id”是一个比较推荐的方式,举个例子,我们可以这样来定义键:“user:1000”。点或破折号通常用于多字字段。 2.2 strings: 该类型是最基本的数据类型,也是最常见的数据类型。我们可以使用set 或get 来设置和获取对应的值: > set mykey somevalue OK > get mykey "somevalue" 在这里面set 如果已经存在的key 会替换已有的值。注意value的值不要大于512M 另外,我们可以使用mset 与 mget来设置和获取多个值,如下: > mset a 10 b 20 c 30 OK > mget a b c 1) "10" 2) "20" 3) "30" 我们可以使用exists判断key对应的值是否存在,del则是删除key对应的值 > set mykey hello OK > exists mykey (integer) 1 > del mykey (integer) 1 > exists mykey (integer) 0 我们也可以指定key值对应的过期时间:(单位为秒) > set key some-value OK > expire key 5 (integer) 1 > get key (immediately) "some-value" > get key (after some time) (nil) 2.3 lists类型:这个类似于我们的集合类型,可以存放多个值,我们可以使用lpush(在头部添加)与rpush(在尾部添加)来添加对应的元素: rpush mylist A (integer) 1 > rpush mylist B (integer) 2 > lpush mylist first (integer) 3 > lrange mylist 0 -1 1) "first" 2) "A" 3) "B" 其中lrange 是取一定范围的元素 2.4 Hashes 这个类似于key->key ,value的形式,我们可以使用hmset与hget来取值 > hmset user:1000 username antirez birthyear 1977 verified 1 OK > hget user:1000 username "antirez" > hget user:1000 birthyear "1977" > hgetall user:1000 1) "username" 2) "antirez" 3) "birthyear" 4) "1977" 5) "verified" 6) "1" 2.5、SETS:存放了一系列无序的字符串集合,我们可以通过sadd与smembers来取值: > sadd myset 1 2 3 (integer) 3 > smembers myset 1. 3 2. 1 3. 2 3、redis中的事务 MULTI 命令用于开启一个事务,它总是返回 OK 。 MULTI执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC命令被调用时, 所有队列中的命令才会被执行。redis中事务不存在回滚,我们这么操作试试: localhost:0>multi "OK" localhost:0>set test abc "QUEUED" localhost:0>incr test "QUEUED" localhost:0>exec 1) "OK" 2) "ERR value is not an integer or out of range" localhost:0>get test "abc" 如果事务当中入队不成功,我们可以看一下例子: localhost:0>multi "OK" localhost:0>set test abcd "QUEUED" localhost:0>test abc "ERR unknown command 'test'" localhost:0>exec "EXECABORT Transaction discarded because of previous errors." localhost:0>get test "abc" 此时就会终止我们提交事务 另外我们调用 DISCARD , 客户端可以清空事务队列并放弃执行事务。例子: localhost:0>multi "OK" localhost:0>set test abcd "QUEUED" localhost:0>discard "OK" localhost:0>get test "abc" 4、远程连接redis注意事项 redis现在的版本开启redis-server后,redis-cli只能访问到127.0.0.1,因为在配置文件中固定了ip,因此需要修改redis.conf(有的版本不是这个文件名,只要找到相对应的conf后缀的文件即可)文件以下几个地方。1、bind 127.0.0.1改为 #bind 127.0.0.1 2、protected-mode yes 改为 protected-mode no 指定配置文件运行: ./redis-server ../redis.conf 远程连接的命令: ./redis-cli -p 6379 -h xxx.xxx.xxx 二、使用SpringCache集成redis 1、关于SpringCache 从SpringFramework3.1版本开始,Spring给我们提供了一系列注解和接口规范来简化我们操作缓存代码量,几个核心接口如下: CacheManager: 该接口主要作用是获取缓存和获取缓存名称 /* * Copyright 2002-2014 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.cache; import java.util.Collection; import org.springframework.lang.Nullable; /** * Spring's central cache manager SPI. * Allows for retrieving named {@link Cache} regions. * * @author Costin Leau * @since 3.1 */ public interface CacheManager { /** * Return the cache associated with the given name. * @param name the cache identifier (must not be {@code null}) * @return the associated cache, or {@code null} if none found */ @Nullable Cache getCache(String name); /** * Return a collection of the cache names known by this manager. * @return the names of all caches known by the cache manager */ Collection<String> getCacheNames(); } View Code 在这里面最核心的接口当然是Cache: /* * Copyright 2002-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.cache; import java.util.concurrent.Callable; import org.springframework.lang.Nullable; /** * Interface that defines common cache operations. * * <b>Note:</b> Due to the generic use of caching, it is recommended that * implementations allow storage of <tt>null</tt> values (for example to * cache methods that return {@code null}). * * @author Costin Leau * @author Juergen Hoeller * @author Stephane Nicoll * @since 3.1 */ public interface Cache { /** * Return the cache name. */ String getName(); /** * Return the underlying native cache provider. */ Object getNativeCache(); /** * Return the value to which this cache maps the specified key. * <p>Returns {@code null} if the cache contains no mapping for this key; * otherwise, the cached value (which may be {@code null} itself) will * be returned in a {@link ValueWrapper}. * @param key the key whose associated value is to be returned * @return the value to which this cache maps the specified key, * contained within a {@link ValueWrapper} which may also hold * a cached {@code null} value. A straight {@code null} being * returned means that the cache contains no mapping for this key. * @see #get(Object, Class) */ @Nullable ValueWrapper get(Object key); /** * Return the value to which this cache maps the specified key, * generically specifying a type that return value will be cast to. * <p>Note: This variant of {@code get} does not allow for differentiating * between a cached {@code null} value and no cache entry found at all. * Use the standard {@link #get(Object)} variant for that purpose instead. * @param key the key whose associated value is to be returned * @param type the required type of the returned value (may be * {@code null} to bypass a type check; in case of a {@code null} * value found in the cache, the specified type is irrelevant) * @return the value to which this cache maps the specified key * (which may be {@code null} itself), or also {@code null} if * the cache contains no mapping for this key * @throws IllegalStateException if a cache entry has been found * but failed to match the specified type * @since 4.0 * @see #get(Object) */ @Nullable <T> T get(Object key, @Nullable Class<T> type); /** * Return the value to which this cache maps the specified key, obtaining * that value from {@code valueLoader} if necessary. This method provides * a simple substitute for the conventional "if cached, return; otherwise * create, cache and return" pattern. * <p>If possible, implementations should ensure that the loading operation * is synchronized so that the specified {@code valueLoader} is only called * once in case of concurrent access on the same key. * <p>If the {@code valueLoader} throws an exception, it is wrapped in * a {@link ValueRetrievalException} * @param key the key whose associated value is to be returned * @return the value to which this cache maps the specified key * @throws ValueRetrievalException if the {@code valueLoader} throws an exception * @since 4.3 */ @Nullable <T> T get(Object key, Callable<T> valueLoader); /** * Associate the specified value with the specified key in this cache. * <p>If the cache previously contained a mapping for this key, the old * value is replaced by the specified value. * @param key the key with which the specified value is to be associated * @param value the value to be associated with the specified key */ void put(Object key, @Nullable Object value); /** * Atomically associate the specified value with the specified key in this cache * if it is not set already. * <p>This is equivalent to: * <pre><code> * Object existingValue = cache.get(key); * if (existingValue == null) { * cache.put(key, value); * return null; * } else { * return existingValue; * } * </code></pre> * except that the action is performed atomically. While all out-of-the-box * {@link CacheManager} implementations are able to perform the put atomically, * the operation may also be implemented in two steps, e.g. with a check for * presence and a subsequent put, in a non-atomic way. Check the documentation * of the native cache implementation that you are using for more details. * @param key the key with which the specified value is to be associated * @param value the value to be associated with the specified key * @return the value to which this cache maps the specified key (which may be * {@code null} itself), or also {@code null} if the cache did not contain any * mapping for that key prior to this call. Returning {@code null} is therefore * an indicator that the given {@code value} has been associated with the key. * @since 4.1 */ @Nullable ValueWrapper putIfAbsent(Object key, @Nullable Object value); /** * Evict the mapping for this key from this cache if it is present. * @param key the key whose mapping is to be removed from the cache */ void evict(Object key); /** * Remove all mappings from the cache. */ void clear(); /** * A (wrapper) object representing a cache value. */ @FunctionalInterface interface ValueWrapper { /** * Return the actual value in the cache. */ @Nullable Object get(); } /** * Wrapper exception to be thrown from {@link #get(Object, Callable)} * in case of the value loader callback failing with an exception. * @since 4.3 */ @SuppressWarnings("serial") class ValueRetrievalException extends RuntimeException { @Nullable private final Object key; public ValueRetrievalException(@Nullable Object key, Callable<?> loader, Throwable ex) { super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex); this.key = key; } @Nullable public Object getKey() { return this.key; } } } View Code 该接口定义了操作缓存的最基本行为 同时Spring给我们提供了一些默认的缓存实现 比如说:JDK java.util.concurrent.ConcurrentMap,ehcache2.x , Gemfire cache, guava等 2、几个重要的注解 @EnableCaching:用于开启缓存注解,通常需要和@Configuration使用 @Cacheable 该注解的意思为:读缓存中的值,如果没有该值,那么将执行注解对应的方法并将方法返回结果存入缓存 使用示例: @Cacheable(cacheNames="books", key="#isbn") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="#isbn.rawNumber") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="T(someType).hash(#isbn)") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) View Code 有些时候我们希望根据条件将对应数据存入缓存,可以这么写: @Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") public Book findBook(String name) View Code 示例中:condition条件为true时,才存入缓存,unless属性是针对本次查询结果筛选。注意当表达式为true时的结果都不会存入缓存, #result 代表了方法的返回结果 @CacheEvict 触发Cache接口中的evict方法,相当于移除key对应的值 @CacheEvict(cacheNames="books", allEntries=true,beforeInvocation=true) public void loadBooks(InputStream batch) View Code beforeInvocation属性表明是否在执行方法之前清除缓存,allEntries属性会删除缓存下的所有数据,另外如果该值为true时就不能和key同时使用了 @CachePut 执行方法并更新缓存 @CachePut(cacheNames="book", key="#isbn") public Book updateBook(ISBN isbn, BookDescriptor descriptor) View Code 这些缓存注解的幕后英雄当然是我们的AOP啦 3、缓存注解中的SpringEL表达式 springEL可以运用在我们的cache注解当中,比如说其中key属性 condition属性等,其中#result只能在unless属性中使用这点比较特殊,在这里我贴出官网的列表: Description Example The name of the method being invoked #root.methodName The method being invoked #root.method.name The target object being invoked #root.target The class of the target being invoked #root.targetClass The arguments (as array) used for invoking the target #root.args[0] Collection of caches against which the current method is executed #root.caches[0].name Name of any of the method arguments. If for some reason the names are not available (e.g. no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0). #iban or #a0 (one can also use #p0 or #p<#arg>notation as an alias). The result of the method call (the value to be cached). Only available in unlessexpressions, cache putexpressions (to compute the key), or cache evictexpressions (when beforeInvocation is false). For supported wrappers such as Optional, #resultrefers to the actual object, not the wrapper. #result 4、代码集成示例 该代码是基于spring与hibernate集成,请参考:这里 gradle配置: compile 'org.springframework.data:spring-data-redis:2.0.6.RELEASE' // https://mvnrepository.com/artifact/redis.clients/jedis compile group: 'redis.clients', name: 'jedis', version: '2.9.0' // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5' // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5' View Code 1.创建RedisConfig: package com.bdqn.lyrk.ssh.study.config; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; @Configuration @EnableCaching public class RedisConfig { /** * 创建redisConnectionFactory * @return */ @Bean public RedisConnectionFactory redisConnectionFactory() { /* 设置相关配置 */ RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); configuration.setPort(6379); configuration.setHostName("localhost"); configuration.setDatabase(0); JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(configuration); return jedisConnectionFactory; } @Bean public StringRedisTemplate stringRedisTemplate() { StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(); stringRedisTemplate.setConnectionFactory(redisConnectionFactory()); return stringRedisTemplate; } @Bean public RedisCacheManager cacheManager() { /* 配置json序列化方式 */ RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(). serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer( new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(redisConnectionFactory()). transactionAware().cacheDefaults(cacheConfiguration).build(); } } View Code 2.创建StudentService package com.bdqn.lyrk.ssh.study.service; import com.bdqn.lyrk.ssh.study.entity.StudentEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.orm.hibernate5.HibernateTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class StudentService { @Autowired private HibernateTemplate hibernateTemplate; @Transactional public int save(StudentEntity studentEntity) { hibernateTemplate.save(studentEntity); return studentEntity.getId(); } @Cacheable(key = "#id", cacheNames = "student", cacheManager = "cacheManager", condition = "#id==1") public StudentEntity listStudents(Integer id) { return hibernateTemplate.get(StudentEntity.class, id); } @Transactional @CachePut(key = "#id", cacheNames = "student", cacheManager = "cacheManager") public StudentEntity updateStudent(Integer id) { StudentEntity studentEntity = new StudentEntity(); studentEntity.setId(id); studentEntity.setStuName("tests"); studentEntity.setPassword("password"); hibernateTemplate.update(studentEntity); return studentEntity; } @CacheEvict(key = "#id", cacheNames = "student") @Transactional public StudentEntity deleteStudent(Integer id) { StudentEntity studentEntity = hibernateTemplate.load(StudentEntity.class, id); hibernateTemplate.delete(studentEntity); return studentEntity; } } View Code 3.Main方法: package com.bdqn.lyrk.ssh.study; import com.bdqn.lyrk.ssh.study.config.AppConfig; import com.bdqn.lyrk.ssh.study.service.StudentService; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); StudentService studentService = applicationContext.getBean(StudentService.class); studentService.updateStudent(1); System.out.println(studentService.listStudents(1).getStuName()); studentService.deleteStudent(1); } } View Code 运行结果: 我们可以看到:第二次查询时并没有输出Hibernate的执行的sql,说明这个是从redis缓存中读取的。
一。spring-cloud-bus是什么? 回答这个问题之前,我们先回顾先前的分布式配置,当配置中心发生变化后,我们需要利用spring-boot-actuator里的refresh端点进行手动刷新: 根据上述示例情况:我们每次要获取最新配置时,要一个一个的通过refresh刷新服务节点,这种方式是不是非常low而且非常麻烦,那该怎么办呢? 大家还记得zookeeper中watch是做什么用的吗?当监控的节点数据发生变化了,那么是不是所有订阅到该节点的客户端都会触发一个订阅回调呢?这其实也类似于我们的消息总线。在微服务架构的系统中,我们通常会使用轻量级的消息代理来构建一个公有的消息主题让系统中所有微服务实例都连接上来,由于该主题中产生的消息会被所有实例监听和消费,所以我们称它为消息总线。 那么在分布式配置中,我们的所有服务都订阅消息总线的话,当配置改变时,配置中心通知消息总线,然后所有的服务节点接收到订阅消息后,在从配置中心获取最新的配置,是不是一个一劳永逸的过程?那么可以得到如下结构图: 二、实现分布式配置的消息总线 我先贴出:项目结构图 我们统一把配置放在config目录下 1、在对应的服务添加对应的依赖 首先我们先配置config-server。gradle配置文件: dependencies { // testCompile group: 'junit', name: 'junit', version: '4.12' compile('org.springframework.cloud:spring-cloud-config-server') compile('org.springframework.kafka:spring-kafka') compile('org.springframework.cloud:spring-cloud-starter-bus-kafka') compile('org.springframework.cloud:spring-cloud-starter-eureka-server') } View Code 注意我们使用kafka作为消息总线的中间件 然后我们依次在所需的服务中添加对应的依赖 dependencies { // testCompile group: 'junit', name: 'junit', version: '4.12' compile('org.springframework.cloud:spring-cloud-starter-config') compile('org.springframework.kafka:spring-kafka') compile('org.springframework.cloud:spring-cloud-starter-bus-kafka') compile('org.springframework.cloud:spring-cloud-starter-eureka-server') } View Code 2、配置对应的application.yml config-server的yml文件: spring: application: name: config-server cloud: config: server: git: uri: file://${user.home}/IdeaProjects/spring-cloud repos: local: pattern: '**/local' uri: file://${user.home}/IdeaProjects/spring-cloud searchPaths: config search-paths: config label: master kafka: bootstrap-servers: localhost:9092 server: port: 8888 endpoints: refresh: sensitive: false bus: sensitive: false View Code 这里面注意要把端点先开放出来,然后进行kafka的相关配置,其余服务的配置文件也进行这样的操作,使其能与消息总线通讯。 3、依次启动服务 当服务启动成功时,SpringBootActuator给我们提供一个/bus/refresh端点,同时我们可以在kafka主题里面找到相应的topics,其名字为springCloudBus: 4、访问Config-Server的/bus/refesh 我们先使用kafka的消费端来监听一下消息内容。运行: ./kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic springCloudBus 紧接着我们在访问 http://localhost:8888/bus/refresh 刷新config-server的配置 然后我们可以发现消费端,订阅到如下消息: 其中:type为消息的事件类型 timestamp 为消息的时间戳 orginService:消息的来源服务实例 destinationService:消息的目标服务实例,**代表了总线上的所有服务实例 在本例子中,我们可以看到每个服务的ackId都来自于 type为RefreshRemoteApplicationEvent的服务ID 5、运行服务 我们先通过gradle的build任务进行打包会得到如下文件:xxxx.jar与xxx.jar.orginal 那么进入到对应目录下 启动两个服务并注册到注册中心 命令如下: java -Dserver.port=8300 -Dspring.profiles.active=local -jar xxxx.jar java -Dserver.port=8200 -Dspring.profiles.active=local -jar xxxx.jar 注意一定不要在application.yml配置如上参数,否则通过(-Dxxx=xxx)系统变量设置的值将不会生效 此时我们更改config对应的配置并commit后,在运行步骤4,就可以拿到最新的结果了,再次附上相关代码: StudentConfig: package com.hzgj.lyrk.order.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @ConfigurationProperties(prefix = "student") @Configuration public class StudentConfig { private String name; private String age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public String toString() { return "StudentConfig{" + "name='" + name + '\'' + ", age='" + age + '\'' + '}'; } } View Code order-server-local: student: name: student_local age: 17 View Code
一、关于Spring-Cloud-Stream Spring Cloud Stream本质上就是整合了Spring Boot和Spring Integration,实现了一套轻量级的消息驱动的微服务框架。通过使用Spring Cloud Stream,可以有效地简化开发人员对消息中间件的使用复杂度,让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。 在这里我先放一张官网的图: 应用程序通过Spring Cloud Stream注入到输入和输出通道与外界进行通信。根据此规则我们很容易的实现消息传递,订阅消息与消息中转。并且当需要切换消息中间件时,几乎不需要修改代码,只需要变更配置就行了。 在用例图中 Inputs代表了应用程序监听消息 、outputs代表发送消息、binder的话大家可以理解为将应用程序与消息中间件隔离的抽象,类似于三层架构下利用dao屏蔽service与数据库的实现的原理。 springcloud默认提供了rabbitmq与kafka的实现。 二、springcloud集成kafka 1、添加gradle依赖: dependencies{ compile('org.springframework.cloud:spring-cloud-stream') compile('org.springframework.cloud:spring-cloud-stream-binder-kafka') compile('org.springframework.kafka:spring-kafka') } View Code 2、定义一个接口: spring-cloud-stream已经给我们定义了最基本的输入与输出接口,他们分别是 Source,Sink, Processor Sink接口: package org.springframework.cloud.stream.messaging; import org.springframework.cloud.stream.annotation.Input; import org.springframework.messaging.SubscribableChannel; public interface Sink { String INPUT = "input"; @Input("input") SubscribableChannel input(); } View Code Source接口: package org.springframework.cloud.stream.messaging; import org.springframework.cloud.stream.annotation.Output; import org.springframework.messaging.MessageChannel; public interface Source { String OUTPUT = "output"; @Output("output") MessageChannel output(); } View Code Processor接口: package org.springframework.cloud.stream.messaging; public interface Processor extends Source, Sink { } View Code 这里面Processor这个接口既定义输入通道又定义了输出通道。同时我们也可以自己定义通道接口,代码如下: package com.bdqn.lyrk.shop.channel; import org.springframework.cloud.stream.annotation.Input; import org.springframework.cloud.stream.annotation.Output; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.SubscribableChannel; public interface ShopChannel { /** * 发消息的通道名称 */ String SHOP_OUTPUT = "shop_output"; /** * 消息的订阅通道名称 */ String SHOP_INPUT = "shop_input"; /** * 发消息的通道 * * @return */ @Output(SHOP_OUTPUT) MessageChannel sendShopMessage(); /** * 收消息的通道 * * @return */ @Input(SHOP_INPUT) SubscribableChannel recieveShopMessage(); } View Code 3、定义服务类 package com.bdqn.lyrk.shop.server; import com.bdqn.lyrk.shop.channel.ShopChannel; import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.MessageBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController public class ShopService { @Resource(name = ShopChannel.SHOP_OUTPUT) private MessageChannel sendShopMessageChannel; @GetMapping("/sendMsg") public String sendShopMessage(String content) { boolean isSendSuccess = sendShopMessageChannel. send(MessageBuilder.withPayload(content).build()); return isSendSuccess ? "发送成功" : "发送失败"; } @StreamListener(ShopChannel.SHOP_INPUT) public void receive(Message<String> message) { System.out.println(message.getPayload()); } } View Code 这里面大家注意 @StreamListener。这个注解可以监听输入通道里的消息内容,注解里面的属性指定我们刚才定义的输入通道名称,而MessageChannel则可以通过 输出通道发送消息。使用@Resource注入时需要指定我们刚才定义的输出通道名称 4、定义启动类 package com.bdqn.lyrk.shop; import com.bdqn.lyrk.shop.channel.ShopChannel; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.stream.annotation.EnableBinding; @SpringBootApplication @EnableBinding(ShopChannel.class) public class ShopServerApplication { public static void main(String[] args) { SpringApplication.run(ShopServerApplication.class, args); } } View Code 注意@EnableBinding注解,这个注解指定刚才我们定义消息通道的接口名称,当然这里也可以传多个相关的接口 5、定义application.yml文件 spring: application: name: shop-server cloud: stream: bindings: #配置自己定义的通道与哪个中间件交互 shop_input: #ShopChannel里Input和Output的值 destination: zhibo #目标主题 shop_output: destination: zhibo default-binder: kafka #默认的binder是kafka kafka: bootstrap-servers: localhost:9092 #kafka服务地址 consumer: group-id: consumer1 producer: key-serializer: org.apache.kafka.common.serialization.ByteArraySerializer value-serializer: org.apache.kafka.common.serialization.ByteArraySerializer client-id: producer1 server: port: 8100 View Code 这里是重头戏,我们必须指定所有通道对应的消息主题,同时指定默认的binder为kafka,紧接着定义Spring-kafka的外部化配置,在这里指定producer的序列化类为ByteArraySerializer 启动程序成功后,我们访问 http://localhost:8100/sendMsg?content=2 即可得到如下结果
一、kafka简介 kafka,ActiveMQ,RabbitMQ是当今最流行的分布式消息中间件,其中kafka在性能及吞吐量方面是三者中的佼佼者,不过最近查阅官网时,官方与它的定义为一个分布式流媒体平台。kafka最主要有以下几个方面作用: 发布和订阅记录流,类似于消息队列或企业消息传递系统。 以容错持久的方式存储记录流。 处理记录发生的流 kafka有四个比较核心的API 分别为: producer:允许应用程序发布一个消息至一个或多个kafka的topic中 consumer:允许应用程序订阅一个或多个主题,并处理所产生的对他们记录的数据流 stream-api: 允许应用程序从一个或多个主题上消费数据然后将消费的数据输出到一个或多个其他的主题当中,有效地变换所述输入流,以输出流。类似于数据中转站的作用 connector-api:允许构建或运行可重复使用的生产者或消费者,将topic链接到现有的应用程序或数据系统。官网给我们的示意图: kafka关键名词解释: producer:生产者。 consumer:消费者。 topic: 消息以topic为类别记录,每一类的消息称之为一个主题(Topic)。为了提高吞吐量,每个消息主题又会有多个分区 broker:以集群的方式运行,可以由一个或多个服务组成,每个服务叫做一个broker;消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。 每个消息(也叫作record记录,也被称为消息)是由一个key,一个value和时间戳构成。 主题与日志: 每一个分区(partition)都是一个顺序的、不可变的消息队列,并且可以持续的添加。分区中的消息都被分了一个序列号,称之为偏移量(offset),在每个分区中此偏移量都是唯一的。Kafka集群保持所有的消息,直到它们过期,无论消息是否被消费了。实际上消费者所持有的仅有的元数据就是这个偏移量,也就是消费者在这个log中的位置。 这个偏移量由消费者控制:正常情况当消费者消费消息的时候,偏移量也线性的的增加。但是实际偏移量由消费者控制,消费者可以将偏移量重置为更老的一个偏移量,重新读取消息。 可以看到这种设计对消费者来说操作自如, 一个消费者的操作不会影响其它消费者对此log的处理。 再说说分区。Kafka中采用分区可以处理更多的消息,不受单台服务器的限制。Topic拥有多个分区意味着它可以不受限的处理更多的数据。 二、kafka速成 1、下载kafka并解压 kafka下载地址,注意kafka需要zookeeper的服务,因此请确保kafka服务启动之前先运行zookeeper,请参考这篇文章。在kafka的bin目录下有 windows的文件夹 用于在windows环境下启动kafka 2、启动kafka服务 > bin/kafka-server-start.sh config/server.properties [2013-04-22 15:01:47,028] INFO Verifying properties (kafka.utils.VerifiableProperties) [2013-04-22 15:01:47,051] INFO Property socket.send.buffer.bytes is overridden to 1048576 (kafka.utils.VerifiableProperties) ... 3、创建一个主题 我们用一个分区和一个副本创建一个名为“test”的主题: > bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test 然后我们可以运行如下命令查看是否已经创建成功: > bin/kafka-topics.sh --list --zookeeper localhost:2181 test 当发送的主题不存在且想自动创建主题时,我们可以编辑config/server.properties auto.create.topics.enable=true default.replication.factor=3 4、发送消息 Kafka附带一个命令行客户端,它将从文件或标准输入中获取输入,并将其作为消息发送到Kafka集群。默认情况下,每行将作为单独的消息发送。 > bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test This is a message This is another message 5、消费消息 > bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning This is a message This is another message 6、集群搭建 首先我们为每个代理创建一个配置文件(在Windows上使用该copy命令): > cp config/server.properties config/server-1.properties > cp config/server.properties config/server-2.properties 分别编辑上述文件: config/server-1.properties: broker.id=1 listeners=PLAINTEXT://:9093 log.dir=/tmp/kafka-logs-1 config/server-2.properties: broker.id=2 listeners=PLAINTEXT://:9094 log.dir=/tmp/kafka-logs-2 分别启动: > bin/kafka-server-start.sh config/server-1.properties & ... > bin/kafka-server-start.sh config/server-2.properties & ... 现在创建一个复制因子为三的新主题: > bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 1 --topic my-replicated-topic 我们可以通过以下命令查看状态: > bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic Topic:my-replicated-topic PartitionCount:1 ReplicationFactor:3 Configs: Topic: my-replicated-topic Partition: 0 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0 7、外网配置kafka注意事项 请编辑server.properties添加如下配置: broker.id主要做集群时区别的编号 port 默认kafka端口号 host.name 设置为云内网地址 advertised.host.name 设置为云外网映射地址 三、spring中使用kafka 1、编辑gradle配置文件: dependencies { // https://mvnrepository.com/artifact/org.springframework/spring-context compile group: 'org.springframework', name: 'spring-context', version: '5.0.4.RELEASE' // https://mvnrepository.com/artifact/org.springframework/spring-web compile group: 'org.springframework', name: 'spring-web', version: '5.0.4.RELEASE' // https://mvnrepository.com/artifact/org.springframework/spring-context-support compile group: 'org.springframework', name: 'spring-context-support', version: '5.0.4.RELEASE' // https://mvnrepository.com/artifact/org.springframework/spring-webmvc compile group: 'org.springframework', name: 'spring-webmvc', version: '5.0.4.RELEASE' // https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka compile group: 'org.springframework.kafka', name: 'spring-kafka', version: '2.1.4.RELEASE' // https://mvnrepository.com/artifact/org.slf4j/slf4j-api compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' // https://mvnrepository.com/artifact/ch.qos.logback/logback-core compile group: 'ch.qos.logback', name: 'logback-core', version: '1.2.3' // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic testCompile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' testCompile group: 'junit', name: 'junit', version: '4.12' } View Code 2、编写AppConfig配置文件类: package com.hzgj.lyrk.spring.study.config; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.annotation.EnableKafka; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; import org.springframework.kafka.config.KafkaListenerContainerFactory; import org.springframework.kafka.core.*; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Configuration @EnableKafka @ComponentScan public class AppConfig { @Bean public ProducerFactory<String, String> producerFactory() { Map<String, Object> props = new HashMap<>(8); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); return new DefaultKafkaProducerFactory<>(props); } @Bean public KafkaTemplate<String, String> kafkaTemplate() { return new KafkaTemplate<>(producerFactory(), true); } @Bean public ConsumerFactory<String, String> consumerFactory() { Map<String, Object> props = new HashMap<>(8); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); return new DefaultKafkaConsumerFactory<>(props); } @Bean public KafkaListenerContainerFactory kafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConcurrency(3); factory.setConsumerFactory(consumerFactory()); factory.getContainerProperties().setPollTimeout(3000); return factory; } @Component static class Listener { @KafkaListener(id="client_one",topics = "test") public void receive(String message) { System.out.println("收到的消息为:" + message); } @KafkaListener(id="client_two",topics = "test1") public void receive(Integer message) { System.out.println("收到的的Integer消息为:" + message); } } } View Code 3. 编写Main方法 package com.hzgj.lyrk.spring.study; import com.hzgj.lyrk.spring.study.config.AppConfig; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.support.SendResult; import org.springframework.util.concurrent.ListenableFutureCallback; public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); KafkaTemplate<String, String> kafkaTemplate = applicationContext.getBean(KafkaTemplate.class); kafkaTemplate.send("test", 0,"msg","{\"id\":2}").addCallback(new ListenableFutureCallback<SendResult<String, String>>() { @Override public void onFailure(Throwable ex) { ex.printStackTrace(); } @Override public void onSuccess(SendResult<String, String> result) { System.out.println("发送消息成功...."); } }); } } View Code 执行成功后得到如下结果:
一、何谓BeanProcessor BeanPostProcessor是SpringFramework里非常重要的核心接口之一,我先贴出一段源代码: /* * Copyright 2002-2015 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.beans.factory.config; import org.springframework.beans.BeansException; /** * Factory hook that allows for custom modification of new bean instances, * e.g. checking for marker interfaces or wrapping them with proxies. * * <p>ApplicationContexts can autodetect BeanPostProcessor beans in their * bean definitions and apply them to any beans subsequently created. * Plain bean factories allow for programmatic registration of post-processors, * applying to all beans created through this factory. * * <p>Typically, post-processors that populate beans via marker interfaces * or the like will implement {@link #postProcessBeforeInitialization}, * while post-processors that wrap beans with proxies will normally * implement {@link #postProcessAfterInitialization}. * * @author Juergen Hoeller * @since 10.10.2003 * @see InstantiationAwareBeanPostProcessor * @see DestructionAwareBeanPostProcessor * @see ConfigurableBeanFactory#addBeanPostProcessor * @see BeanFactoryPostProcessor */ public interface BeanPostProcessor { /** * Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean * initialization callbacks (like InitializingBean's {@code afterPropertiesSet} * or a custom init-method). The bean will already be populated with property values. * The returned bean instance may be a wrapper around the original. * @param bean the new bean instance * @param beanName the name of the bean * @return the bean instance to use, either the original or a wrapped one; * if {@code null}, no subsequent BeanPostProcessors will be invoked * @throws org.springframework.beans.BeansException in case of errors * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet */ Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; /** * Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean * initialization callbacks (like InitializingBean's {@code afterPropertiesSet} * or a custom init-method). The bean will already be populated with property values. * The returned bean instance may be a wrapper around the original. * <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean * instance and the objects created by the FactoryBean (as of Spring 2.0). The * post-processor can decide whether to apply to either the FactoryBean or created * objects or both through corresponding {@code bean instanceof FactoryBean} checks. * <p>This callback will also be invoked after a short-circuiting triggered by a * {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method, * in contrast to all other BeanPostProcessor callbacks. * @param bean the new bean instance * @param beanName the name of the bean * @return the bean instance to use, either the original or a wrapped one; * if {@code null}, no subsequent BeanPostProcessors will be invoked * @throws org.springframework.beans.BeansException in case of errors * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet * @see org.springframework.beans.factory.FactoryBean */ Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; } View Code 在这里我先简单解释一下其注释的含义: 这个接口允许我们自定义修改新bean的一个实例,比如说:检查它们的接口或者将他们包装成代理对象等,ApplicationContexts能自动察觉到我们在BeanProcessor里对对象作出的改变,并在后来创建该对象时应用其对应的改变。 这两个方法分别对应IOC容器对对象初始化前的操作和对象初始化后的操作 下面我们来演示一个例子: StudentEntity: package org.hzgj.spring.study.entity; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class StudentEntity { private Integer id; private String name; private Map<String,String> memerories= new HashMap<>(); public Map<String, String> getMemerories() { return memerories; } public void setMemerories(Map<String, String> memerories) { this.memerories = memerories; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public StudentEntity() { // System.out.println("studentEntity initializer...."); } @Override public String toString() { return "StudentEntity{" + "id=" + id + ", name='" + name + '\'' + '}'; } private List<String> hobbies=new ArrayList<>(); public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } } View Code TestBeanPostProcessor: package org.hzgj.spring.study.context; import org.hzgj.spring.study.entity.StudentEntity; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class TestBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof StudentEntity) { StudentEntity studentEntity = new StudentEntity(); studentEntity.setName(beanName); return studentEntity; } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof StudentEntity) System.out.println(bean); return bean; } } View Code Main方法: package org.hzgj.spring.study; import org.hzgj.spring.study.entity.StudentEntity; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); StudentEntity studentEntity = applicationContext.getBean(StudentEntity.class); System.out.println(studentEntity.getName()); } } View Code spring-config.xml关键代码: <bean id="studentEntity" class="org.hzgj.spring.study.entity.StudentEntity" depends-on="studentServiceWithFactory"> <property name="hobbies" ref="hobbies"> </property> <property name="memerories"> <map> <entry key="glad" value="play"/> <entry key="cry" value="cry"/> </map> </property> <property name="name" value="admin"/> </bean> <bean id="beanPostProcessor" class="org.hzgj.spring.study.context.TestBeanPostProcessor" /> View Code 运行得到如下结果: 我们可以看到在配置文件里定义的bean属性已经发生改变 二、SpringFramework中BeanPostProcessor经典应用场景 1.初始化BeanPostProcessor的源码 根据 ClassPathXmlApplicationContext的构造方法,我们可以看到该类初始化的时候会调用refresh(): /** * Create a new ClassPathXmlApplicationContext with the given parent, * loading the definitions from the given XML files. * @param configLocations array of resource locations * @param refresh whether to automatically refresh the context, * loading all bean definitions and creating all singletons. * Alternatively, call refresh manually after further configuring the context. * @param parent the parent context * @throws BeansException if context creation failed * @see #refresh() */ public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } View Code 那么紧接着在AbstractApplicationContext中找到refresh()方法: @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } } View Code 那么在这里Spring会进行一系列的初始化操作,我们请留意registerBeanPostProcessors(beanFactory);这句代码,追踪一下我们可以看到在该方法里注册Processor: public static void registerBeanPostProcessors( ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) { String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false); // Register BeanPostProcessorChecker that logs an info message when // a bean is created during BeanPostProcessor instantiation, i.e. when // a bean is not eligible for getting processed by all BeanPostProcessors. int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length; beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount)); // Separate between BeanPostProcessors that implement PriorityOrdered, // Ordered, and the rest. List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanPostProcessor>(); List<BeanPostProcessor> internalPostProcessors = new ArrayList<BeanPostProcessor>(); List<String> orderedPostProcessorNames = new ArrayList<String>(); List<String> nonOrderedPostProcessorNames = new ArrayList<String>(); for (String ppName : postProcessorNames) { if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); priorityOrderedPostProcessors.add(pp); if (pp instanceof MergedBeanDefinitionPostProcessor) { internalPostProcessors.add(pp); } } else if (beanFactory.isTypeMatch(ppName, Ordered.class)) { orderedPostProcessorNames.add(ppName); } else { nonOrderedPostProcessorNames.add(ppName); } } // First, register the BeanPostProcessors that implement PriorityOrdered. sortPostProcessors(priorityOrderedPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors); // Next, register the BeanPostProcessors that implement Ordered. List<BeanPostProcessor> orderedPostProcessors = new ArrayList<BeanPostProcessor>(); for (String ppName : orderedPostProcessorNames) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); orderedPostProcessors.add(pp); if (pp instanceof MergedBeanDefinitionPostProcessor) { internalPostProcessors.add(pp); } } sortPostProcessors(orderedPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, orderedPostProcessors); // Now, register all regular BeanPostProcessors. List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanPostProcessor>(); for (String ppName : nonOrderedPostProcessorNames) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); nonOrderedPostProcessors.add(pp); if (pp instanceof MergedBeanDefinitionPostProcessor) { internalPostProcessors.add(pp); } } registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors); // Finally, re-register all internal BeanPostProcessors. sortPostProcessors(internalPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, internalPostProcessors); // Re-register post-processor for detecting inner beans as ApplicationListeners, // moving it to the end of the processor chain (for picking up proxies etc). beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext)); } View Code 2.@Autowired实现的代码跟踪 注意AutowiredAnnotationBeanPostProcessor最终也是BeanPostProcessor的实现类,具体类的描述我就不再这里阐述了,大家可自行查看源码 3、SpringBoot中的定制化内嵌web容器 这个EmbeddedServletContainerCustomizerBeanPostProcessor直接实现的就是BeanPostProcessor,该类下请关注如下方法: @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof ConfigurableEmbeddedServletContainer) { postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean); } return bean; } View Code private void postProcessBeforeInitialization( ConfigurableEmbeddedServletContainer bean) { for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) { customizer.customize(bean); } } View Code private Collection<EmbeddedServletContainerCustomizer> getCustomizers() { if (this.customizers == null) { // Look up does not include the parent context this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>( this.beanFactory .getBeansOfType(EmbeddedServletContainerCustomizer.class, false, false) .values()); Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE); this.customizers = Collections.unmodifiableList(this.customizers); } return this.customizers; } View Code 这里面有一个接口:EmbeddedServletContainerCustomizer 该接口有个实现类 ServerProperties 熟悉springboot外部化配置原理的同胞们其实一看便知 /* * Copyright 2012-2018 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; import java.io.File; import java.net.InetAddress; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.SessionCookieConfig; import javax.servlet.SessionTrackingMode; import io.undertow.Undertow.Builder; import io.undertow.UndertowOptions; import org.apache.catalina.Context; import org.apache.catalina.connector.Connector; import org.apache.catalina.valves.AccessLogValve; import org.apache.catalina.valves.ErrorReportValve; import org.apache.catalina.valves.RemoteIpValve; import org.apache.coyote.AbstractProtocol; import org.apache.coyote.ProtocolHandler; import org.apache.coyote.http11.AbstractHttp11Protocol; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.springframework.boot.autoconfigure.web.ServerProperties.Session.Cookie; import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.context.embedded.Compression; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.InitParameterConfiguringServletContextInitializer; import org.springframework.boot.context.embedded.JspServlet; import org.springframework.boot.context.embedded.Ssl; import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.undertow.UndertowBuilderCustomizer; import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.context.EnvironmentAware; import org.springframework.core.Ordered; import org.springframework.core.env.Environment; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * {@link ConfigurationProperties} for a web server (e.g. port and path settings). Will be * used to customize an {@link EmbeddedServletContainerFactory} when an * {@link EmbeddedServletContainerCustomizerBeanPostProcessor} is active. * * @author Dave Syer * @author Stephane Nicoll * @author Andy Wilkinson * @author Ivan Sopov * @author Marcos Barbero * @author Eddú Meléndez * @author Quinten De Swaef * @author Venil Noronha * @author Aurélien Leboulanger */ @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered { /** * Server HTTP port. */ private Integer port; /** * Network address to which the server should bind to. */ private InetAddress address; /** * Context path of the application. */ private String contextPath; /** * Display name of the application. */ private String displayName = "application"; @NestedConfigurationProperty private ErrorProperties error = new ErrorProperties(); /** * Path of the main dispatcher servlet. */ private String servletPath = "/"; /** * ServletContext parameters. */ private final Map<String, String> contextParameters = new HashMap<String, String>(); /** * If X-Forwarded-* headers should be applied to the HttpRequest. */ private Boolean useForwardHeaders; /** * Value to use for the Server response header (no header is sent if empty). */ private String serverHeader; /** * Maximum size in bytes of the HTTP message header. */ private int maxHttpHeaderSize = 0; // bytes /** * Maximum size in bytes of the HTTP post content. */ private int maxHttpPostSize = 0; // bytes /** * Time in milliseconds that connectors will wait for another HTTP request before * closing the connection. When not set, the connector's container-specific default * will be used. Use a value of -1 to indicate no (i.e. infinite) timeout. */ private Integer connectionTimeout; private Session session = new Session(); @NestedConfigurationProperty private Ssl ssl; @NestedConfigurationProperty private Compression compression = new Compression(); @NestedConfigurationProperty private JspServlet jspServlet; private final Tomcat tomcat = new Tomcat(); private final Jetty jetty = new Jetty(); private final Undertow undertow = new Undertow(); private Environment environment; @Override public int getOrder() { return 0; } @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void customize(ConfigurableEmbeddedServletContainer container) { if (getPort() != null) { container.setPort(getPort()); } if (getAddress() != null) { container.setAddress(getAddress()); } if (getContextPath() != null) { container.setContextPath(getContextPath()); } if (getDisplayName() != null) { container.setDisplayName(getDisplayName()); } if (getSession().getTimeout() != null) { container.setSessionTimeout(getSession().getTimeout()); } container.setPersistSession(getSession().isPersistent()); container.setSessionStoreDir(getSession().getStoreDir()); if (getSsl() != null) { container.setSsl(getSsl()); } if (getJspServlet() != null) { container.setJspServlet(getJspServlet()); } if (getCompression() != null) { container.setCompression(getCompression()); } container.setServerHeader(getServerHeader()); if (container instanceof TomcatEmbeddedServletContainerFactory) { getTomcat().customizeTomcat(this, (TomcatEmbeddedServletContainerFactory) container); } if (container instanceof JettyEmbeddedServletContainerFactory) { getJetty().customizeJetty(this, (JettyEmbeddedServletContainerFactory) container); } if (container instanceof UndertowEmbeddedServletContainerFactory) { getUndertow().customizeUndertow(this, (UndertowEmbeddedServletContainerFactory) container); } container.addInitializers(new SessionConfiguringInitializer(this.session)); container.addInitializers(new InitParameterConfiguringServletContextInitializer( getContextParameters())); } public String getServletMapping() { if (this.servletPath.equals("") || this.servletPath.equals("/")) { return "/"; } if (this.servletPath.contains("*")) { return this.servletPath; } if (this.servletPath.endsWith("/")) { return this.servletPath + "*"; } return this.servletPath + "/*"; } public String getPath(String path) { String prefix = getServletPrefix(); if (!path.startsWith("/")) { path = "/" + path; } return prefix + path; } public String getServletPrefix() { String result = this.servletPath; if (result.contains("*")) { result = result.substring(0, result.indexOf("*")); } if (result.endsWith("/")) { result = result.substring(0, result.length() - 1); } return result; } public String[] getPathsArray(Collection<String> paths) { String[] result = new String[paths.size()]; int i = 0; for (String path : paths) { result[i++] = getPath(path); } return result; } public String[] getPathsArray(String[] paths) { String[] result = new String[paths.length]; int i = 0; for (String path : paths) { result[i++] = getPath(path); } return result; } public void setLoader(String value) { // no op to support Tomcat running as a traditional container (not embedded) } public Integer getPort() { return this.port; } public void setPort(Integer port) { this.port = port; } public InetAddress getAddress() { return this.address; } public void setAddress(InetAddress address) { this.address = address; } public String getContextPath() { return this.contextPath; } public void setContextPath(String contextPath) { this.contextPath = cleanContextPath(contextPath); } private String cleanContextPath(String contextPath) { if (StringUtils.hasText(contextPath) && contextPath.endsWith("/")) { return contextPath.substring(0, contextPath.length() - 1); } return contextPath; } public String getDisplayName() { return this.displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } public String getServletPath() { return this.servletPath; } public void setServletPath(String servletPath) { Assert.notNull(servletPath, "ServletPath must not be null"); this.servletPath = servletPath; } public Map<String, String> getContextParameters() { return this.contextParameters; } public Boolean isUseForwardHeaders() { return this.useForwardHeaders; } public void setUseForwardHeaders(Boolean useForwardHeaders) { this.useForwardHeaders = useForwardHeaders; } public String getServerHeader() { return this.serverHeader; } public void setServerHeader(String serverHeader) { this.serverHeader = serverHeader; } public int getMaxHttpHeaderSize() { return this.maxHttpHeaderSize; } public void setMaxHttpHeaderSize(int maxHttpHeaderSize) { this.maxHttpHeaderSize = maxHttpHeaderSize; } @Deprecated @DeprecatedConfigurationProperty(reason = "Use dedicated property for each container.") public int getMaxHttpPostSize() { return this.maxHttpPostSize; } @Deprecated public void setMaxHttpPostSize(int maxHttpPostSize) { this.maxHttpPostSize = maxHttpPostSize; this.jetty.setMaxHttpPostSize(maxHttpPostSize); this.tomcat.setMaxHttpPostSize(maxHttpPostSize); this.undertow.setMaxHttpPostSize(maxHttpPostSize); } protected final boolean getOrDeduceUseForwardHeaders() { if (this.useForwardHeaders != null) { return this.useForwardHeaders; } CloudPlatform platform = CloudPlatform.getActive(this.environment); return (platform == null ? false : platform.isUsingForwardHeaders()); } public Integer getConnectionTimeout() { return this.connectionTimeout; } public void setConnectionTimeout(Integer connectionTimeout) { this.connectionTimeout = connectionTimeout; } public ErrorProperties getError() { return this.error; } public Session getSession() { return this.session; } public void setSession(Session session) { this.session = session; } public Ssl getSsl() { return this.ssl; } public void setSsl(Ssl ssl) { this.ssl = ssl; } public Compression getCompression() { return this.compression; } public JspServlet getJspServlet() { return this.jspServlet; } public void setJspServlet(JspServlet jspServlet) { this.jspServlet = jspServlet; } public Tomcat getTomcat() { return this.tomcat; } public Jetty getJetty() { return this.jetty; } public Undertow getUndertow() { return this.undertow; } public static class Session { /** * Session timeout in seconds. */ private Integer timeout; /** * Session tracking modes (one or more of the following: "cookie", "url", "ssl"). */ private Set<SessionTrackingMode> trackingModes; /** * Persist session data between restarts. */ private boolean persistent; /** * Directory used to store session data. */ private File storeDir; private Cookie cookie = new Cookie(); public Cookie getCookie() { return this.cookie; } public Integer getTimeout() { return this.timeout; } public void setTimeout(Integer sessionTimeout) { this.timeout = sessionTimeout; } public Set<SessionTrackingMode> getTrackingModes() { return this.trackingModes; } public void setTrackingModes(Set<SessionTrackingMode> trackingModes) { this.trackingModes = trackingModes; } public boolean isPersistent() { return this.persistent; } public void setPersistent(boolean persistent) { this.persistent = persistent; } public File getStoreDir() { return this.storeDir; } public void setStoreDir(File storeDir) { this.storeDir = storeDir; } public static class Cookie { /** * Session cookie name. */ private String name; /** * Domain for the session cookie. */ private String domain; /** * Path of the session cookie. */ private String path; /** * Comment for the session cookie. */ private String comment; /** * "HttpOnly" flag for the session cookie. */ private Boolean httpOnly; /** * "Secure" flag for the session cookie. */ private Boolean secure; /** * Maximum age of the session cookie in seconds. */ private Integer maxAge; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getDomain() { return this.domain; } public void setDomain(String domain) { this.domain = domain; } public String getPath() { return this.path; } public void setPath(String path) { this.path = path; } public String getComment() { return this.comment; } public void setComment(String comment) { this.comment = comment; } public Boolean getHttpOnly() { return this.httpOnly; } public void setHttpOnly(Boolean httpOnly) { this.httpOnly = httpOnly; } public Boolean getSecure() { return this.secure; } public void setSecure(Boolean secure) { this.secure = secure; } public Integer getMaxAge() { return this.maxAge; } public void setMaxAge(Integer maxAge) { this.maxAge = maxAge; } } } public static class Tomcat { /** * Access log configuration. */ private final Accesslog accesslog = new Accesslog(); /** * Regular expression that matches proxies that are to be trusted. */ private String internalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 10/8 + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16 + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/16 + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 127/8 + "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 172.16/12 + "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}"; /** * Header that holds the incoming protocol, usually named "X-Forwarded-Proto". */ private String protocolHeader; /** * Value of the protocol header that indicates that the incoming request uses SSL. */ private String protocolHeaderHttpsValue = "https"; /** * Name of the HTTP header used to override the original port value. */ private String portHeader = "X-Forwarded-Port"; /** * Name of the http header from which the remote ip is extracted.. */ private String remoteIpHeader; /** * Tomcat base directory. If not specified a temporary directory will be used. */ private File basedir; /** * Delay in seconds between the invocation of backgroundProcess methods. */ private int backgroundProcessorDelay = 30; // seconds /** * Maximum amount of worker threads. */ private int maxThreads = 0; // Number of threads in protocol handler /** * Minimum amount of worker threads. */ private int minSpareThreads = 0; // Minimum spare threads in protocol handler /** * Maximum size in bytes of the HTTP post content. */ private int maxHttpPostSize = 0; // bytes /** * Maximum size in bytes of the HTTP message header. */ private int maxHttpHeaderSize = 0; // bytes /** * Whether requests to the context root should be redirected by appending a / to * the path. */ private Boolean redirectContextRoot; /** * Character encoding to use to decode the URI. */ private Charset uriEncoding; /** * Maximum number of connections that the server will accept and process at any * given time. Once the limit has been reached, the operating system may still * accept connections based on the "acceptCount" property. */ private int maxConnections = 0; /** * Maximum queue length for incoming connection requests when all possible request * processing threads are in use. */ private int acceptCount = 0; /** * Comma-separated list of additional patterns that match jars to ignore for TLD * scanning. The special '?' and '*' characters can be used in the pattern to * match one and only one character and zero or more characters respectively. */ private List<String> additionalTldSkipPatterns = new ArrayList<String>(); public int getMaxThreads() { return this.maxThreads; } public void setMaxThreads(int maxThreads) { this.maxThreads = maxThreads; } public int getMinSpareThreads() { return this.minSpareThreads; } public void setMinSpareThreads(int minSpareThreads) { this.minSpareThreads = minSpareThreads; } public int getMaxHttpPostSize() { return this.maxHttpPostSize; } public void setMaxHttpPostSize(int maxHttpPostSize) { this.maxHttpPostSize = maxHttpPostSize; } public Accesslog getAccesslog() { return this.accesslog; } public int getBackgroundProcessorDelay() { return this.backgroundProcessorDelay; } public void setBackgroundProcessorDelay(int backgroundProcessorDelay) { this.backgroundProcessorDelay = backgroundProcessorDelay; } public File getBasedir() { return this.basedir; } public void setBasedir(File basedir) { this.basedir = basedir; } public String getInternalProxies() { return this.internalProxies; } public void setInternalProxies(String internalProxies) { this.internalProxies = internalProxies; } public String getProtocolHeader() { return this.protocolHeader; } public void setProtocolHeader(String protocolHeader) { this.protocolHeader = protocolHeader; } public String getProtocolHeaderHttpsValue() { return this.protocolHeaderHttpsValue; } public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) { this.protocolHeaderHttpsValue = protocolHeaderHttpsValue; } public String getPortHeader() { return this.portHeader; } public void setPortHeader(String portHeader) { this.portHeader = portHeader; } public Boolean getRedirectContextRoot() { return this.redirectContextRoot; } public void setRedirectContextRoot(Boolean redirectContextRoot) { this.redirectContextRoot = redirectContextRoot; } public String getRemoteIpHeader() { return this.remoteIpHeader; } public void setRemoteIpHeader(String remoteIpHeader) { this.remoteIpHeader = remoteIpHeader; } public Charset getUriEncoding() { return this.uriEncoding; } public void setUriEncoding(Charset uriEncoding) { this.uriEncoding = uriEncoding; } public int getMaxConnections() { return this.maxConnections; } public void setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; } public int getAcceptCount() { return this.acceptCount; } public void setAcceptCount(int acceptCount) { this.acceptCount = acceptCount; } public List<String> getAdditionalTldSkipPatterns() { return this.additionalTldSkipPatterns; } public void setAdditionalTldSkipPatterns(List<String> additionalTldSkipPatterns) { this.additionalTldSkipPatterns = additionalTldSkipPatterns; } void customizeTomcat(ServerProperties serverProperties, TomcatEmbeddedServletContainerFactory factory) { if (getBasedir() != null) { factory.setBaseDirectory(getBasedir()); } factory.setBackgroundProcessorDelay(Tomcat.this.backgroundProcessorDelay); customizeRemoteIpValve(serverProperties, factory); if (this.maxThreads > 0) { customizeMaxThreads(factory); } if (this.minSpareThreads > 0) { customizeMinThreads(factory); } int maxHttpHeaderSize = (serverProperties.getMaxHttpHeaderSize() > 0 ? serverProperties.getMaxHttpHeaderSize() : this.maxHttpHeaderSize); if (maxHttpHeaderSize > 0) { customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize); } if (this.maxHttpPostSize != 0) { customizeMaxHttpPostSize(factory, this.maxHttpPostSize); } if (this.accesslog.enabled) { customizeAccessLog(factory); } if (getUriEncoding() != null) { factory.setUriEncoding(getUriEncoding()); } if (serverProperties.getConnectionTimeout() != null) { customizeConnectionTimeout(factory, serverProperties.getConnectionTimeout()); } if (this.redirectContextRoot != null) { customizeRedirectContextRoot(factory, this.redirectContextRoot); } if (this.maxConnections > 0) { customizeMaxConnections(factory); } if (this.acceptCount > 0) { customizeAcceptCount(factory); } if (!ObjectUtils.isEmpty(this.additionalTldSkipPatterns)) { factory.getTldSkipPatterns().addAll(this.additionalTldSkipPatterns); } if (serverProperties.getError() .getIncludeStacktrace() == ErrorProperties.IncludeStacktrace.NEVER) { customizeErrorReportValve(factory); } } private void customizeErrorReportValve( TomcatEmbeddedServletContainerFactory factory) { factory.addContextCustomizers(new TomcatContextCustomizer() { @Override public void customize(Context context) { ErrorReportValve valve = new ErrorReportValve(); valve.setShowServerInfo(false); valve.setShowReport(false); context.getParent().getPipeline().addValve(valve); } }); } private void customizeAcceptCount(TomcatEmbeddedServletContainerFactory factory) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { @Override @SuppressWarnings("deprecation") public void customize(Connector connector) { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractProtocol) { AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler; protocol.setBacklog(Tomcat.this.acceptCount); } } }); } private void customizeMaxConnections( TomcatEmbeddedServletContainerFactory factory) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { @Override public void customize(Connector connector) { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractProtocol) { AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler; protocol.setMaxConnections(Tomcat.this.maxConnections); } } }); } private void customizeConnectionTimeout( TomcatEmbeddedServletContainerFactory factory, final int connectionTimeout) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { @Override public void customize(Connector connector) { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractProtocol) { AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler; protocol.setConnectionTimeout(connectionTimeout); } } }); } private void customizeRemoteIpValve(ServerProperties properties, TomcatEmbeddedServletContainerFactory factory) { String protocolHeader = getProtocolHeader(); String remoteIpHeader = getRemoteIpHeader(); // For back compatibility the valve is also enabled if protocol-header is set if (StringUtils.hasText(protocolHeader) || StringUtils.hasText(remoteIpHeader) || properties.getOrDeduceUseForwardHeaders()) { RemoteIpValve valve = new RemoteIpValve(); valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) ? protocolHeader : "X-Forwarded-Proto"); if (StringUtils.hasLength(remoteIpHeader)) { valve.setRemoteIpHeader(remoteIpHeader); } // The internal proxies default to a white list of "safe" internal IP // addresses valve.setInternalProxies(getInternalProxies()); valve.setPortHeader(getPortHeader()); valve.setProtocolHeaderHttpsValue(getProtocolHeaderHttpsValue()); // ... so it's safe to add this valve by default. factory.addEngineValves(valve); } } @SuppressWarnings("rawtypes") private void customizeMaxThreads(TomcatEmbeddedServletContainerFactory factory) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { @Override public void customize(Connector connector) { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractProtocol) { AbstractProtocol protocol = (AbstractProtocol) handler; protocol.setMaxThreads(Tomcat.this.maxThreads); } } }); } @SuppressWarnings("rawtypes") private void customizeMinThreads(TomcatEmbeddedServletContainerFactory factory) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { @Override public void customize(Connector connector) { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractProtocol) { AbstractProtocol protocol = (AbstractProtocol) handler; protocol.setMinSpareThreads(Tomcat.this.minSpareThreads); } } }); } @SuppressWarnings("rawtypes") private void customizeMaxHttpHeaderSize( TomcatEmbeddedServletContainerFactory factory, final int maxHttpHeaderSize) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { @Override public void customize(Connector connector) { ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractHttp11Protocol) { AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler; protocol.setMaxHttpHeaderSize(maxHttpHeaderSize); } } }); } private void customizeMaxHttpPostSize( TomcatEmbeddedServletContainerFactory factory, final int maxHttpPostSize) { factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { @Override public void customize(Connector connector) { connector.setMaxPostSize(maxHttpPostSize); } }); } private void customizeAccessLog(TomcatEmbeddedServletContainerFactory factory) { AccessLogValve valve = new AccessLogValve(); valve.setPattern(this.accesslog.getPattern()); valve.setDirectory(this.accesslog.getDirectory()); valve.setPrefix(this.accesslog.getPrefix()); valve.setSuffix(this.accesslog.getSuffix()); valve.setRenameOnRotate(this.accesslog.isRenameOnRotate()); valve.setRequestAttributesEnabled( this.accesslog.isRequestAttributesEnabled()); valve.setRotatable(this.accesslog.isRotate()); valve.setBuffered(this.accesslog.isBuffered()); valve.setFileDateFormat(this.accesslog.getFileDateFormat()); factory.addEngineValves(valve); } private void customizeRedirectContextRoot( TomcatEmbeddedServletContainerFactory factory, final boolean redirectContextRoot) { factory.addContextCustomizers(new TomcatContextCustomizer() { @Override public void customize(Context context) { context.setMapperContextRootRedirectEnabled(redirectContextRoot); } }); } public static class Accesslog { /** * Enable access log. */ private boolean enabled = false; /** * Format pattern for access logs. */ private String pattern = "common"; /** * Directory in which log files are created. Can be relative to the tomcat * base dir or absolute. */ private String directory = "logs"; /** * Log file name prefix. */ protected String prefix = "access_log"; /** * Log file name suffix. */ private String suffix = ".log"; /** * Enable access log rotation. */ private boolean rotate = true; /** * Defer inclusion of the date stamp in the file name until rotate time. */ private boolean renameOnRotate; /** * Date format to place in log file name. */ private String fileDateFormat = ".yyyy-MM-dd"; /** * Set request attributes for IP address, Hostname, protocol and port used for * the request. */ private boolean requestAttributesEnabled; /** * Buffer output such that it is only flushed periodically. */ private boolean buffered = true; public boolean isEnabled() { return this.enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public String getPattern() { return this.pattern; } public void setPattern(String pattern) { this.pattern = pattern; } public String getDirectory() { return this.directory; } public void setDirectory(String directory) { this.directory = directory; } public String getPrefix() { return this.prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return this.suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } public boolean isRotate() { return this.rotate; } public void setRotate(boolean rotate) { this.rotate = rotate; } public boolean isRenameOnRotate() { return this.renameOnRotate; } public void setRenameOnRotate(boolean renameOnRotate) { this.renameOnRotate = renameOnRotate; } public String getFileDateFormat() { return this.fileDateFormat; } public void setFileDateFormat(String fileDateFormat) { this.fileDateFormat = fileDateFormat; } public boolean isRequestAttributesEnabled() { return this.requestAttributesEnabled; } public void setRequestAttributesEnabled(boolean requestAttributesEnabled) { this.requestAttributesEnabled = requestAttributesEnabled; } public boolean isBuffered() { return this.buffered; } public void setBuffered(boolean buffered) { this.buffered = buffered; } } } public static class Jetty { /** * Maximum size in bytes of the HTTP post or put content. */ private int maxHttpPostSize = 0; // bytes /** * Number of acceptor threads to use. */ private Integer acceptors; /** * Number of selector threads to use. */ private Integer selectors; public int getMaxHttpPostSize() { return this.maxHttpPostSize; } public void setMaxHttpPostSize(int maxHttpPostSize) { this.maxHttpPostSize = maxHttpPostSize; } public Integer getAcceptors() { return this.acceptors; } public void setAcceptors(Integer acceptors) { this.acceptors = acceptors; } public Integer getSelectors() { return this.selectors; } public void setSelectors(Integer selectors) { this.selectors = selectors; } void customizeJetty(final ServerProperties serverProperties, JettyEmbeddedServletContainerFactory factory) { factory.setUseForwardHeaders(serverProperties.getOrDeduceUseForwardHeaders()); if (this.acceptors != null) { factory.setAcceptors(this.acceptors); } if (this.selectors != null) { factory.setSelectors(this.selectors); } if (serverProperties.getMaxHttpHeaderSize() > 0) { customizeMaxHttpHeaderSize(factory, serverProperties.getMaxHttpHeaderSize()); } if (this.maxHttpPostSize > 0) { customizeMaxHttpPostSize(factory, this.maxHttpPostSize); } if (serverProperties.getConnectionTimeout() != null) { customizeConnectionTimeout(factory, serverProperties.getConnectionTimeout()); } } private void customizeConnectionTimeout( JettyEmbeddedServletContainerFactory factory, final int connectionTimeout) { factory.addServerCustomizers(new JettyServerCustomizer() { @Override public void customize(Server server) { for (org.eclipse.jetty.server.Connector connector : server .getConnectors()) { if (connector instanceof AbstractConnector) { ((AbstractConnector) connector) .setIdleTimeout(connectionTimeout); } } } }); } private void customizeMaxHttpHeaderSize( JettyEmbeddedServletContainerFactory factory, final int maxHttpHeaderSize) { factory.addServerCustomizers(new JettyServerCustomizer() { @Override public void customize(Server server) { for (org.eclipse.jetty.server.Connector connector : server .getConnectors()) { try { for (ConnectionFactory connectionFactory : connector .getConnectionFactories()) { if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) { customize( (HttpConfiguration.ConnectionFactory) connectionFactory); } } } catch (NoSuchMethodError ex) { customizeOnJetty8(connector, maxHttpHeaderSize); } } } private void customize(HttpConfiguration.ConnectionFactory factory) { HttpConfiguration configuration = factory.getHttpConfiguration(); configuration.setRequestHeaderSize(maxHttpHeaderSize); configuration.setResponseHeaderSize(maxHttpHeaderSize); } private void customizeOnJetty8( org.eclipse.jetty.server.Connector connector, int maxHttpHeaderSize) { try { connector.getClass().getMethod("setRequestHeaderSize", int.class) .invoke(connector, maxHttpHeaderSize); connector.getClass().getMethod("setResponseHeaderSize", int.class) .invoke(connector, maxHttpHeaderSize); } catch (Exception ex) { throw new RuntimeException(ex); } } }); } private void customizeMaxHttpPostSize( JettyEmbeddedServletContainerFactory factory, final int maxHttpPostSize) { factory.addServerCustomizers(new JettyServerCustomizer() { @Override public void customize(Server server) { setHandlerMaxHttpPostSize(maxHttpPostSize, server.getHandlers()); } private void setHandlerMaxHttpPostSize(int maxHttpPostSize, Handler... handlers) { for (Handler handler : handlers) { if (handler instanceof ContextHandler) { ((ContextHandler) handler) .setMaxFormContentSize(maxHttpPostSize); } else if (handler instanceof HandlerWrapper) { setHandlerMaxHttpPostSize(maxHttpPostSize, ((HandlerWrapper) handler).getHandler()); } else if (handler instanceof HandlerCollection) { setHandlerMaxHttpPostSize(maxHttpPostSize, ((HandlerCollection) handler).getHandlers()); } } } }); } } public static class Undertow { /** * Maximum size in bytes of the HTTP post content. */ private long maxHttpPostSize = 0; // bytes /** * Size of each buffer in bytes. */ private Integer bufferSize; /** * Number of buffer per region. */ @Deprecated private Integer buffersPerRegion; /** * Number of I/O threads to create for the worker. */ private Integer ioThreads; /** * Number of worker threads. */ private Integer workerThreads; /** * Allocate buffers outside the Java heap. */ private Boolean directBuffers; private final Accesslog accesslog = new Accesslog(); public long getMaxHttpPostSize() { return this.maxHttpPostSize; } public void setMaxHttpPostSize(long maxHttpPostSize) { this.maxHttpPostSize = maxHttpPostSize; } public Integer getBufferSize() { return this.bufferSize; } public void setBufferSize(Integer bufferSize) { this.bufferSize = bufferSize; } @DeprecatedConfigurationProperty(reason = "The property is not used by Undertow. See https://issues.jboss.org/browse/UNDERTOW-587 for details") public Integer getBuffersPerRegion() { return this.buffersPerRegion; } public void setBuffersPerRegion(Integer buffersPerRegion) { this.buffersPerRegion = buffersPerRegion; } public Integer getIoThreads() { return this.ioThreads; } public void setIoThreads(Integer ioThreads) { this.ioThreads = ioThreads; } public Integer getWorkerThreads() { return this.workerThreads; } public void setWorkerThreads(Integer workerThreads) { this.workerThreads = workerThreads; } public Boolean getDirectBuffers() { return this.directBuffers; } public void setDirectBuffers(Boolean directBuffers) { this.directBuffers = directBuffers; } public Accesslog getAccesslog() { return this.accesslog; } void customizeUndertow(final ServerProperties serverProperties, UndertowEmbeddedServletContainerFactory factory) { if (this.bufferSize != null) { factory.setBufferSize(this.bufferSize); } if (this.ioThreads != null) { factory.setIoThreads(this.ioThreads); } if (this.workerThreads != null) { factory.setWorkerThreads(this.workerThreads); } if (this.directBuffers != null) { factory.setDirectBuffers(this.directBuffers); } if (this.accesslog.enabled != null) { factory.setAccessLogEnabled(this.accesslog.enabled); } factory.setAccessLogDirectory(this.accesslog.dir); factory.setAccessLogPattern(this.accesslog.pattern); factory.setAccessLogPrefix(this.accesslog.prefix); factory.setAccessLogSuffix(this.accesslog.suffix); factory.setAccessLogRotate(this.accesslog.rotate); factory.setUseForwardHeaders(serverProperties.getOrDeduceUseForwardHeaders()); if (serverProperties.getMaxHttpHeaderSize() > 0) { customizeMaxHttpHeaderSize(factory, serverProperties.getMaxHttpHeaderSize()); } if (this.maxHttpPostSize > 0) { customizeMaxHttpPostSize(factory, this.maxHttpPostSize); } if (serverProperties.getConnectionTimeout() != null) { customizeConnectionTimeout(factory, serverProperties.getConnectionTimeout()); } } private void customizeConnectionTimeout( UndertowEmbeddedServletContainerFactory factory, final int connectionTimeout) { factory.addBuilderCustomizers(new UndertowBuilderCustomizer() { @Override public void customize(Builder builder) { builder.setSocketOption(UndertowOptions.NO_REQUEST_TIMEOUT, connectionTimeout); } }); } private void customizeMaxHttpHeaderSize( UndertowEmbeddedServletContainerFactory factory, final int maxHttpHeaderSize) { factory.addBuilderCustomizers(new UndertowBuilderCustomizer() { @Override public void customize(Builder builder) { builder.setServerOption(UndertowOptions.MAX_HEADER_SIZE, maxHttpHeaderSize); } }); } private void customizeMaxHttpPostSize( UndertowEmbeddedServletContainerFactory factory, final long maxHttpPostSize) { factory.addBuilderCustomizers(new UndertowBuilderCustomizer() { @Override public void customize(Builder builder) { builder.setServerOption(UndertowOptions.MAX_ENTITY_SIZE, maxHttpPostSize); } }); } public static class Accesslog { /** * Enable access log. */ private Boolean enabled; /** * Format pattern for access logs. */ private String pattern = "common"; /** * Log file name prefix. */ protected String prefix = "access_log."; /** * Log file name suffix. */ private String suffix = "log"; /** * Undertow access log directory. */ private File dir = new File("logs"); /** * Enable access log rotation. */ private boolean rotate = true; public Boolean getEnabled() { return this.enabled; } public void setEnabled(Boolean enabled) { this.enabled = enabled; } public String getPattern() { return this.pattern; } public void setPattern(String pattern) { this.pattern = pattern; } public String getPrefix() { return this.prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return this.suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } public File getDir() { return this.dir; } public void setDir(File dir) { this.dir = dir; } public boolean isRotate() { return this.rotate; } public void setRotate(boolean rotate) { this.rotate = rotate; } } } /** * {@link ServletContextInitializer} to apply appropriate parts of the {@link Session} * configuration. */ private static class SessionConfiguringInitializer implements ServletContextInitializer { private final Session session; SessionConfiguringInitializer(Session session) { this.session = session; } @Override public void onStartup(ServletContext servletContext) throws ServletException { if (this.session.getTrackingModes() != null) { servletContext.setSessionTrackingModes(this.session.getTrackingModes()); } configureSessionCookie(servletContext.getSessionCookieConfig()); } private void configureSessionCookie(SessionCookieConfig config) { Cookie cookie = this.session.getCookie(); if (cookie.getName() != null) { config.setName(cookie.getName()); } if (cookie.getDomain() != null) { config.setDomain(cookie.getDomain()); } if (cookie.getPath() != null) { config.setPath(cookie.getPath()); } if (cookie.getComment() != null) { config.setComment(cookie.getComment()); } if (cookie.getHttpOnly() != null) { config.setHttpOnly(cookie.getHttpOnly()); } if (cookie.getSecure() != null) { config.setSecure(cookie.getSecure()); } if (cookie.getMaxAge() != null) { config.setMaxAge(cookie.getMaxAge()); } } } } View Code 在这个类里我们可以找到customizeTomcat方法,大家自行看看喽,类图如下:
一、摘自官网的一段描述 1.背景 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。 单一应用架构 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。 垂直应用架构 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。 分布式服务架构 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。 流动计算架构 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。 为什么我贴出这段话,它描述了互联网架构的演变,关键要素及dubbo存在的意义,可谓简约而不简单 官网地址,在这里关于dubbo的介绍我就不再这里阐述了,官网有中文的说明而且很详细 2、需求 在大规模服务化之前,应用可能只是通过 RMI 或 Hessian 等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,通过 F5 等硬件进行负载均衡。 当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。 此时需要一个服务注册中心,动态的注册和发现服务,使服务的位置透明。并通过在消费方获取服务提供方地址列表,实现软负载均衡和 Failover,降低对 F5 硬件负载均衡器的依赖,也能减少部分成本。 当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 这时,需要自动画出应用间的依赖关系图,以帮助架构师理清理关系。 接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器? 为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阀值,记录此时的访问量,再以此访问量乘以机器数反推总容量。 以上是 Dubbo 最基本的几个需求。 点评:其实前半段就是描述了注册中心必要性,后半段说明了监控与分析的重要性,恰好dubbo有独特的monitor模块 3、架构 这个图不多说了,描述了一个服务注册与发现的场景 : 服务容器负责启动,加载,运行服务提供者。 服务提供者在启动时,向注册中心注册自己提供的服务。 服务消费者在启动时,向注册中心订阅自己所需的服务。 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 二、使用spring-boot快速搭建dubbo 1.项目结构图 2. 编写service-api层代码 IStudentService: package com.hzgj.lyrk.dubbo.api; import com.hzgj.lyrk.dubbo.dto.StudentDTO; public interface IStudentService { StudentDTO getStudentById(Integer id); } View Code StudentDTO:注意必须实现serializable接口 package com.hzgj.lyrk.dubbo.dto; import lombok.Data; import java.io.Serializable; @Data public class StudentDTO implements Serializable { private Integer id; private String name; } View Code 3.编写student-server模块 3.1 首先添加gradle依赖项: dependencies { // testCompile group: 'junit', name: 'junit', version: '4.12' compile 'com.alibaba.boot:dubbo-spring-boot-starter:0.1.0' // https://mvnrepository.com/artifact/com.101tec/zkclient compile group: 'com.101tec', name: 'zkclient', version: '0.10' compile project(":service-api") } View Code 3.2 StudentServer: package com.hzgj.lyrk.dubbo.student.server; import com.alibaba.dubbo.config.annotation.Service; import com.hzgj.lyrk.dubbo.api.IStudentService; import com.hzgj.lyrk.dubbo.dto.StudentDTO; @Service public class StudentService implements IStudentService { @Override public StudentDTO getStudentById(Integer id) { StudentDTO studentDTO = new StudentDTO(); studentDTO.setId(id); studentDTO.setName("学号为" + id + "的学生"); return studentDTO; } } View Code 注意此处@Service要用 com.alibaba.dubbo.config.annotation.Service 3.3 编写启动类: package com.hzgj.lyrk.dubbo.student; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class StudentApplication { public static void main(String[] args) { SpringApplication.run(StudentApplication.class, args); } } View Code 3.4 application.yml文件 server: port: 8100 spring: application: name: student-server dubbo: application: name: student-server id: student-server version: 1.0 scan: base-packages: com.hzgj.lyrk.dubbo.student.server registry: address: zookeeper://localhost:2181 View Code 在这里面我们注意以下几点: 1)首先定义spring.application.name这个不多说了 遵守规范就行 2)dubbo集成的配置时通常以dubbo.xxxx打头 3)dubbo.scan.base-packages:主要是扫描dubbo的注解包 4)dubbo.registry.address:是指定注册中心的地址,这里我们使用zookeeper作为注册中心 3.5 启动成功时,我们通过zkCli能够发现在zookeeper存在如下节点: 这里面的结构为:/dubbo/接口的类全名/节点 4、编写消费端:project-portal 4.1 添加gradle依赖: dependencies { compile 'com.alibaba.boot:dubbo-spring-boot-starter:0.1.0' // https://mvnrepository.com/artifact/com.101tec/zkclient compile group: 'com.101tec', name: 'zkclient', version: '0.10' compile project(":service-api") } View Code 4.2 编写controller package com.hzgj.lyrk.dubbo.portal.controller; import com.alibaba.dubbo.config.annotation.Reference; import com.hzgj.lyrk.dubbo.api.IStudentService; import com.hzgj.lyrk.dubbo.dto.StudentDTO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class StudentController { @Reference private IStudentService studentService; @GetMapping("/student/id/{stuId}") public StudentDTO getStudent(@PathVariable Integer stuId) { return studentService.getStudentById(stuId); } } View Code 注意: @Reference注解的使用 4.3 编写启动类 package com.hzgj.lyrk.dubbo.portal; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class PortalApplication { public static void main(String[] args) { SpringApplication.run(PortalApplication.class, args); } } View Code 4.4 编写application.yml spring: application: name: project-portal server: port: 8101 dubbo: registry: address: zookeeper://localhost:2181 application: version: 1.0 id: project-portal name: project-portal View Code 测试一下: 三 、后话- Dubbo与SpringCloud 其实,这两者都是当下微服务典型的技术解决方案,可谓一时瑜亮,只不过在国内dubbo比较流行一些,原因其实很简单: 1. dubbo官方文档详细且有中文,而且运行原理技术解决方案描述比较透彻 2.国内的架构师有许多来自于阿里,对dubbo的推广起到了不可磨灭的作用 3.由于dubbo出现的较早,当然也开源。对于当时可谓轰动一时,各大公司争先使用,即使到现在谁也不愿意将原先的项目大刀阔斧的用新技术重构。 相反,在国外的社区,dubbo的使用广度恐怕就远不及SpringCloud了。原因其实也很明了:就公司而言,dubbo出自于阿里,属于商业公司。我觉得阿里的框架肯定优先满足于自己的业务与利益。而springcloud出自于Spring的产品族,而其公司本身就是为了简化企业的开发模式,为各大企业所服务。因此他们的本身出发点就不同,我觉得这个才是必要因素。 但是有几点我在这里重点提一下: 1. 就完成的功能而言:dubbo其实是SpringCloud组件中的一部分,也就相当于netflix中的eureka+小半个Hystrix+ribbon+feign。但是SpringCloud集成的诸如:链路跟踪,分布式配置,网关路由等,目前dubbo里还没有找到,也有可能我没有发现。因此在dubbo里需要使用这些功能,我们还要借助于第三方的实现。 2. dubbo是基于rpc实现远程调用,springcloud各个服务之间调用还是经过http,就性能而言是要弱于dubbo的,毕竟dubbo是经过阿里庞大的业务产品族和并发量考验的,不过这并不代表springcloud性能会很差 3. 常用的dubbo的技术使用方案还是基于spring,因此,我还是愿意把幕后英雄归功于spring 4. spring-cloud就相当于电脑的品牌机,拿来很方便的使用,因此它绝对是中小型公司(没有过多的精力和成本去搞基础研发)福音。而dubbo就好比是组装机,我们通过其已有的实现,完整的文档装配成我们自己想要的一套微服务方案。
说句实话,很久都没使用SSH开发项目了,但是出于各种原因,再次记录一下整合方式,纯注解零配置。 一。前期准备工作 gradle配置文件: group 'com.bdqn.lyrk.ssh.study' version '1.0-SNAPSHOT' apply plugin: 'war' repositories { mavenLocal() mavenCentral() } dependencies { // https://mvnrepository.com/artifact/org.apache.struts/struts2-core compile group: 'org.apache.struts', name: 'struts2-core', version: '2.5.14.1' // https://mvnrepository.com/artifact/org.apache.struts/struts2-spring-plugin compile group: 'org.apache.struts', name: 'struts2-spring-plugin', version: '2.5.14.1' // https://mvnrepository.com/artifact/org.springframework/spring-context compile group: 'org.springframework', name: 'spring-context', version: '5.0.4.RELEASE' // https://mvnrepository.com/artifact/org.springframework/spring-web compile group: 'org.springframework', name: 'spring-web', version: '5.0.4.RELEASE' // https://mvnrepository.com/artifact/org.hibernate/hibernate-core compile group: 'org.hibernate', name: 'hibernate-core', version: '5.2.12.Final' // https://mvnrepository.com/artifact/org.hibernate/hibernate-validator compile group: 'org.hibernate', name: 'hibernate-validator', version: '5.4.1.Final' // https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0' // https://mvnrepository.com/artifact/org.springframework/spring-orm compile group: 'org.springframework', name: 'spring-orm', version: '5.0.4.RELEASE' // https://mvnrepository.com/artifact/org.springframework/spring-tx compile group: 'org.springframework', name: 'spring-tx', version: '5.0.4.RELEASE' // https://mvnrepository.com/artifact/com.zaxxer/HikariCP compile group: 'com.zaxxer', name: 'HikariCP', version: '2.7.4' // https://mvnrepository.com/artifact/org.apache.struts/struts2-convention-plugin compile group: 'org.apache.struts', name: 'struts2-convention-plugin', version: '2.5.14.1' testCompile group: 'junit', name: 'junit', version: '4.11' } View Code 结构文件: 注意在classpath下创建META-INF/services/javax.serlvet.ServletContainerInitializer文件,那么在文件中定义的类在servlet容器启动时会执行onStartup方法,我们可以在这里编码创建servlet,fliter,listener等,文件内容如下: com.bdqn.lyrk.ssh.study.config.WebInitializer 二。具体的集成实现方案 1.定义 MyAnnotationConfigWebApplicationContext package com.bdqn.lyrk.ssh.study.config; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; /** * 自定义配置,为了在ContextLoaderListener初始化注解配置时使用 * @Author chen.nie */ public class MyAnnotationConfigWebApplicationContext extends AnnotationConfigWebApplicationContext { public MyAnnotationConfigWebApplicationContext(){ this.register(AppConfig.class); } } View Code 2.定义WebInitializer package com.bdqn.lyrk.ssh.study.config; import org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter; import org.springframework.orm.hibernate5.support.OpenSessionInViewFilter; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import javax.servlet.*; import javax.servlet.annotation.HandlesTypes; import javax.servlet.http.HttpServlet; import java.util.EnumSet; import java.util.Set; /** * 自定义servlet容器初始化,请参考servlet规范 */ @HandlesTypes({HttpServlet.class, Filter.class}) public class WebInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { /* 创建struts2的核心控制器 */ StrutsPrepareAndExecuteFilter strutsPrepareAndExecuteFilter = ctx.createFilter(StrutsPrepareAndExecuteFilter.class); OpenSessionInViewFilter openSessionInViewFilter = ctx.createFilter(OpenSessionInViewFilter.class); /* 向servlet容器中添加filter */ ctx.addFilter("strutsPrepareAndExecuteFilter", strutsPrepareAndExecuteFilter). addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); FilterRegistration.Dynamic dynamic = ctx.addFilter("openSessionInViewFilter", openSessionInViewFilter); dynamic.setInitParameter("flushMode", "COMMIT"); dynamic.setInitParameter("sessionFactoryBeanName","localSessionFactoryBean"); dynamic.setInitParameter("singleSession","true"); dynamic.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); /* 添加监听器 */ ContextLoaderListener contextLoaderListener = new ContextLoaderListener(); ctx.addListener(contextLoaderListener); ctx.setInitParameter("contextClass", "com.bdqn.lyrk.ssh.study.config.MyAnnotationConfigWebApplicationContext"); } } View Code 3.定义AppConfig package com.bdqn.lyrk.ssh.study.config; import com.zaxxer.hikari.HikariDataSource; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.orm.hibernate5.HibernateTemplate; import org.springframework.orm.hibernate5.HibernateTransactionManager; import org.springframework.orm.hibernate5.LocalSessionFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; import java.util.Properties; /** * spring基于注解配置 * * @author chen.nie * @date 2018/3/21 **/ @Configuration @ComponentScan("com.bdqn") @EnableTransactionManagement public class AppConfig { /** * 定义数据源 * @return */ @Bean public HikariDataSource dataSource() { HikariDataSource hikariDataSource = new HikariDataSource(); hikariDataSource.setDriverClassName("com.mysql.jdbc.Driver"); hikariDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/MySchool?characterEncoding=utf-8&useSSL=false"); hikariDataSource.setUsername("root"); hikariDataSource.setPassword("root"); return hikariDataSource; } /** * 定义spring创建hibernate的Session对象工厂 * @param dataSource * @return */ @Bean public LocalSessionFactoryBean localSessionFactoryBean(@Autowired DataSource dataSource) { LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean(); localSessionFactoryBean.setDataSource(dataSource); localSessionFactoryBean.setPackagesToScan("com.bdqn.lyrk.ssh.study.entity"); Properties properties = new Properties(); properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect"); properties.setProperty("hibernate.show_sql", "true"); localSessionFactoryBean.setHibernateProperties(properties); return localSessionFactoryBean; } /** * 定义hibernate事务管理器 * @param localSessionFactoryBean * @return */ @Bean public HibernateTransactionManager transactionManager(@Autowired SessionFactory localSessionFactoryBean) { HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager(localSessionFactoryBean); return hibernateTransactionManager; } /** * 定义hibernateTemplate * @param localSessionFactoryBean * @return */ @Bean public HibernateTemplate hibernateTemplate(@Autowired SessionFactory localSessionFactoryBean) { HibernateTemplate hibernateTemplate = new HibernateTemplate(localSessionFactoryBean); return hibernateTemplate; } } View Code 4.定义实体类 package com.bdqn.lyrk.ssh.study.entity; import javax.persistence.*; @Table(name = "student") @Entity public class StudentEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Integer id; @Column(name = "stuName") private String stuName; @Column(name = "password") private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getStuName() { return stuName; } public void setStuName(String stuName) { this.stuName = stuName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } View Code 5.定义service package com.bdqn.lyrk.ssh.study.service; import com.bdqn.lyrk.ssh.study.entity.StudentEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate5.HibernateTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class StudentService { @Autowired private HibernateTemplate hibernateTemplate; @Transactional public int save(StudentEntity studentEntity){ hibernateTemplate.save(studentEntity); return studentEntity.getId(); } } View Code 6.定义action 请大家留意关于struts2注解的注意事项 package com.bdqn.lyrk.ssh.study.action; import com.bdqn.lyrk.ssh.study.entity.StudentEntity; import com.bdqn.lyrk.ssh.study.service.StudentService; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; import org.apache.struts2.convention.annotation.Action; import org.apache.struts2.convention.annotation.Namespace; import org.apache.struts2.convention.annotation.ParentPackage; import org.apache.struts2.convention.annotation.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; /** * struts2注解配置,注意: * 1.所在的包名必须以action结尾 * 2.Action要必须继承ActionSupport父类; * 3.添加struts2-convention-plugin-xxx.jar * * @author chen.nie * @date 2018/3/21 **/ @Scope("prototype") @ParentPackage("struts-default") //表示继承的父包 @Namespace(value = "/") //命名空间 public class IndexAction extends ActionSupport { @Autowired private StudentService studentService; @Action(value = "index", results = {@Result(name = "success", location = "/index.jsp")}) public String index() { StudentEntity studentEntity = new StudentEntity(); studentEntity.setStuName("test"); studentEntity.setPassword("123"); studentService.save(studentEntity); ActionContext.getContext().getContextMap().put("student", studentEntity); return com.opensymphony.xwork2.Action.SUCCESS; } } View Code 7.index.jsp <%-- Created by IntelliJ IDEA. User: chen.nie Date: 2018/3/21 Time: 下午6:55 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>ssh基于零配置文件整合</title> </head> <body> 新增的学生id为:${student.id} </body> </html> View Code 启动tomcat,访问http://localhost:8080/index得到如下界面: 总结:前面我写过ssm基于注解零配置整合,那么ssh其思路也大体相同,主要是@Configuration @Bean等注解的使用,但是大家请留意的是struts2的注解与servlet的规范(脱离web.xml,手动添加servlet filter listener等)
一。调用链跟踪的必要性 首先我们简单来看一下下单到支付的过程,别的不多说,在业务复杂的时候往往服务会一层接一层的调用,当某一服务环节出现响应缓慢时会影响整个服务的响应速度,由于业务调用层次很“深”,那么在排查问题的时候也会更加困难,如果有一种机制帮我们监控、收集这些服务之间层层调用的时间与逻辑关系是否会助于我们排查问题呢?要解决这个问题。我们就必须借助于分布式服务跟踪系统的力量了 一般的,一个分布式服务跟踪系统,主要有三部分:数据收集、数据存储和数据展示。根据系统大小不同,每一部分的结构又有一定变化。譬如,对于大规模分布式系统,数据存储可分为实时数据和全量数据两部分,实时数据用于故障排查(troubleshooting),全量数据用于系统优化。那么我在贴一张图,此图是spring-cloud-sleuth的概念图: 初次看这个图会有点迷糊,因为有一些名词需要大家了解一下 trace:是指我们从服务开始到服务执行的终点的一次完整的请求与相应。 span: 在一次完整的trace过程中,每调用一个服务都会记录调用信息与响应时间,这个就是span 由此我们可以得出一个结论就是:一次trace由若干个span构成,sleuth是用于调用链追踪,通过sleuth我们可以轻易的追踪到trace经过了哪几个服务,每个服务花费的时间等,而zipkin是一个开源的追踪系统,它负责收集,存储数据并展示给用户。Zipkin提供了可插拔数据存储方式:In-Memory、MySql、Cassandra以及Elasticsearch。 二。spring-cloud-sleuth的快速使用 注意SpringCloud的Finchley M8版本的sleuth和zipkin的包还有点冲突,因此我使用Edgware SR2的版本,此版本对应的SpringBoot的版本是1.5.10.RELEASE,我们还是基于以下项目模块构建: 1. 添加启动zipkin服务zipkin-server模块 gradle配置: dependencies { compile('org.springframework.cloud:spring-cloud-starter-eureka') compile('io.zipkin.java:zipkin-server') compile('io.zipkin.java:zipkin-autoconfigure-ui') } application.yml配置 server: port: 8200 eureka: client: service-url: defaultZone: http://localhost:8000/eureka spring: application: name: zipkin-server View Code 定义启动类: package com.hzgj.lyrk.zipkin.server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import zipkin.server.EnableZipkinServer; @EnableZipkinServer @EnableDiscoveryClient @SpringBootApplication public class ZipkinServerApplication { public static void main(String[] args) { SpringApplication.run(ZipkinServerApplication.class, args); } } View Code 当我们启动成功后,访问http://localhost:8200/可以看到如下界面: 由于我们没有配置监控的服务内容,因此在zipkin里并没有统计数据 2.处理要跟踪的服务模块 tips:在这里gateway-server为网关模块使用zuul来实现,通过网关调用order-server。 分别在要进行服务追踪的模块gateway-server里添加如下依赖: compile('org.springframework.cloud:spring-cloud-starter-eureka-server') compile('org.springframework.cloud:spring-cloud-sleuth-zipkin') compile 'org.springframework.cloud:spring-cloud-starter-sleuth' View Code 在application.yml里的配置: spring: zipkin: base-url: http://localhost:8200 sleuth: sampler: percentage: 1.0 View Code spring.zipkin.baseUrl是指我们启动的zipkin-server服务地址 spring.sleuth.sampler.percentage 是代表采样率,值越大就代表采样的频率越高 默认为0.1 最大为1.0(即每次请求都进行跟踪) 分别启动gateway-server与order-server并通过gateway-server访问order-server若干次后,我们在通过zipkin查找按钮来访问结果,如下: 在这里面我们留意几个问题: 1)通过上述的配置,sleuth每次将监控的span通过http请求发送到zipkin-server会浪费一些性能,同时如果zipkin-server出现问题时,数据容易产生丢失 2)在本例当中,数据存到内存当中,数据量过大容易产生OOM错误 在以后的篇幅中会给出这些问题的解决方案。
一。zookeeper简介 zookeeper 是apache旗下的hadoop子项目,它一个开源的,分布式的服务协调器。同样通过zookeeper可以实现服务间的同步与配置维护。通常情况下,在分布式应用开发中,协调服务这样的工作不是件容易的事,很容易出现死锁,不恰当的选举竞争等。zookeeper就是担负起了分布式协调的重担。 zookeeper的特点: 使用简单:ZooKeeper允许分布式程序通过一个类似于标准文件系统的共享的层次化名称空间来相互协调。名称空间由数据寄存器(称为znode)组成,在ZooKeeper中,它们类似于文件和目录。与为存储而设计的典型文件系统不同,ZooKeeper数据保存在内存中,这意味着ZooKeeper可以达到高吞吐量和低延迟数 同步与复制:组成ZooKeeper服务的服务器必须互相有感知。客户端连接到一个ZooKeeper服务器。客户端维护一个TCP连接,通过它发送请求、获取响应、获取观察事件和发送心跳。如果连接到服务器的TCP连接中断,客户端将连接到另一个服务器。 有序 在进行大量读操作时,运行速度奇快 ZooKeeper提供的名称空间非常类似于标准文件系统。名称是由斜杠(/)分隔的路径元素序列。在ZooKeeper的名称空间中,每一个节点都是通过一条路径来标识的。如图所示 : 当然zookeeper与标准文件系统不同的是,它的节点分为永久节点和临时节点(随着会话断开而消失) 客户端的节点都会被设置一个监控,当znode发生更改时,这个变化会通知所有客户端然后删除 二。zookeeper快速上手 下载zookeeper:地址,并解压,然后得到如下目录: 进入conf目录下我们复制一份zoo_sample.cfg并改名为zoo.cfg cp zoo_sample.cfg zoo.cfg 运行 vi zoo.cfg 我们先来看看配置 # The number of milliseconds of each tick tickTime=2000 # The number of ticks that the initial # synchronization phase can take initLimit=10 # The number of ticks that can pass between # sending a request and getting an acknowledgement syncLimit=5 # the directory where the snapshot is stored. # do not use /tmp for storage, /tmp here is just # example sakes. dataDir=../zookeeper-data # the port at which the clients will connect clientPort=2181 # the maximum number of client connections. # increase this if you need to handle more clients #maxClientCnxns=60 # # Be sure to read the maintenance section of the # administrator guide before turning on autopurge. # # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance # # The number of snapshots to retain in dataDir #autopurge.snapRetainCount=3 # Purge task interval in hours # Set to "0" to disable auto purge feature #autopurge.purgeInterval=1 View Code 在这里我们保持默认配置先不变。更改一下dataDir这个值,注意:先在zookeeper的根目录下把该文件夹建好 进入bin目录下启动zookeeper。注意:再此之前必须配置JAVA_HOME的环境变量 ./zkServer.sh start 紧接着在bin目录下运行客户端 ./zkCli.sh -server 127.0.0.1:2181 运行过后会得到如下提示界面: Connecting to localhost:2181 log4j:WARN No appenders could be found for logger (org.apache.zookeeper.ZooKeeper). log4j:WARN Please initialize the log4j system properly. Welcome to ZooKeeper! JLine support is enabled [zkshell: 0] 紧接着我们运行help命令可以看看当前可以运行哪些指令: [zkshell: 0] help ZooKeeper host:port cmd args get path [watch] ls path [watch] set path data [version] delquota [-n|-b] path quit printwatches on|off create path data acl stat path [watch] listquota path history setAcl path acl getAcl path sync path redo cmdno addauth scheme auth delete path [version] setquota -n|-b val path 其中 get:为获取节点数据 , ls查看当前节点的子节点 create:创建节点 delete:删除节点 set设置节点的内容数据 下面我们依次运行如下命令看看: [zkshell: 8] ls / [zookeeper] [zkshell: 9] create /zk_test my_data Created /zk_test [zkshell: 11] ls / [zookeeper, zk_test] [zkshell: 12] get /zk_test my_data cZxid = 5 ctime = Fri Jun 05 13:57:06 PDT 2009 mZxid = 5 mtime = Fri Jun 05 13:57:06 PDT 2009 pZxid = 5 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0 dataLength = 7 numChildren = 0 [zkshell: 14] set /zk_test junk cZxid = 5 ctime = Fri Jun 05 13:57:06 PDT 2009 mZxid = 6 mtime = Fri Jun 05 14:01:52 PDT 2009 pZxid = 5 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0 dataLength = 4 numChildren = 0 [zkshell: 15] get /zk_test junk cZxid = 5 ctime = Fri Jun 05 13:57:06 PDT 2009 mZxid = 6 mtime = Fri Jun 05 14:01:52 PDT 2009 pZxid = 5 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0 dataLength = 4 numChildren = 0 [zkshell: 16] delete /zk_test [zkshell: 17] ls / [zookeeper] [zkshell: 18] 三。使用java操作zookeeper 通常情况下我们可以使用zookeeper提供的原生jar包操作zookeeper服务,但是zookeeper原生方式操作很麻烦,不过我们可以用第三方的组件来操作zookeeper,比如说:zkClient 或者curator。curator提供了更丰富的功能,但是使用起来比zkClient稍微复杂一点。关于curator我们后续篇幅会介绍,这里先贴出zkClient的例子: 添加zkClient的依赖: // https://mvnrepository.com/artifact/com.101tec/zkclient compile group: 'com.101tec', name: 'zkclient', version: '0.10' 示例代码: package com.bdqn.lyrk.register; import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.ZkClient; import org.apache.zookeeper.CreateMode; import java.io.IOException; public class ResgisterApplication { public static void main(String[] args) throws IOException { ZkClient zkClient = new ZkClient("localhost:2181", 1000); //获取指定路径的子节点个数 System.out.println(zkClient.countChildren("/")); //如果节点存在则删除该节点 if (zkClient.exists("/dubbo")) { zkClient.delete("/dubbo"); } //创建永久的节点 String nodeName = zkClient.create("/dubbo", "{\"name\":\"admin\"}", CreateMode.PERSISTENT); System.out.println(nodeName); //创建临时节点 zkClient.createEphemeralSequential("/dubbo/test", "a"); zkClient.createEphemeralSequential("/dubbo/test", "b"); //读取节点数据 System.out.println(zkClient.readData("/dubbo").toString()); //订阅dubbo数据的变化 zkClient.subscribeDataChanges("/dubbo", new IZkDataListener() { @Override public void handleDataChange(String dataPath, Object data) throws Exception { System.out.println(dataPath+"节点数据发生变化。。。"); } @Override public void handleDataDeleted(String dataPath) throws Exception { System.out.println(dataPath+"节点数据被删除...."); } }); //订阅dubbo子节点的变化 zkClient.subscribeChildChanges("/dubbo",(parentPath, currentChilds) -> System.out.println("dubbo节点发生变化")); //更新dubbo节点的数据 zkClient.writeData("/dubbo", "dubbo"); System.in.read(); } } View Code 注意以下几点: 1.不能删除已经存在子节点的节点 2.不能再临时节点上创建节点 四。zookeeper与eureka浅谈 一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性) zookeeper优先保证CP,当服务发生故障会进行leader的选举,整个期间服务处在不可用状态,如果选举时间过长势必会大幅度降低性能,另外就用途来说zookeeper偏向于服务的协调,当然含有注册中心的作用 eureka优先保证AP, 即服务的节点各个都是平等的,没有leader不leader一说, 当服务发生故障时,其余的节点仍然可以提供服务,因此在出现故障时,性能表现优于zookeeper,但是可能会造成数据不一致的情况。
一。为什么要有网关 我们先看一个图,如果按照consumer and server(最初的调用方式),如下所示 这样我们要面临如下问题: 1. 用户面临着一对N的问题既用户必须知道每个服务。随着服务的增多难免会.... 2.消费端(在这里可能是服务,也有可能为controller等),如何进行安全控制?比如说对调用者身份的验证,防止爬虫,或者限制IP在一定时间内的请求数? 3.即便做了这些验证,那么每个消费端都要重复的编写代码?会不会造成冗余? 那么解决这些问题,我们不妨进行改造一下: 这样我们可以将一对N转成了一对一,用户只需跟网关打交道就好了,这样我们可以在网关里处理身份验证等安全性问题,何乐而不为呢 二。SpringCloud中的zuul Zuul 是在云平台上提供动态路由,监控,弹性,安全等服务框架,Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。同时Zuul使用一系列不同类型的过滤器,使我们能够快速,灵活地将功能应用于我们的安全服务。这些过滤器可帮助我们执行以下功能: 身份验证和安全性 - 识别每个资源的身份验证要求并拒绝不符合要求的请求。 洞察和监测 - 跟踪有意义的数据和统计数据,以便为我们提供准确的生产视图。 动态路由 - 根据需要动态路由请求到不同的后端群集。 压力测试 - 逐渐增加群集流量以衡量性能。 加载Shedding - 为每种类型的请求分配容量并删除超出限制的请求。 静态响应处理 - 直接在边缘建立一些响应,而不是将它们转发到内部群集 多区域弹性 - 跨AWS区域的路由请求,以便扩大我们的ELB使用范围。 1.首先我们先添加spring-cloud对zuul的依赖: compile('org.springframework.cloud:spring-cloud-starter-zuul') 2.我们创建application.yml配置文件: spring: application: name: gateway-server server: port: 8080 eureka: client: service-url: defaultZone: http://localhost:8000/eureka zuul: routes: orders: path: /orders/** #url: http://www.baidu.com serviceId: ORDER-SERVER pay: path: /pay/** serviceId: PAY-SERVER View Code 3.编写启动类 package com.zhibo.springcloud.zuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @SpringBootApplication @EnableDiscoveryClient @EnableZuulProxy public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class,args); } } View Code 注意@EnableZuulProxy是@EnableZuulServer的一个超集,我们可以通过源代码发现 org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration继承了ZuulServerAutoConfiguration 三。 zuul中的filter 4.1 filter(过滤器)是一个很重要的概念,它可以在真正的请求之前进行必要的业务操作,比如说验证等。那么在zuul中过滤器最核心的接口为IZuulFilter,该接口定义非常简单: /* * Copyright 2013 Netflix, Inc. * * 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 com.netflix.zuul; import com.netflix.zuul.exception.ZuulException; /** * BAse interface for ZuulFilters * * @author Mikey Cohen * Date: 10/27/11 * Time: 3:03 PM */ public interface IZuulFilter { /** * a "true" return from this method means that the run() method should be invoked * * @return true if the run() method should be invoked. false will not invoke the run() method */ boolean shouldFilter(); /** * if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter * * @return Some arbitrary artifact may be returned. Current implementation ignores it. * @throws ZuulException if an error occurs during execution. */ Object run() throws ZuulException; } View Code 而ZuulFilter是一个实现IZuulFilter接口的抽象类,这个类在接口的基础上又添加了如下抽象方法 /** * to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering, * "route" for routing to an origin, "post" for post-routing filters, "error" for error handling. * We also support a "static" type for static responses see StaticResponseFilter. * Any filterType made be created or added and run by calling FilterProcessor.runFilters(type) * * @return A String representing that type */ abstract public String filterType(); /** * filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not * important for a filter. filterOrders do not need to be sequential. * * @return the int order of a filter */ abstract public int filterOrder(); View Code 我在这里简单解释一下这几个方法作用: filterType:定义过滤器的类型:常见的有pre(预处理阶段) post(请求原始服务之前) error(发生错误以后) route(请求原始服务之后) filterOrder:定义多个过滤器的优先级,值越小优先级越高 shouldFilter:值如果为true,那么终会执行run()方法 run : 过滤器实际上执行的内容 4.2 RequestContext是ConcurrentMap的一个子类,这个类可以拿到HttpServletRequest与HttpServletResponse,同时这个类当中的数据可以被多个zuulFilter共享,其中有几个方法值得我们注意以下: getCurrentContext() 获取ThreadLocal中的RequestContext对象 setSendZuulResponse() 如果设置为false那么将终止对原始地址的路由 setResponseStatusCode() 设置http的状态响应码 addZuulResponseHeader() 设置响应头,通过这个方法我们能解决响应时中文乱码问题 setResponseBody() 设置响应体 4.3 代码示例:实现用户名验证 package com.zhibo.springcloud.zuul; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Component public class ValidateUserZuulFilter extends ZuulFilter { /** * * @return */ @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); String loginName = request.getParameter("loginName"); if (loginName == null || !"admin".equals(loginName)) { requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(500); Gson gson = new GsonBuilder().create(); requestContext.addZuulResponseHeader("content-type", "application/json;charset=utf-8"); requestContext.setResponseBody(gson.toJson(new ResponseEntity("没有登录名", HttpStatus.CONFLICT))); return null; } return null; } } View Code 四。zuul中超时的设置总结 在这篇文章里已经很详细的说明了,请大家参考:周立的Springcloud超时总结
一。负载均衡与Ribbon 负载均衡,在集群中是很常见的一个“名词”,顾名思义是根据一定的算法将请求分摊至对应的服务节点上,常见的算法有如下几种: 轮询法:所有请求被依次分发到每台应用服务器上,每台服务器需要处理的请求数目都相同,适合所有服务器硬件都相同的场景 随机法:请求被随机分配到各个应用服务器,在许多场合下,这种方案都很简单实用。 源地址哈希(Hash)法:将请求来源的IP地址进行Hash计算,得到对应的服务器,这样来自同一个IP的请求总在同一个服务器上处理 加权法:根据应用服务器配置的情况,按照权重将请求分发到每个服务器,当然高性能的服务器分配的权重更高 最小连接数(Least Connections)法:计算每个应用服务器正在处理的连接数,将新的请求分发到最少连接的服务器上,按理说,这是最符合负载均衡定义的算法 2. Ribbon是Netfix公司提供的一个负载均衡的客户端框架,它可以和公司旗下的Eureka feign Hystrix等开源产品很好的集成,Ribbon框架具备以下特点: 负载均衡 容错 多协议(HTTP, TCP, UDP)支持异步和反应模型。 缓存和批处理 二。Ribbon使用方式 1。首先我们定义服务(在这里就是order-server)注意在服务中的两个配置文件 application.properties #应用程序名称 spring.application.name=order-server #指定config-server服务的地址 spring.cloud.config.uri=http://localhost:8888 #spring.profiles.active=local #取的是当前激活的环境 #spring.cloud.config.profile=${spring.profiles.active} spring.cloud.config.label=master #注册中心地址 eureka.client.service-url.defaultZone=http://localhost:8000/eureka #服务端口号 server.port=8001 #management.endpoint.health.show-details=always #management.endpoint.hystrix.health.enabled=true # http://localhost:8888/{spring.application.name}/{spring.cloud.config.profile}/{label} View Code application-multi.properties spring.application.name=order-server eureka.client.service-url.defaultZone=http://localhost:8000/eureka server.port=8011 management.endpoint.health.show-details=always management.endpoints.web.base-path=/ management.endpoints.web.exposure.include=health,beans,info # http://localhost:8888/{spring.application.name}/{spring.cloud.config.profile}/{label} View Code 这里我们关注 spring.application.name配置 ,并设置不同的端口号 2。在idea里设置启动类配置项 注意single instance only的复选框勾掉 active profiles 是设置当前激活的环境,配置完毕后启动.OrderApplication服务两次 3.新建一个项目ribbon-project并添加依赖 compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon') 4.在application.properties里添加配置项 order.ribbon.listOfServers=http://localhost:8001,http://localhost:8011 order.ribbon.connectTimeout=3000 order.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule View Code 注意: 这里面的格式定义:<client-name>.<namespace>.<property-value>=<value>,其中client-name代表客户端名称,今后我们会根据这个名字拿到客户端对象。 namespace默认的为ribbon的命名空间 property-value我们可以参考:Enum CommonClientConfigKey 。其中listOfServers配置的是我们order-server的服务地址,当然我们也可以不做任何配置,那么此时ribbon会给我们设置默认的配置(可以参考:DefaultConfigClientImpl),如果不指定客户端名称,那么配置适用于所有客户端。 我们可以指定负载均衡策略,其格式为:<clientName>.<clientConfigNameSpace>.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.<className> className值:BestAvailableRule(),RandomRule(随机) , RoundRobbinRule(轮询) , WeightedResponseTimeRule(权重响应时间) 5.编写客户端请求代码 package com.bdqn.lyrk.ribbon.study; import com.netflix.client.ClientFactory; import com.netflix.client.http.HttpRequest; import com.netflix.client.http.HttpResponse; import com.netflix.config.ConfigurationManager; import com.netflix.niws.client.http.RestClient; public class RibbonApplication { public static void main(String[] args) throws Exception { //加载配置文件 ConfigurationManager.loadPropertiesFromResources("application.properties"); RestClient restClient = (RestClient) ClientFactory.getNamedClient("order"); HttpRequest httpRequest = HttpRequest.newBuilder().uri("/orderId/1").build(); for (int i = 0; i < 5; i++) { HttpResponse response = restClient.executeWithLoadBalancer(httpRequest); System.out.println(response.getEntity(String.class)); } } } View Code 6.服务端请参考:SpringCloud学习之Feign结尾的代码示例 三。SpringCloud中的Ribbon 1) 使用@LoadBlanced注解和FeignClient 在RestTemplate上添加@LoadBlanced注解后,幕后英雄就成为了LoadBalancerClient,此时会创建LoadBalancerInterceptor的拦截器对象加入RestTemplate的拦截器栈中,大家可以自行了解下,具体的代码示例如下: @Bean @LoadBalanced RestTemplate restTemplate(){return new RestTemplate();} View Code 2)使用DiscoveryClient 这个对象是spring给我们提供好的,我们通过@Autowired拿来用就是了。 3) @RibbonClient 与 @LoadBalanced SpringCloud提供了@RibbonClient注解来创建自己定义的RibbonClient,初次接触很容易与@LoadBalanced注解混淆,那么我在这里简单解释一下: @LoadBalced主要标记在RestTemplate上,那么此时RestTemplate会使用RibbonLoadBalancerClient 来获取服务 @RibbonClient 主要用于配置RibbonClient客户端的,而且这个注解在我们服务发现中不是必须要配置的,如果我们使用SpringCloud中的服务发现机制,此时SpringCloud会给我们提供默认的Ribbon配置,甚至我们不需要配置@RibbonClient,不过当我们需要定义自己的RibbonClient或者不实用服务发现时,那么我们可以使用@RibbonClient注解 使用例子: 在我们的启动类上添加如下注解 @RibbonClient(name = "myservice") 然后我们在application.properties做如下配置: myservice.ribbon.eureka.enabled=false myservice.ribbon.listOfServers=http://localhost:5000, http://localhost:5001 View Code
一。关于终止线程stop与interrupt 一般来说,线程执行结束后就变成消亡状态,乍看之下我们并不需要人为进行干预(人为停止线程),不过凡事都有例外吧,在服务器或者其他应用场景下,线程为了提供服务而一直在不停的运转,因此必要时刻我们还需“人为干涉的”。 通常情况下,终止线程有两种方式:stop与interrupt 1) stop:暴力的停止线程(不管线程执行到哪段代码,立刻干掉),这个方法因为过于暴力会导致安全问题,因此JDK不推荐使用。 2) interrupt:优雅停止,调用该方法会通知线程可以进行停止操作了,此时线程只是变成可停止状态(thread.isInterrupted的值为true),实际上并没有停止 请看下段代码: 1 package com.bdqn.lyrk.basic; 2 3 /** 4 * 一个线程设置共享变量的值,保持ID与name值相同 5 * 另外一个线程读取共享变量的值,发现ID与name值不同时打印 6 * 7 * @author chen.nie 8 * @date 2018/1/30 9 **/ 10 public class StopThreadUnsafe { 11 12 public static User user = new User(); 13 14 public static class User { 15 private int id; 16 private String name; 17 18 public int getId() { 19 return id; 20 } 21 22 public void setId(int id) { 23 this.id = id; 24 } 25 26 public String getName() { 27 return name; 28 } 29 30 public void setName(String name) { 31 this.name = name; 32 } 33 34 public User() { 35 id = 0; 36 name = "0"; 37 } 38 39 @Override 40 public String toString() { 41 return "User [id=" + id + ",name=" + name + "]"; 42 } 43 } 44 45 public static class ChangeObjectThread extends Thread { 46 47 @Override 48 public void run() { 49 while (true) { 50 synchronized (user) { 51 if (Thread.currentThread().isInterrupted()) { 52 break; 53 } 54 int i = (int) System.currentTimeMillis() / 1000; 55 user.setId(i); 56 try { 57 Thread.sleep(100); 58 } catch (InterruptedException e) { 59 Thread.currentThread().interrupt(); 60 } 61 user.setName("" + i); 62 63 } 64 Thread.yield(); 65 } 66 } 67 } 68 69 public static class ReadObjectThread extends Thread { 70 71 @Override 72 public void run() { 73 while (true) { 74 synchronized (user) { 75 if (user.getId() != Integer.parseInt(user.getName())) { 76 System.out.println(user); 77 } 78 } 79 Thread.yield(); 80 } 81 } 82 } 83 } View Code Main方法: 1 package com.bdqn.lyrk.basic; 2 3 public class Main { 4 public static void main(String[] args) throws InterruptedException { 5 new StopThreadUnsafe.ReadObjectThread().start(); 6 while (true) { 7 StopThreadUnsafe.ChangeObjectThread thread = new StopThreadUnsafe.ChangeObjectThread(); 8 thread.start(); 9 Thread.sleep(200); 10 thread.stop(); 11 // thread.interrupt(); 12 } 13 } 14 } View Code 此时调用stop终止线程时,得到如下结果 User [id=1197577,name=1197576] User [id=1197577,name=1197576] User [id=1197577,name=1197576] User [id=1197577,name=1197576] User [id=1197578,name=1197577] 原因很简单:stop方法会释放对象锁,并终止线程,当线程还没有与name赋值时,已经被干掉了因此其他线程在读取时,很有可能读到NAME与ID值不一致的情况 二。守护线程Daemon 守护线程顾名思义,是系统的守护者,这个线程为系统的运行默默提供服务,当系统任务运行完毕,守护线程也就完成使命了,比如说垃圾回收线程,JIT线程都是守护线程,设置守护线程的方式:在调用start方法前,通过线程对象.setDaemon(true) 代码如下: 1 package com.bdqn.lyrk.basic; 2 3 public class DaemonDemo { 4 public static class DaemonT extends Thread { 5 @Override 6 public void run() { 7 while (true){ 8 System.out.println("I am alive"); 9 try { 10 Thread.sleep(1000); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 } 16 } 17 18 public static void main(String[] args) throws InterruptedException { 19 DaemonT daemonT = new DaemonT(); 20 daemonT.setDaemon(true); 21 daemonT.start(); 22 Thread.sleep(5000); 23 } 24 } View Code 运行结果 I am alive I am alive I am alive I am alive I am alive Process finished with exit code 0 我们可以看到,当主线程执行完毕后,守护线程也随之结束 三。wait与notify wait与notify是多线程协同工作的最基本手段,可是这两个方法属于Object的方法,当需要使用wait和notify时,必须配合synchronized使用,此时调用wait方法,当前线程会进入等待队列并释放当前的对象锁,直到线程被唤醒(notify),notify方法会随机唤醒一个在等待队列中的线程,notifyAll方法则唤醒所有在等待队列中的线程 代码示例: 1 package com.bdqn.lyrk.basic; 2 3 public class SimpleWN { 4 public static final Object object = new Object(); 5 6 public static class T1 extends Thread { 7 8 @Override 9 public void run() { 10 synchronized (object) { 11 System.out.println("开始执行线程..."); 12 try { 13 object.wait(); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 System.out.println("结束执行线程..."); 18 } 19 } 20 } 21 22 public static class T2 extends Thread { 23 @Override 24 public void run() { 25 synchronized (object) { 26 System.out.println("5秒后准备唤醒线程.."); 27 try { 28 Thread.sleep(5000); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 object.notify(); 33 } 34 } 35 } 36 37 public static void main(String[] args) { 38 T1 t1 = new T1(); 39 T2 t2 = new T2(); 40 t1.start(); 41 t2.start(); 42 } 43 } View Code 输出内容: 开始执行线程... 5秒后准备唤醒线程.. 结束执行线程... Process finished with exit code 0 注意以下几点: 1)当线程T1被notify过后,也必须要重新获取对象锁,才能够继续执行 2)sleep也能达到wait的效果,但是唯一区别时,sleep时并不会释放对象锁,因此其他线程并没有得到执行的机会
在前面使用SSM集成时,我们可以使用注解实现无配置化注入,但是这种依赖被进行“人工干预了的”,换句话就是说我们手动进行装配,那么此时还没有达到SpringBoot这种自动装配的效果,那么究竟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"); } } } View Code 该代码是创建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. 注意:Conditional 只有在所有配置类被加载完的时候被评估是否要创建,因此Conditional不能在配置类里根据其他创建的方法进行判断 @Bean@ConditionalOnBean({Teacher.class})public Student student(StudentProperties studentProperties) { Student student = new Student(); student.setStuName(studentProperties.getName()); return student;}@Bean@ConditionalOnMissingBeanpublic static Teacher teacher() { return new Teacher();}@Bean@ConditionalOnMissingBeanpublic 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; }} @Configurationpublic 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" } ] } View Code 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()); } } } View Code 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; } } View Code 4.application.yml配置: my: loginName: test spring: main: web-environment: false View Code 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()); } } View Code 当运行成功时:我们可以看到输出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所定义的配置类并加载
Gradle中的闭包其实就等同于Groovy中闭包,Groovy是一种jvm语言,语法兼容于java,曾几何时,也在脚本语言中独树一帜,初学Gradle的时候,大家很容易被其语法所迷惑,由于Gradle基本上是基于闭包以及委托实现的,所以要学Gradle必须先学Groovy闭包 一.Groovy中的闭包(Closures) 1.闭包是一种匿名的代码块,这个代码块可以有参数及返回值,同时也可以被定义成变量,在Groovy中闭包是一个对象,我们可以简单的把它理解成将方法赋值给一个变量 2.定义闭包的语法结构:{ [闭包参数 ->] 语句 },闭包参数可以是一个或者多个,可以定义参数类型也可以不定义其类型, 例子: { item++ } { -> item++ } { println it } { it -> println it } { name -> println name } { String x, int y -> println "hey ${x} the value is ${y}" } { reader -> def line = reader.readLine() line.trim() } 3.当闭包参数唯一时,我们可以用it来代替该参数,注意在闭包内不要再次定义it类型变量避免引起冲突4.闭包也可以作为一个返回值付给一个变量,比如: def listener = { e -> println "Clicked on $e.source" } 如果我们不用def来定义,那么使用Groovy中groovy.lang.Closure来代表闭包类型 Closure callback = { println 'Done!' } 5.调用闭包方法: 闭包名([参数 ...]) 或者 闭包名.call([参数 ...]) def test = { num -> println(num * 2) } test(2) 6.可以使用闭包来实现接口: def x ={Object [] args ->} as 接口名 二。闭包中的委托策略 1.在闭包中我们有三个引用对象的关键字:this,owner,delegete,委托策略是指:在闭包中写的属性或者方法,默认绑定到哪个对象上 2.三种关键字说明: this:代表闭包的外部类 owner:默认情况与this相似,但是ownner可以代表类,也可以代表闭包对象 delegete:默认情况等同于owner,但是我们可以动态改变这个值让闭包内的属性和方法代表不同的意义 3.我们可以用闭包对象的resolveStrategy 来改变代理策略,常见的值 Closure.OWNER_FIRST:默认策略,如果闭包里的属性或者方法优先调用owner中的,如果owner不存在则调用delegete中的 Closure.DELEGATE_FIRST 属性或方法优先使用delegete中,如果不存在,则使用owner中的 Closure.OWNER_ONLY 属性或方法仅仅在owner里寻找,delegete中存在的将被忽略 Closure.DELEGATE_ONLY 属性或方法仅仅在delegete里寻找,delegete中存在的将被忽略 新建Groovy脚本,代码如下: package com.bdqn.gradle.study /** * Groovy委托策略演示 * @author chen.nie * @date 2018/1/16 **/ class GroovyTest { def name def age def getName() { return name } void setName(name) { this.name = name } def getAge() { return age } void setAge(age) { this.age = age } /** * 闭包 this owner delegate 的相同情况下演示 * @return */ def testClosure1() { def closure1 = { // test = "20" println owner println this println delegate } closure1.call() } /** * 嵌套闭包时 this owner delegate区别演示 * 注意:此时this代表了类 * 而owner代表了闭包对象 */ def testClosure2() { def closure2 = { def test = { println this println owner println delegate } test() } closure2() } /** * 代理对象演示,注意在闭包内定义的date属性如果不指定其代理一定会报错,因为owner中没有date属性 * @return */ def testClosure3() { def closure3 = { def test = { date = "2018-01-16" println(date) } test() } GroovyTest1 groovyTest1 = new GroovyTest1() closure3.delegate = groovyTest1 closure3() } } class GroovyTest1 { def name def date def getName() { return name } void setName(name) { this.name = name } def getDate() { return date } void setDate(date) { this.date = date } } def test = new GroovyTest() test.testClosure1() println() test.testClosure2() println() test.testClosure3() View Code 输出结果: com.bdqn.gradle.study.GroovyTest@23f7d05d com.bdqn.gradle.study.GroovyTest@23f7d05d com.bdqn.gradle.study.GroovyTest@23f7d05d com.bdqn.gradle.study.GroovyTest@23f7d05d com.bdqn.gradle.study.GroovyTest$_testClosure2_closure2@2a32de6c com.bdqn.gradle.study.GroovyTest$_testClosure2_closure2@2a32de6c 2018-01-16 View Code 三 groovy常用特有语法总结: 1. groovy本身就支持java语法,groovy文件最终将会被翻译成.class文件 2. 定义变量或者方法 def name def test(args){ }//方法可以省略返回值,参数可以省略类型 3.定义多行字符串 def ab=""" 字符串 """ 4).对象安全操作 对象名?.属性名 当对象为null时直接返回Null 5.定义集合 def items=[4,5] def list=[1,2,3,*items] 6) 定义map: def m1 = [c:3,d:4] def map= [a:1,*:m1] 7) 定义范围: def range= 0..5 (包含5) def range1= 0..<5 (不包含5) def list = [0,1,2,3,4] assert list[2] == 2 list[2] = 4 assert list[0..2] == [0,1,4] list[0..2] = [6,6,6] assert list == [6,6,6,3,4] 8) 定义方法参数的默认值 def test(a=10){ //.... } 9) 创建对象指定参数 def person = new Person(age:10,name:"张三")
一.使用Gradle的java插件构建Java项目 1)Gradle插件包含了若干个接口定义和已有的任务项,语法结构:apply plugin:'插件名' ,此处我们定义插件 apply plugin : 'java' 2)Gradle希望我们的java项目需要遵循以下规范: src/main/java :放置java源文件 src/test/java :放置测试文件,比如单元测试等 src/main/resources: 此目录下的文件会被作为资源文件打入jar包 src/test/resources: 放置提供给测试用的配置文件 3) java插件包含了若干个构建项目的任务,最常用的就是build任务,当我们运行build任务时,Gradle会编译,运行我们的测试脚本(类)并生成jar文件在build/lib下 4) 其他常用的任务: clean:删除已经构建的目录及其文件 assemble:编译并生成Jar或者war文件,注意不会运行测试文件 check:编译并测试代码 二.外部依赖 1)和maven类似,如果要在项目中添加所依赖的外部jar文件,我们必须要告诉Gradle在哪里找到它们 语法: 1 repositories { 2 jcenter() 3 mavenLocal() //maven本地仓库 4 mavenCentral() //maven中心仓库 5 /* 6 指定maven远程仓库地址 7 */ 8 maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } 9 maven { url "http://repo.spring.io/snapshot" } 10 maven { url "http://repo.spring.io/milestone" } 11 } 2)当指定好仓库地址时,我们可以添加依赖 语法: dependencies{ compile group:'类似于maven中的groupid',name:'类似于maven中的affactid',version:'版本号' compile 'group:name:version' } 三.java多项目构建 1)gradle也可以支持多项目构建,比如说如下项目: multiproject/ api/ services/webservice/ shared/ services/shared/2)此时我们要在settings.gradle里配置 依赖的模块: include "shared", "api", "services:webservice", "services:shared" 3)比如说 shared模块要依赖api模块 我们可以在shared模块中的build.gradle文件中这样写: dependencies { compile project(':shared') } 4)常见属性 sourceCompatibility: 使用哪种JDK版本编译 targetCompatibility : 生成class的Java版本 四。构建web项目 1)需要添加web插件:apply plugin:'war'2) war任务 主要用于将web应用程序打包成war3) 与maven规范一致:web的相关资源位于src/main/webapp下 使用示例: war { from 'src/rootContent' // adds a file-set to the root of the archive webInf { from 'src/additionalWebInf' } // adds a file-set to the WEB-INF dir. classpath fileTree('additionalLibs') // adds a file-set to the WEB-INF/lib dir. classpath configurations.moreLibs // adds a configuration to the WEB-INF/lib dir. webXml = file('src/someWeb.xml') // copies a file to WEB-INF/web.xml } 另外我们可以设置webAppDirName来指定我们web上下文路径
一。gradle基础概念 Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具。Gradle抛弃了基于各种繁琐的XML,使用一种基于Groovy的特定领域语言(DSL)来声明项目设置。类似于Maven,gradle定义了一个对项目生命周期中各个阶段的行为操作 gradle的特点: 声明式和合约构建:兼容于Maven的目录结构,规定了源代码,静态文件存放的位置 基于依赖的编程语言:摒弃了繁琐的xml,采用编码灵活构建 灵活的扩展:丰富的插件库 多项目构建: 和maven一样支持多项目构建 gradle需要运行在一个Java环境里,因此安装gradle之前需要配置Java的运行环境,比如:环境变量等 当安装完毕时,我们可以在命令窗口运行gradle -v来查看版本信息等 运行 gralde init 初始化gradle工程,或者 gradle init --type pom (将maven项目转成gradle项目) gradle下载地址 二。Gradle的几个核心概念 gradle最核心的接口是Project,在一个Project里我们可以通过编程访问所有的Gradle功能。 生命周期:一个Project和build.gradle 文件之间有一对一的关系。在构建初始化期间,Gradle Project为每个要参与构建的项目组装一个对象 任务:一个项目本质上是一个Task对象的集合。每个任务都执行一些基本的工作,比如编译类,运行单元测试,或者压缩WAR文件。 依赖:一个项目通常需要一些依赖来完成工作。而且,一个项目通常会产生一些其他项目可以使用的工件。这些依赖关系被分组在配置中,并且可以从存储库中检索和上传。 多项目构建:项目被安排到项目层次结构中。一个项目有一个名称和一个在层次结构中唯一标识它的全限定路径。 插件:插件可以用来模块化和重用项目配置 属性:Gradle根据Project实例执行项目的构建文件来配置项目。你的脚本使用的任何属性或方法都被委托给关联的Project对象 额外的属性:所有额外的属性必须通过“ext”命名空间来定义。一旦定义了一个额外的属性,它就可以直接在拥有的对象上(在下面的例子中是项目,任务和子项目)直接可用,并且可以被读取和更新。只有最初的声明需要通过命名空间完成。 三。Gradle中的任务 每一个构建由一个或多个projects构成,一个project代表着我们想让gradle做的事情,每一个Project是由一个或多个task组成 创建task的语法结构:task 任务名 << {} 运行gradle任务语法: gradle -q 任务名 任务依赖:task 任务名(dependsOn:任务名) << {} 定义任务自定义属性: task 任务名 << { ext.属性名=值} 默认任务:defaultTaks '任务名1','任务名2' ..... 短标记法:在字符串中我们可以通过 $任务名 来获取task对象 build.gradle 代码示例 task basic << { ext.name = "basic Task" println("这是第一个任务") } //依赖任务 task taskDependsOn(dependsOn: basic) << { println("task1依赖basic任务") println(basic.name) } //动态创建4个任务 4.times { i -> task "task$i" << { println "task1" } } //短标记法 task shortTask <<{ println "basic任务的name属性值:$basic.name" } // 默认任务 defaultTasks 'shortTask','task1' View Code 四。Gradle的Project build.gradle实际上就代表了Project对象,我们在这里可以写java或者groovy代码来执行构建 我们在build.gradle里写的代码就相当于实现Project里的方法,我们可以把build.gradle看成完成Project接口的定义的闭包 在此我贴出Project的接口定义供大家参考: 1 /* 2 * Copyright 2010 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.gradle.api; 18 19 import groovy.lang.Closure; 20 import groovy.lang.DelegatesTo; 21 import groovy.lang.MissingPropertyException; 22 import groovy.transform.stc.ClosureParams; 23 import groovy.transform.stc.SimpleType; 24 import org.gradle.api.artifacts.ConfigurationContainer; 25 import org.gradle.api.artifacts.dsl.ArtifactHandler; 26 import org.gradle.api.artifacts.dsl.DependencyHandler; 27 import org.gradle.api.artifacts.dsl.RepositoryHandler; 28 import org.gradle.api.component.SoftwareComponentContainer; 29 import org.gradle.api.file.ConfigurableFileCollection; 30 import org.gradle.api.file.ConfigurableFileTree; 31 import org.gradle.api.file.CopySpec; 32 import org.gradle.api.file.DeleteSpec; 33 import org.gradle.api.file.FileTree; 34 import org.gradle.api.initialization.dsl.ScriptHandler; 35 import org.gradle.api.internal.ReturnType; 36 import org.gradle.api.invocation.Gradle; 37 import org.gradle.api.logging.Logger; 38 import org.gradle.api.logging.LoggingManager; 39 import org.gradle.api.model.ObjectFactory; 40 import org.gradle.api.plugins.Convention; 41 import org.gradle.api.plugins.ExtensionAware; 42 import org.gradle.api.plugins.ExtensionContainer; 43 import org.gradle.api.plugins.PluginAware; 44 import org.gradle.api.provider.PropertyState; 45 import org.gradle.api.provider.Provider; 46 import org.gradle.api.provider.ProviderFactory; 47 import org.gradle.api.resources.ResourceHandler; 48 import org.gradle.api.tasks.TaskContainer; 49 import org.gradle.api.tasks.WorkResult; 50 import org.gradle.internal.HasInternalProtocol; 51 import org.gradle.normalization.InputNormalizationHandler; 52 import org.gradle.process.ExecResult; 53 import org.gradle.process.ExecSpec; 54 import org.gradle.process.JavaExecSpec; 55 56 import java.io.File; 57 import java.net.URI; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Set; 61 import java.util.concurrent.Callable; 62 63 /** 64 * <p>This interface is the main API you use to interact with Gradle from your build file. From a <code>Project</code>, 65 * you have programmatic access to all of Gradle's features.</p> 66 * 67 * <h3>Lifecycle</h3> 68 * 69 * <p>There is a one-to-one relationship between a <code>Project</code> and a <code>{@value #DEFAULT_BUILD_FILE}</code> 70 * file. During build initialisation, Gradle assembles a <code>Project</code> object for each project which is to 71 * participate in the build, as follows:</p> 72 * 73 * <ul> 74 * 75 * <li>Create a {@link org.gradle.api.initialization.Settings} instance for the build.</li> 76 * 77 * <li>Evaluate the <code>{@value org.gradle.api.initialization.Settings#DEFAULT_SETTINGS_FILE}</code> script, if 78 * present, against the {@link org.gradle.api.initialization.Settings} object to configure it.</li> 79 * 80 * <li>Use the configured {@link org.gradle.api.initialization.Settings} object to create the hierarchy of 81 * <code>Project</code> instances.</li> 82 * 83 * <li>Finally, evaluate each <code>Project</code> by executing its <code>{@value #DEFAULT_BUILD_FILE}</code> file, if 84 * present, against the project. The projects are evaluated in breadth-wise order, such that a project is evaluated 85 * before its child projects. This order can be overridden by calling <code>{@link #evaluationDependsOnChildren()}</code> or by adding an 86 * explicit evaluation dependency using <code>{@link #evaluationDependsOn(String)}</code>.</li> 87 * 88 * </ul> 89 * 90 * <h3>Tasks</h3> 91 * 92 * <p>A project is essentially a collection of {@link Task} objects. Each task performs some basic piece of work, such 93 * as compiling classes, or running unit tests, or zipping up a WAR file. You add tasks to a project using one of the 94 * {@code create()} methods on {@link TaskContainer}, such as {@link TaskContainer#create(String)}. You can locate existing 95 * tasks using one of the lookup methods on {@link TaskContainer}, such as {@link org.gradle.api.tasks.TaskCollection#getByName(String)}.</p> 96 * 97 * <h3>Dependencies</h3> 98 * 99 * <p>A project generally has a number of dependencies it needs in order to do its work. Also, a project generally 100 * produces a number of artifacts, which other projects can use. Those dependencies are grouped in configurations, and 101 * can be retrieved and uploaded from repositories. You use the {@link org.gradle.api.artifacts.ConfigurationContainer} 102 * returned by {@link #getConfigurations()} method to manage the configurations. The {@link 103 * org.gradle.api.artifacts.dsl.DependencyHandler} returned by {@link #getDependencies()} method to manage the 104 * dependencies. The {@link org.gradle.api.artifacts.dsl.ArtifactHandler} returned by {@link #getArtifacts()} method to 105 * manage the artifacts. The {@link org.gradle.api.artifacts.dsl.RepositoryHandler} returned by {@link 106 * #getRepositories()} method to manage the repositories.</p> 107 * 108 * <h3>Multi-project Builds</h3> 109 * 110 * <p>Projects are arranged into a hierarchy of projects. A project has a name, and a fully qualified path which 111 * uniquely identifies it in the hierarchy.</p> 112 * 113 * <h3>Plugins</h3> 114 * 115 * <p> 116 * Plugins can be used to modularise and reuse project configuration. 117 * Plugins can be applied using the {@link PluginAware#apply(java.util.Map)} method, or by using the {@link org.gradle.plugin.use.PluginDependenciesSpec plugins script block}. 118 * </p> 119 * 120 * <a name="properties"/> <h3>Properties</h3> 121 * 122 * <p>Gradle executes the project's build file against the <code>Project</code> instance to configure the project. Any 123 * property or method which your script uses is delegated through to the associated <code>Project</code> object. This 124 * means, that you can use any of the methods and properties on the <code>Project</code> interface directly in your script. 125 * </p><p>For example: 126 * <pre> 127 * defaultTasks('some-task') // Delegates to Project.defaultTasks() 128 * reportsDir = file('reports') // Delegates to Project.file() and the Java Plugin 129 * </pre> 130 * <p>You can also access the <code>Project</code> instance using the <code>project</code> property. This can make the 131 * script clearer in some cases. For example, you could use <code>project.name</code> rather than <code>name</code> to 132 * access the project's name.</p> 133 * 134 * <p>A project has 5 property 'scopes', which it searches for properties. You can access these properties by name in 135 * your build file, or by calling the project's {@link #property(String)} method. The scopes are:</p> 136 * 137 * <ul> 138 * 139 * <li>The <code>Project</code> object itself. This scope includes any property getters and setters declared by the 140 * <code>Project</code> implementation class. For example, {@link #getRootProject()} is accessible as the 141 * <code>rootProject</code> property. The properties of this scope are readable or writable depending on the presence 142 * of the corresponding getter or setter method.</li> 143 * 144 * <li>The <em>extra</em> properties of the project. Each project maintains a map of extra properties, which 145 * can contain any arbitrary name -> value pair. Once defined, the properties of this scope are readable and writable. 146 * See <a href="#extraproperties">extra properties</a> for more details.</li> 147 * 148 * <li>The <em>extensions</em> added to the project by the plugins. Each extension is available as a read-only property with the same name as the extension.</li> 149 * 150 * <li>The <em>convention</em> properties added to the project by the plugins. A plugin can add properties and methods 151 * to a project through the project's {@link Convention} object. The properties of this scope may be readable or writable, depending on the convention objects.</li> 152 * 153 * <li>The tasks of the project. A task is accessible by using its name as a property name. The properties of this 154 * scope are read-only. For example, a task called <code>compile</code> is accessible as the <code>compile</code> 155 * property.</li> 156 * 157 * <li>The extra properties and convention properties inherited from the project's parent, recursively up to the root 158 * project. The properties of this scope are read-only.</li> 159 * 160 * </ul> 161 * 162 * <p>When reading a property, the project searches the above scopes in order, and returns the value from the first 163 * scope it finds the property in. If not found, an exception is thrown. See {@link #property(String)} for more details.</p> 164 * 165 * <p>When writing a property, the project searches the above scopes in order, and sets the property in the first scope 166 * it finds the property in. If not found, an exception is thrown. See {@link #setProperty(String, Object)} for more details.</p> 167 * 168 * <a name="extraproperties"/> <h4>Extra Properties</h4> 169 * 170 * All extra properties must be defined through the &quot;ext&quot; namespace. Once an extra property has been defined, 171 * it is available directly on the owning object (in the below case the Project, Task, and sub-projects respectively) and can 172 * be read and updated. Only the initial declaration that needs to be done via the namespace. 173 * 174 * <pre> 175 * project.ext.prop1 = "foo" 176 * task doStuff { 177 * ext.prop2 = "bar" 178 * } 179 * subprojects { ext.${prop3} = false } 180 * </pre> 181 * 182 * Reading extra properties is done through the &quot;ext&quot; or through the owning object. 183 * 184 * <pre> 185 * ext.isSnapshot = version.endsWith("-SNAPSHOT") 186 * if (isSnapshot) { 187 * // do snapshot stuff 188 * } 189 * </pre> 190 * 191 * <h4>Dynamic Methods</h4> 192 * 193 * <p>A project has 5 method 'scopes', which it searches for methods:</p> 194 * 195 * <ul> 196 * 197 * <li>The <code>Project</code> object itself.</li> 198 * 199 * <li>The build file. The project searches for a matching method declared in the build file.</li> 200 * 201 * <li>The <em>extensions</em> added to the project by the plugins. Each extension is available as a method which takes 202 * a closure or {@link org.gradle.api.Action} as a parameter.</li> 203 * 204 * <li>The <em>convention</em> methods added to the project by the plugins. A plugin can add properties and method to 205 * a project through the project's {@link Convention} object.</li> 206 * 207 * <li>The tasks of the project. A method is added for each task, using the name of the task as the method name and 208 * taking a single closure or {@link org.gradle.api.Action} parameter. The method calls the {@link Task#configure(groovy.lang.Closure)} method for the 209 * associated task with the provided closure. For example, if the project has a task called <code>compile</code>, then a 210 * method is added with the following signature: <code>void compile(Closure configureClosure)</code>.</li> 211 * 212 * <li>The methods of the parent project, recursively up to the root project.</li> 213 * 214 * <li>A property of the project whose value is a closure. The closure is treated as a method and called with the provided parameters. 215 * The property is located as described above.</li> 216 * 217 * </ul> 218 */ 219 @HasInternalProtocol 220 public interface Project extends Comparable<Project>, ExtensionAware, PluginAware { 221 /** 222 * The default project build file name. 223 */ 224 String DEFAULT_BUILD_FILE = "build.gradle"; 225 226 /** 227 * The hierarchy separator for project and task path names. 228 */ 229 String PATH_SEPARATOR = ":"; 230 231 /** 232 * The default build directory name. 233 */ 234 String DEFAULT_BUILD_DIR_NAME = "build"; 235 236 String GRADLE_PROPERTIES = "gradle.properties"; 237 238 String SYSTEM_PROP_PREFIX = "systemProp"; 239 240 String DEFAULT_VERSION = "unspecified"; 241 242 String DEFAULT_STATUS = "release"; 243 244 /** 245 * <p>Returns the root project for the hierarchy that this project belongs to. In the case of a single-project 246 * build, this method returns this project.</p> 247 * 248 * @return The root project. Never returns null. 249 */ 250 Project getRootProject(); 251 252 /** 253 * <p>Returns the root directory of this project. The root directory is the project directory of the root 254 * project.</p> 255 * 256 * @return The root directory. Never returns null. 257 */ 258 File getRootDir(); 259 260 /** 261 * <p>Returns the build directory of this project. The build directory is the directory which all artifacts are 262 * generated into. The default value for the build directory is <code><i>projectDir</i>/build</code></p> 263 * 264 * @return The build directory. Never returns null. 265 */ 266 File getBuildDir(); 267 268 /** 269 * <p>Sets the build directory of this project. The build directory is the directory which all artifacts are 270 * generated into.</p> 271 * 272 * @param path The build directory 273 * @since 4.0 274 */ 275 void setBuildDir(File path); 276 277 /** 278 * <p>Sets the build directory of this project. The build directory is the directory which all artifacts are 279 * generated into. The path parameter is evaluated as described for {@link #file(Object)}. This mean you can use, 280 * amongst other things, a relative or absolute path or File object to specify the build directory.</p> 281 * 282 * @param path The build directory. This is evaluated as per {@link #file(Object)} 283 */ 284 void setBuildDir(Object path); 285 286 /** 287 * <p>Returns the build file Gradle will evaluate against this project object. The default is <code> {@value 288 * #DEFAULT_BUILD_FILE}</code>. If an embedded script is provided the build file will be null. </p> 289 * 290 * @return Current build file. May return null. 291 */ 292 File getBuildFile(); 293 294 /** 295 * <p>Returns the parent project of this project, if any.</p> 296 * 297 * @return The parent project, or null if this is the root project. 298 */ 299 Project getParent(); 300 301 /** 302 * <p>Returns the name of this project. The project's name is not necessarily unique within a project hierarchy. You 303 * should use the {@link #getPath()} method for a unique identifier for the project.</p> 304 * 305 * @return The name of this project. Never return null. 306 */ 307 String getName(); 308 309 /** 310 * Returns a human-consumable display name for this project. 311 */ 312 String getDisplayName(); 313 314 /** 315 * Returns the description of this project, if any. 316 * 317 * @return the description. May return null. 318 */ 319 String getDescription(); 320 321 /** 322 * Sets a description for this project. 323 * 324 * @param description The description of the project. Might be null. 325 */ 326 void setDescription(String description); 327 328 /** 329 * <p>Returns the group of this project. Gradle always uses the {@code toString()} value of the group. The group 330 * defaults to the path with dots as separators.</p> 331 * 332 * @return The group of this project. Never returns null. 333 */ 334 Object getGroup(); 335 336 /** 337 * <p>Sets the group of this project.</p> 338 * 339 * @param group The group of this project. Must not be null. 340 */ 341 void setGroup(Object group); 342 343 /** 344 * <p>Returns the version of this project. Gradle always uses the {@code toString()} value of the version. The 345 * version defaults to {@value #DEFAULT_VERSION}.</p> 346 * 347 * @return The version of this project. Never returns null. 348 */ 349 Object getVersion(); 350 351 /** 352 * <p>Sets the version of this project.</p> 353 * 354 * @param version The version of this project. Must not be null. 355 */ 356 void setVersion(Object version); 357 358 /** 359 * <p>Returns the status of this project. Gradle always uses the {@code toString()} value of the status. The status 360 * defaults to {@value #DEFAULT_STATUS}.</p> 361 * 362 * <p>The status of the project is only relevant, if you upload libraries together with a module descriptor. The 363 * status specified here, will be part of this module descriptor.</p> 364 * 365 * @return The status of this project. Never returns null. 366 */ 367 Object getStatus(); 368 369 /** 370 * Sets the status of this project. 371 * 372 * @param status The status. Must not be null. 373 */ 374 void setStatus(Object status); 375 376 /** 377 * <p>Returns the direct children of this project.</p> 378 * 379 * @return A map from child project name to child project. Returns an empty map if this project does not have 380 * any children. 381 */ 382 Map<String, Project> getChildProjects(); 383 384 /** 385 * <p>Sets a property of this project. This method searches for a property with the given name in the following 386 * locations, and sets the property on the first location where it finds the property.</p> 387 * 388 * <ol> 389 * 390 * <li>The project object itself. For example, the <code>rootDir</code> project property.</li> 391 * 392 * <li>The project's {@link Convention} object. For example, the <code>srcRootName</code> java plugin 393 * property.</li> 394 * 395 * <li>The project's extra properties.</li> 396 * 397 * </ol> 398 * 399 * If the property is not found, a {@link groovy.lang.MissingPropertyException} is thrown. 400 * 401 * @param name The name of the property 402 * @param value The value of the property 403 */ 404 void setProperty(String name, Object value) throws MissingPropertyException; 405 406 /** 407 * <p>Returns this project. This method is useful in build files to explicitly access project properties and 408 * methods. For example, using <code>project.name</code> can express your intent better than using 409 * <code>name</code>. This method also allows you to access project properties from a scope where the property may 410 * be hidden, such as, for example, from a method or closure. </p> 411 * 412 * @return This project. Never returns null. 413 */ 414 Project getProject(); 415 416 /** 417 * <p>Returns the set containing this project and its subprojects.</p> 418 * 419 * @return The set of projects. 420 */ 421 Set<Project> getAllprojects(); 422 423 /** 424 * <p>Returns the set containing the subprojects of this project.</p> 425 * 426 * @return The set of projects. Returns an empty set if this project has no subprojects. 427 */ 428 Set<Project> getSubprojects(); 429 430 /** 431 * <p>Creates a {@link Task} with the given name and adds it to this project. Calling this method is equivalent to 432 * calling {@link #task(java.util.Map, String)} with an empty options map.</p> 433 * 434 * <p>After the task is added to the project, it is made available as a property of the project, so that you can 435 * reference the task by name in your build file. See <a href="#properties">here</a> for more details</p> 436 * 437 * <p>If a task with the given name already exists in this project, an exception is thrown.</p> 438 * 439 * @param name The name of the task to be created 440 * @return The newly created task object 441 * @throws InvalidUserDataException If a task with the given name already exists in this project. 442 */ 443 Task task(String name) throws InvalidUserDataException; 444 445 /** 446 * <p>Creates a {@link Task} with the given name and adds it to this project. A map of creation options can be 447 * passed to this method to control how the task is created. The following options are available:</p> 448 * 449 * <table> 450 * 451 * <tr><th>Option</th><th>Description</th><th>Default Value</th></tr> 452 * 453 * <tr><td><code>{@value org.gradle.api.Task#TASK_TYPE}</code></td><td>The class of the task to 454 * create.</td><td>{@link org.gradle.api.DefaultTask}</td></tr> 455 * 456 * <tr><td><code>{@value org.gradle.api.Task#TASK_OVERWRITE}</code></td><td>Replace an existing 457 * task?</td><td><code>false</code></td></tr> 458 * 459 * 460 * <tr><td><code>{@value org.gradle.api.Task#TASK_DEPENDS_ON}</code></td><td>A task name or set of task names which 461 * this task depends on</td><td><code>[]</code></td></tr> 462 * 463 * <tr><td><code>{@value org.gradle.api.Task#TASK_ACTION}</code></td><td>A closure or {@link Action} to add to the 464 * task.</td><td><code>null</code></td></tr> 465 * 466 * <tr><td><code>{@value org.gradle.api.Task#TASK_DESCRIPTION}</code></td><td>A description of the task. 467 * </td><td><code>null</code></td></tr> 468 * 469 * <tr><td><code>{@value org.gradle.api.Task#TASK_GROUP}</code></td><td>A task group which this task belongs to. 470 * </td><td><code>null</code></td></tr> 471 * 472 * </table> 473 * 474 * <p>After the task is added to the project, it is made available as a property of the project, so that you can 475 * reference the task by name in your build file. See <a href="#properties">here</a> for more details</p> 476 * 477 * <p>If a task with the given name already exists in this project and the <code>override</code> option is not set 478 * to true, an exception is thrown.</p> 479 * 480 * @param args The task creation options. 481 * @param name The name of the task to be created 482 * @return The newly created task object 483 * @throws InvalidUserDataException If a task with the given name already exists in this project. 484 */ 485 Task task(Map<String, ?> args, String name) throws InvalidUserDataException; 486 487 /** 488 * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given 489 * closure is executed to configure the task. A map of creation options can be passed to this method to control how 490 * the task is created. See {@link #task(java.util.Map, String)} for the available options.</p> 491 * 492 * <p>After the task is added to the project, it is made available as a property of the project, so that you can 493 * reference the task by name in your build file. See <a href="#properties">here</a> for more details</p> 494 * 495 * <p>If a task with the given name already exists in this project and the <code>override</code> option is not set 496 * to true, an exception is thrown.</p> 497 * 498 * @param args The task creation options. 499 * @param name The name of the task to be created 500 * @param configureClosure The closure to use to configure the created task. 501 * @return The newly created task object 502 * @throws InvalidUserDataException If a task with the given name already exists in this project. 503 */ 504 Task task(Map<String, ?> args, String name, @DelegatesTo(value = Task.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(ReturnType.class) Closure configureClosure); 505 506 /** 507 * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given 508 * closure is executed to configure the task.</p> <p/> <p>After the task is added to the project, it is made 509 * available as a property of the project, so that you can reference the task by name in your build file. See <a 510 * href="#properties">here</a> for more details</p> 511 * 512 * @param name The name of the task to be created 513 * @param configureClosure The closure to use to configure the created task. 514 * @return The newly created task object 515 * @throws InvalidUserDataException If a task with the given name already exists in this project. 516 */ 517 Task task(String name, @DelegatesTo(value = Task.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(ReturnType.class) Closure configureClosure); 518 519 /** 520 * <p>Returns the path of this project. The path is the fully qualified name of the project.</p> 521 * 522 * @return The path. Never returns null. 523 */ 524 String getPath(); 525 526 /** 527 * <p>Returns the names of the default tasks of this project. These are used when no tasks names are provided when 528 * starting the build.</p> 529 * 530 * @return The default task names. Returns an empty list if this project has no default tasks. 531 */ 532 List<String> getDefaultTasks(); 533 534 /** 535 * <p>Sets the names of the default tasks of this project. These are used when no tasks names are provided when 536 * starting the build.</p> 537 * 538 * @param defaultTasks The default task names. 539 */ 540 void setDefaultTasks(List<String> defaultTasks); 541 542 /** 543 * <p>Sets the names of the default tasks of this project. These are used when no tasks names are provided when 544 * starting the build.</p> 545 * 546 * @param defaultTasks The default task names. 547 */ 548 void defaultTasks(String... defaultTasks); 549 550 /** 551 * <p>Declares that this project has an evaluation dependency on the project with the given path.</p> 552 * 553 * @param path The path of the project which this project depends on. 554 * @return The project which this project depends on. 555 * @throws UnknownProjectException If no project with the given path exists. 556 */ 557 Project evaluationDependsOn(String path) throws UnknownProjectException; 558 559 /** 560 * <p>Declares that this project has an evaluation dependency on each of its child projects.</p> 561 * 562 */ 563 void evaluationDependsOnChildren(); 564 565 /** 566 * <p>Locates a project by path. If the path is relative, it is interpreted relative to this project.</p> 567 * 568 * @param path The path. 569 * @return The project with the given path. Returns null if no such project exists. 570 */ 571 Project findProject(String path); 572 573 /** 574 * <p>Locates a project by path. If the path is relative, it is interpreted relative to this project.</p> 575 * 576 * @param path The path. 577 * @return The project with the given path. Never returns null. 578 * @throws UnknownProjectException If no project with the given path exists. 579 */ 580 Project project(String path) throws UnknownProjectException; 581 582 /** 583 * <p>Locates a project by path and configures it using the given closure. If the path is relative, it is 584 * interpreted relative to this project. The target project is passed to the closure as the closure's delegate.</p> 585 * 586 * @param path The path. 587 * @param configureClosure The closure to use to configure the project. 588 * @return The project with the given path. Never returns null. 589 * @throws UnknownProjectException If no project with the given path exists. 590 */ 591 Project project(String path, @DelegatesTo(value = Project.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(ReturnType.class) Closure configureClosure); 592 593 /** 594 * <p>Locates a project by path and configures it using the given action. If the path is relative, it is 595 * interpreted relative to this project.</p> 596 * 597 * @param path The path. 598 * @param configureAction The action to use to configure the project. 599 * @return The project with the given path. Never returns null. 600 * @throws UnknownProjectException If no project with the given path exists. 601 * 602 * @since 3.4 603 */ 604 Project project(String path, Action<? super Project> configureAction); 605 606 /** 607 * <p>Returns a map of the tasks contained in this project, and optionally its subprojects.</p> 608 * 609 * @param recursive If true, returns the tasks of this project and its subprojects. If false, returns the tasks of 610 * just this project. 611 * @return A map from project to a set of tasks. 612 */ 613 Map<Project, Set<Task>> getAllTasks(boolean recursive); 614 615 /** 616 * <p>Returns the set of tasks with the given name contained in this project, and optionally its subprojects.</p> 617 * 618 * @param name The name of the task to locate. 619 * @param recursive If true, returns the tasks of this project and its subprojects. If false, returns the tasks of 620 * just this project. 621 * @return The set of tasks. Returns an empty set if no such tasks exist in this project. 622 */ 623 Set<Task> getTasksByName(String name, boolean recursive); 624 625 /** 626 * <p>The directory containing the project build file.</p> 627 * 628 * @return The project directory. Never returns null. 629 */ 630 File getProjectDir(); 631 632 /** 633 * <p>Resolves a file path relative to the project directory of this project. This method converts the supplied path 634 * based on its type:</p> 635 * 636 * <ul> 637 * 638 * <li>A {@link CharSequence}, including {@link String} or {@link groovy.lang.GString}. Interpreted relative to the project directory. A string 639 * that starts with {@code file:} is treated as a file URL.</li> 640 * 641 * <li>A {@link File}. If the file is an absolute file, it is returned as is. Otherwise, the file's path is 642 * interpreted relative to the project directory.</li> 643 * 644 * <li>A {@link java.nio.file.Path}. The path must be associated with the default provider and is treated the 645 * same way as an instance of {@code File}.</li> 646 * 647 * <li>A {@link java.net.URI} or {@link java.net.URL}. The URL's path is interpreted as the file path. Currently, only 648 * {@code file:} URLs are supported.</li> 649 * 650 * <li>A {@link Closure}. The closure's return value is resolved recursively.</li> 651 * 652 * <li>A {@link java.util.concurrent.Callable}. The callable's return value is resolved recursively.</li> 653 * 654 * </ul> 655 * 656 * @param path The object to resolve as a File. 657 * @return The resolved file. Never returns null. 658 */ 659 File file(Object path); 660 661 /** 662 * <p>Resolves a file path relative to the project directory of this project and validates it using the given 663 * scheme. See {@link PathValidation} for the list of possible validations.</p> 664 * 665 * @param path An object which toString method value is interpreted as a relative path to the project directory. 666 * @param validation The validation to perform on the file. 667 * @return The resolved file. Never returns null. 668 * @throws InvalidUserDataException When the file does not meet the given validation constraint. 669 */ 670 File file(Object path, PathValidation validation) throws InvalidUserDataException; 671 672 /** 673 * <p>Resolves a file path to a URI, relative to the project directory of this project. Evaluates the provided path 674 * object as described for {@link #file(Object)}, with the exception that any URI scheme is supported, not just 675 * 'file:' URIs.</p> 676 * 677 * @param path The object to resolve as a URI. 678 * @return The resolved URI. Never returns null. 679 */ 680 URI uri(Object path); 681 682 /** 683 * <p>Returns the relative path from the project directory to the given path. The given path object is (logically) 684 * resolved as described for {@link #file(Object)}, from which a relative path is calculated.</p> 685 * 686 * @param path The path to convert to a relative path. 687 * @return The relative path. Never returns null. 688 */ 689 String relativePath(Object path); 690 691 /** 692 * <p>Returns a {@link ConfigurableFileCollection} containing the given files. You can pass any of the following 693 * types to this method:</p> 694 * 695 * <ul> <li>A {@link CharSequence}, including {@link String} or {@link groovy.lang.GString}. Interpreted relative to the project directory, as per {@link #file(Object)}. A string 696 * that starts with {@code file:} is treated as a file URL.</li> 697 * 698 * <li>A {@link File}. Interpreted relative to the project directory, as per {@link #file(Object)}.</li> 699 * 700 * <li>A {@link java.nio.file.Path} as defined by {@link #file(Object)}.</li> 701 * 702 * <li>A {@link java.net.URI} or {@link java.net.URL}. The URL's path is interpreted as a file path. Currently, only 703 * {@code file:} URLs are supported.</li> 704 * 705 * <li>A {@link java.util.Collection}, {@link Iterable}, or an array. May contain any of the types listed here. The elements of the collection 706 * are recursively converted to files.</li> 707 * 708 * <li>A {@link org.gradle.api.file.FileCollection}. The contents of the collection are included in the returned 709 * collection.</li> 710 * 711 * <li>A {@link java.util.concurrent.Callable}. The {@code call()} method may return any of the types listed here. 712 * The return value of the {@code call()} method is recursively converted to files. A {@code null} return value is 713 * treated as an empty collection.</li> 714 * 715 * <li>A Closure. May return any of the types listed here. The return value of the closure is recursively converted 716 * to files. A {@code null} return value is treated as an empty collection.</li> 717 * 718 * <li>A {@link Task}. Converted to the task's output files.</li> 719 * 720 * <li>A {@link org.gradle.api.tasks.TaskOutputs}. Converted to the output files the related task.</li> 721 * 722 * <li>Anything else is treated as a failure.</li> 723 * 724 * </ul> 725 * 726 * <p>The returned file collection is lazy, so that the paths are evaluated only when the contents of the file 727 * collection are queried. The file collection is also live, so that it evaluates the above each time the contents 728 * of the collection is queried.</p> 729 * 730 * <p>The returned file collection maintains the iteration order of the supplied paths.</p> 731 * 732 * @param paths The paths to the files. May be empty. 733 * @return The file collection. Never returns null. 734 */ 735 ConfigurableFileCollection files(Object... paths); 736 737 /** 738 * <p>Creates a new {@code ConfigurableFileCollection} using the given paths. The paths are evaluated as per {@link 739 * #files(Object...)}. The file collection is configured using the given closure. The file collection is passed to 740 * the closure as its delegate. Example:</p> 741 * <pre> 742 * files "$buildDir/classes" { 743 * builtBy 'compile' 744 * } 745 * </pre> 746 * <p>The returned file collection is lazy, so that the paths are evaluated only when the contents of the file 747 * collection are queried. The file collection is also live, so that it evaluates the above each time the contents 748 * of the collection is queried.</p> 749 * 750 * @param paths The contents of the file collection. Evaluated as per {@link #files(Object...)}. 751 * @param configureClosure The closure to use to configure the file collection. 752 * @return the configured file tree. Never returns null. 753 */ 754 ConfigurableFileCollection files(Object paths, @DelegatesTo(value = ConfigurableFileCollection.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(ReturnType.class) Closure configureClosure); 755 756 /** 757 * <p>Creates a new {@code ConfigurableFileCollection} using the given paths. The paths are evaluated as per {@link 758 * #files(Object...)}. The file collection is configured using the given action. Example:</p> 759 * <pre> 760 * files "$buildDir/classes" { 761 * builtBy 'compile' 762 * } 763 * </pre> 764 * <p>The returned file collection is lazy, so that the paths are evaluated only when the contents of the file 765 * collection are queried. The file collection is also live, so that it evaluates the above each time the contents 766 * of the collection is queried.</p> 767 * 768 * @param paths The contents of the file collection. Evaluated as per {@link #files(Object...)}. 769 * @param configureAction The action to use to configure the file collection. 770 * @return the configured file tree. Never returns null. 771 * @since 3.5 772 */ 773 ConfigurableFileCollection files(Object paths, Action<? super ConfigurableFileCollection> configureAction); 774 775 /** 776 * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated 777 * as per {@link #file(Object)}.</p> 778 * 779 * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are 780 * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are 781 * queried.</p> 782 * 783 * <pre autoTested=''> 784 * def myTree = fileTree("src") 785 * myTree.include "**&#47;*.java" 786 * myTree.builtBy "someTask" 787 * 788 * task copy(type: Copy) { 789 * from myTree 790 * } 791 * </pre> 792 * 793 * @param baseDir The base directory of the file tree. Evaluated as per {@link #file(Object)}. 794 * @return the file tree. Never returns null. 795 */ 796 ConfigurableFileTree fileTree(Object baseDir); 797 798 /** 799 * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated 800 * as per {@link #file(Object)}. The closure will be used to configure the new file tree. 801 * The file tree is passed to the closure as its delegate. Example:</p> 802 * 803 * <pre autoTested=''> 804 * def myTree = fileTree('src') { 805 * exclude '**&#47;.data/**' 806 * builtBy 'someTask' 807 * } 808 * 809 * task copy(type: Copy) { 810 * from myTree 811 * } 812 * </pre> 813 * 814 * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are 815 * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are 816 * queried.</p> 817 * 818 * @param baseDir The base directory of the file tree. Evaluated as per {@link #file(Object)}. 819 * @param configureClosure Closure to configure the {@code ConfigurableFileTree} object. 820 * @return the configured file tree. Never returns null. 821 */ 822 ConfigurableFileTree fileTree(Object baseDir, @DelegatesTo(value = ConfigurableFileTree.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(ReturnType.class) Closure configureClosure); 823 824 /** 825 * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated 826 * as per {@link #file(Object)}. The action will be used to configure the new file tree. Example:</p> 827 * 828 * <pre autoTested=''> 829 * def myTree = fileTree('src') { 830 * exclude '**&#47;.data/**' 831 * builtBy 'someTask' 832 * } 833 * 834 * task copy(type: Copy) { 835 * from myTree 836 * } 837 * </pre> 838 * 839 * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are 840 * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are 841 * queried.</p> 842 * 843 * @param baseDir The base directory of the file tree. Evaluated as per {@link #file(Object)}. 844 * @param configureAction Action to configure the {@code ConfigurableFileTree} object. 845 * @return the configured file tree. Never returns null. 846 * @since 3.5 847 */ 848 ConfigurableFileTree fileTree(Object baseDir, Action<? super ConfigurableFileTree> configureAction); 849 850 /** 851 * <p>Creates a new {@code ConfigurableFileTree} using the provided map of arguments. The map will be applied as 852 * properties on the new file tree. Example:</p> 853 * 854 * <pre autoTested=''> 855 * def myTree = fileTree(dir:'src', excludes:['**&#47;ignore/**', '**&#47;.data/**']) 856 * 857 * task copy(type: Copy) { 858 * from myTree 859 * } 860 * </pre> 861 * 862 * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are 863 * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are 864 * queried.</p> 865 * 866 * @param args map of property assignments to {@code ConfigurableFileTree} object 867 * @return the configured file tree. Never returns null. 868 */ 869 ConfigurableFileTree fileTree(Map<String, ?> args); 870 871 /** 872 * <p>Creates a new {@code FileTree} which contains the contents of the given ZIP file. The given zipPath path is 873 * evaluated as per {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)} 874 * method to unzip a ZIP file.</p> 875 * 876 * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are 877 * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are 878 * queried.</p> 879 * 880 * @param zipPath The ZIP file. Evaluated as per {@link #file(Object)}. 881 * @return the file tree. Never returns null. 882 */ 883 FileTree zipTree(Object zipPath); 884 885 /** 886 * Creates a new {@code FileTree} which contains the contents of the given TAR file. The given tarPath path can be: 887 * <ul> 888 * <li>an instance of {@link org.gradle.api.resources.Resource}</li> 889 * <li>any other object is evaluated as per {@link #file(Object)}</li> 890 * </ul> 891 * 892 * The returned file tree is lazy, so that it scans for files only when the contents of the file tree are 893 * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are 894 * queried. 895 * <p> 896 * Unless custom implementation of resources is passed, the tar tree attempts to guess the compression based on the file extension. 897 * <p> 898 * You can combine this method with the {@link #copy(groovy.lang.Closure)} 899 * method to untar a TAR file: 900 * 901 * <pre autoTested=''> 902 * task untar(type: Copy) { 903 * from tarTree('someCompressedTar.gzip') 904 * 905 * //tar tree attempts to guess the compression based on the file extension 906 * //however if you must specify the compression explicitly you can: 907 * from tarTree(resources.gzip('someTar.ext')) 908 * 909 * //in case you work with unconventionally compressed tars 910 * //you can provide your own implementation of a ReadableResource: 911 * //from tarTree(yourOwnResource as ReadableResource) 912 * 913 * into 'dest' 914 * } 915 * </pre> 916 * 917 * @param tarPath The TAR file or an instance of {@link org.gradle.api.resources.Resource}. 918 * @return the file tree. Never returns null. 919 */ 920 FileTree tarTree(Object tarPath); 921 922 /** 923 * Creates a {@code Provider} implementation based on the provided value. 924 * 925 * @param value The {@code java.util.concurrent.Callable} use to calculate the value. 926 * @return The provider. Never returns null. 927 * @throws org.gradle.api.InvalidUserDataException If the provided value is null. 928 * @see org.gradle.api.provider.ProviderFactory#provider(Callable) 929 * @since 4.0 930 */ 931 @Incubating 932 <T> Provider<T> provider(Callable<T> value); 933 934 /** 935 * Creates a {@code PropertyState} implementation based on the provided class. 936 * 937 * @param clazz The class to be used for property state. 938 * @return The property state. Never returns null. 939 * @throws org.gradle.api.InvalidUserDataException If the provided class is null. 940 * @see org.gradle.api.provider.ProviderFactory#property(Class) 941 * @since 4.0 942 */ 943 @Incubating 944 <T> PropertyState<T> property(Class<T> clazz); 945 946 /** 947 * Provides access to methods to create various kinds of {@link Provider} instances. 948 * 949 * @since 4.0 950 */ 951 @Incubating 952 ProviderFactory getProviders(); 953 954 /** 955 * Provides access to methods to create various kinds of model objects. 956 * 957 * @since 4.0 958 */ 959 @Incubating 960 ObjectFactory getObjects(); 961 962 /** 963 * Creates a directory and returns a file pointing to it. 964 * 965 * @param path The path for the directory to be created. Evaluated as per {@link #file(Object)}. 966 * @return the created directory 967 * @throws org.gradle.api.InvalidUserDataException If the path points to an existing file. 968 */ 969 File mkdir(Object path); 970 971 /** 972 * Deletes files and directories. 973 * <p> 974 * This will not follow symlinks. If you need to follow symlinks too use {@link #delete(Action)}. 975 * 976 * @param paths Any type of object accepted by {@link org.gradle.api.Project#files(Object...)} 977 * @return true if anything got deleted, false otherwise 978 */ 979 boolean delete(Object... paths); 980 981 /** 982 * Deletes the specified files. The given action is used to configure a {@link DeleteSpec}, which is then used to 983 * delete the files. 984 * <p>Example: 985 * <pre> 986 * project.delete { 987 * delete 'somefile' 988 * followSymlinks = true 989 * } 990 * </pre> 991 * 992 * @param action Action to configure the DeleteSpec 993 * @return {@link WorkResult} that can be used to check if delete did any work. 994 */ 995 WorkResult delete(Action<? super DeleteSpec> action); 996 997 /** 998 * Executes a Java main class. The closure configures a {@link org.gradle.process.JavaExecSpec}. 999 * 1000 * @param closure The closure for configuring the execution. 1001 * @return the result of the execution 1002 */ 1003 ExecResult javaexec(@DelegatesTo(value = JavaExecSpec.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.process.JavaExecSpec"}) Closure closure); 1004 1005 /** 1006 * Executes an external Java process. 1007 * <p> 1008 * The given action configures a {@link org.gradle.process.JavaExecSpec}, which is used to launch the process. 1009 * This method blocks until the process terminates, with its result being returned. 1010 * 1011 * @param action The action for configuring the execution. 1012 * @return the result of the execution 1013 */ 1014 ExecResult javaexec(Action<? super JavaExecSpec> action); 1015 1016 /** 1017 * Executes an external command. The closure configures a {@link org.gradle.process.ExecSpec}. 1018 * 1019 * @param closure The closure for configuring the execution. 1020 * @return the result of the execution 1021 */ 1022 ExecResult exec(@DelegatesTo(value = ExecSpec.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.process.ExecSpec"}) Closure closure); 1023 1024 /** 1025 * Executes an external command. 1026 * <p> 1027 * The given action configures a {@link org.gradle.process.ExecSpec}, which is used to launch the process. 1028 * This method blocks until the process terminates, with its result being returned. 1029 * 1030 * @param action The action for configuring the execution. 1031 * @return the result of the execution 1032 */ 1033 ExecResult exec(Action<? super ExecSpec> action); 1034 1035 /** 1036 * <p>Converts a name to an absolute project path, resolving names relative to this project.</p> 1037 * 1038 * @param path The path to convert. 1039 * @return The absolute path. 1040 */ 1041 String absoluteProjectPath(String path); 1042 1043 /** 1044 * <p>Converts a name to a project path relative to this project.</p> 1045 * 1046 * @param path The path to convert. 1047 * @return The relative path. 1048 */ 1049 String relativeProjectPath(String path); 1050 1051 /** 1052 * <p>Returns the <code>AntBuilder</code> for this project. You can use this in your build file to execute ant 1053 * tasks. See example below.</p> 1054 * <pre autoTested=''> 1055 * task printChecksum { 1056 * doLast { 1057 * ant { 1058 * //using ant checksum task to store the file checksum in the checksumOut ant property 1059 * checksum(property: 'checksumOut', file: 'someFile.txt') 1060 * 1061 * //we can refer to the ant property created by checksum task: 1062 * println "The checksum is: " + checksumOut 1063 * } 1064 * 1065 * //we can refer to the ant property later as well: 1066 * println "I just love to print checksums: " + ant.checksumOut 1067 * } 1068 * } 1069 * </pre> 1070 * 1071 * Consider following example of ant target: 1072 * <pre> 1073 * &lt;target name='printChecksum'&gt; 1074 * &lt;checksum property='checksumOut'&gt; 1075 * &lt;fileset dir='.'&gt; 1076 * &lt;include name='agile.txt'/&gt; 1077 * &lt;/fileset&gt; 1078 * &lt;/checksum&gt; 1079 * &lt;echo&gt;The checksum is: ${checksumOut}&lt;/echo&gt; 1080 * &lt;/target&gt; 1081 * </pre> 1082 * 1083 * Here's how it would look like in gradle. Observe how the ant XML is represented in groovy by the ant builder 1084 * <pre autoTested=''> 1085 * task printChecksum { 1086 * doLast { 1087 * ant { 1088 * checksum(property: 'checksumOut') { 1089 * fileset(dir: '.') { 1090 * include name: 'agile1.txt' 1091 * } 1092 * } 1093 * } 1094 * logger.lifecycle("The checksum is $ant.checksumOut") 1095 * } 1096 * } 1097 * </pre> 1098 * 1099 * @return The <code>AntBuilder</code> for this project. Never returns null. 1100 */ 1101 AntBuilder getAnt(); 1102 1103 /** 1104 * <p>Creates an additional <code>AntBuilder</code> for this project. You can use this in your build file to execute 1105 * ant tasks.</p> 1106 * 1107 * @return Creates an <code>AntBuilder</code> for this project. Never returns null. 1108 * @see #getAnt() 1109 */ 1110 AntBuilder createAntBuilder(); 1111 1112 /** 1113 * <p>Executes the given closure against the <code>AntBuilder</code> for this project. You can use this in your 1114 * build file to execute ant tasks. The <code>AntBuilder</code> is passed to the closure as the closure's 1115 * delegate. See example in javadoc for {@link #getAnt()}</p> 1116 * 1117 * @param configureClosure The closure to execute against the <code>AntBuilder</code>. 1118 * @return The <code>AntBuilder</code>. Never returns null. 1119 */ 1120 AntBuilder ant(@DelegatesTo(value = AntBuilder.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(ReturnType.class) Closure configureClosure); 1121 1122 /** 1123 * <p>Executes the given action against the <code>AntBuilder</code> for this project. You can use this in your 1124 * build file to execute ant tasks. See example in javadoc for {@link #getAnt()}</p> 1125 * 1126 * @param configureAction The action to execute against the <code>AntBuilder</code>. 1127 * @return The <code>AntBuilder</code>. Never returns null. 1128 * @since 3.5 1129 */ 1130 AntBuilder ant(Action<? super AntBuilder> configureAction); 1131 1132 /** 1133 * Returns the configurations of this project. 1134 * 1135 * <h3>Examples:</h3> See docs for {@link ConfigurationContainer} 1136 * 1137 * @return The configuration of this project. 1138 */ 1139 ConfigurationContainer getConfigurations(); 1140 1141 /** 1142 * <p>Configures the dependency configurations for this project. 1143 * 1144 * <p>This method executes the given closure against the {@link ConfigurationContainer} 1145 * for this project. The {@link ConfigurationContainer} is passed to the closure as the closure's delegate. 1146 * 1147 * <h3>Examples:</h3> See docs for {@link ConfigurationContainer} 1148 * 1149 * @param configureClosure the closure to use to configure the dependency configurations. 1150 */ 1151 void configurations(@DelegatesTo(value = ConfigurationContainer.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.artifacts.ConfigurationContainer"}) Closure configureClosure); 1152 1153 /** 1154 * Returns a handler for assigning artifacts produced by the project to configurations. 1155 * <h3>Examples:</h3>See docs for {@link ArtifactHandler} 1156 */ 1157 ArtifactHandler getArtifacts(); 1158 1159 /** 1160 * <p>Configures the published artifacts for this project. 1161 * 1162 * <p>This method executes the given closure against the {@link ArtifactHandler} for this project. The {@link 1163 * ArtifactHandler} is passed to the closure as the closure's delegate. 1164 * 1165 * <p>Example: 1166 * <pre autoTested=''> 1167 * configurations { 1168 * //declaring new configuration that will be used to associate with artifacts 1169 * schema 1170 * } 1171 * 1172 * task schemaJar(type: Jar) { 1173 * //some imaginary task that creates a jar artifact with the schema 1174 * } 1175 * 1176 * //associating the task that produces the artifact with the configuration 1177 * artifacts { 1178 * //configuration name and the task: 1179 * schema schemaJar 1180 * } 1181 * </pre> 1182 * 1183 * @param configureClosure the closure to use to configure the published artifacts. 1184 */ 1185 void artifacts(@DelegatesTo(value = ArtifactHandler.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.artifacts.dsl.ArtifactHandler"}) Closure configureClosure); 1186 1187 /** 1188 * <p>Configures the published artifacts for this project. 1189 * 1190 * <p>This method executes the given action against the {@link ArtifactHandler} for this project. 1191 * 1192 * <p>Example: 1193 * <pre autoTested=''> 1194 * configurations { 1195 * //declaring new configuration that will be used to associate with artifacts 1196 * schema 1197 * } 1198 * 1199 * task schemaJar(type: Jar) { 1200 * //some imaginary task that creates a jar artifact with the schema 1201 * } 1202 * 1203 * //associating the task that produces the artifact with the configuration 1204 * artifacts { 1205 * //configuration name and the task: 1206 * schema schemaJar 1207 * } 1208 * </pre> 1209 * 1210 * @param configureAction the action to use to configure the published artifacts. 1211 * @since 3.5 1212 */ 1213 void artifacts(Action<? super ArtifactHandler> configureAction); 1214 1215 /** 1216 * <p>Returns the {@link Convention} for this project.</p> <p/> <p>You can access this property in your build file 1217 * using <code>convention</code>. You can also can also access the properties and methods of the convention object 1218 * as if they were properties and methods of this project. See <a href="#properties">here</a> for more details</p> 1219 * 1220 * @return The <code>Convention</code>. Never returns null. 1221 */ 1222 Convention getConvention(); 1223 1224 /** 1225 * <p>Compares the nesting level of this project with another project of the multi-project hierarchy.</p> 1226 * 1227 * @param otherProject The project to compare the nesting level with. 1228 * @return a negative integer, zero, or a positive integer as this project has a nesting level less than, equal to, 1229 * or greater than the specified object. 1230 * @see #getDepth() 1231 */ 1232 int depthCompare(Project otherProject); 1233 1234 /** 1235 * <p>Returns the nesting level of a project in a multi-project hierarchy. For single project builds this is always 1236 * 0. In a multi-project hierarchy 0 is returned for the root project.</p> 1237 */ 1238 int getDepth(); 1239 1240 /** 1241 * <p>Returns the tasks of this project.</p> 1242 * 1243 * @return the tasks of this project. 1244 */ 1245 TaskContainer getTasks(); 1246 1247 /** 1248 * <p>Configures the sub-projects of this project</p> 1249 * 1250 * <p>This method executes the given {@link Action} against the sub-projects of this project.</p> 1251 * 1252 * @param action The action to execute. 1253 */ 1254 void subprojects(Action<? super Project> action); 1255 1256 /** 1257 * <p>Configures the sub-projects of this project.</p> 1258 * 1259 * <p>This method executes the given closure against each of the sub-projects of this project. The target {@link 1260 * Project} is passed to the closure as the closure's delegate.</p> 1261 * 1262 * @param configureClosure The closure to execute. 1263 */ 1264 void subprojects(@DelegatesTo(value = Project.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.Project"}) Closure configureClosure); 1265 1266 /** 1267 * <p>Configures this project and each of its sub-projects.</p> 1268 * 1269 * <p>This method executes the given {@link Action} against this project and each of its sub-projects.</p> 1270 * 1271 * @param action The action to execute. 1272 */ 1273 void allprojects(Action<? super Project> action); 1274 1275 /** 1276 * <p>Configures this project and each of its sub-projects.</p> 1277 * 1278 * <p>This method executes the given closure against this project and its sub-projects. The target {@link Project} 1279 * is passed to the closure as the closure's delegate.</p> 1280 * 1281 * @param configureClosure The closure to execute. 1282 */ 1283 void allprojects(@DelegatesTo(value = Project.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.Project"}) Closure configureClosure); 1284 1285 /** 1286 * Adds an action to execute immediately before this project is evaluated. 1287 * 1288 * @param action the action to execute. 1289 */ 1290 void beforeEvaluate(Action<? super Project> action); 1291 1292 /** 1293 * Adds an action to execute immediately after this project is evaluated. 1294 * 1295 * @param action the action to execute. 1296 */ 1297 void afterEvaluate(Action<? super Project> action); 1298 1299 /** 1300 * <p>Adds a closure to be called immediately before this project is evaluated. The project is passed to the closure 1301 * as a parameter.</p> 1302 * 1303 * @param closure The closure to call. 1304 */ 1305 void beforeEvaluate(@ClosureParams(value = SimpleType.class, options = {"org.gradle.api.Project"}) Closure closure); 1306 1307 /** 1308 * <p>Adds a closure to be called immediately after this project has been evaluated. The project is passed to the 1309 * closure as a parameter. Such a listener gets notified when the build file belonging to this project has been 1310 * executed. A parent project may for example add such a listener to its child project. Such a listener can further 1311 * configure those child projects based on the state of the child projects after their build files have been 1312 * run.</p> 1313 * 1314 * @param closure The closure to call. 1315 */ 1316 void afterEvaluate(@ClosureParams(value = SimpleType.class, options = {"org.gradle.api.Project"}) Closure closure); 1317 1318 /** 1319 * <p>Determines if this project has the given property. See <a href="#properties">here</a> for details of the 1320 * properties which are available for a project.</p> 1321 * 1322 * @param propertyName The name of the property to locate. 1323 * @return True if this project has the given property, false otherwise. 1324 */ 1325 boolean hasProperty(String propertyName); 1326 1327 /** 1328 * <p>Returns the properties of this project. See <a href="#properties">here</a> for details of the properties which 1329 * are available for a project.</p> 1330 * 1331 * @return A map from property name to value. 1332 */ 1333 Map<String, ?> getProperties(); 1334 1335 /** 1336 * <p>Returns the value of the given property. This method locates a property as follows:</p> 1337 * 1338 * <ol> 1339 * 1340 * <li>If this project object has a property with the given name, return the value of the property.</li> 1341 * 1342 * <li>If this project has an extension with the given name, return the extension.</li> 1343 * 1344 * <li>If this project's convention object has a property with the given name, return the value of the 1345 * property.</li> 1346 * 1347 * <li>If this project has an extra property with the given name, return the value of the property.</li> 1348 * 1349 * <li>If this project has a task with the given name, return the task.</li> 1350 * 1351 * <li>Search up through this project's ancestor projects for a convention property or extra property with the 1352 * given name.</li> 1353 * 1354 * <li>If not found, a {@link MissingPropertyException} is thrown.</li> 1355 * 1356 * </ol> 1357 * 1358 * @param propertyName The name of the property. 1359 * @return The value of the property, possibly null. 1360 * @throws MissingPropertyException When the given property is unknown. 1361 * @see Project#findProperty(String) 1362 */ 1363 Object property(String propertyName) throws MissingPropertyException; 1364 1365 /** 1366 * <p>Returns the value of the given property or null if not found. 1367 * This method locates a property as follows:</p> 1368 * 1369 * <ol> 1370 * 1371 * <li>If this project object has a property with the given name, return the value of the property.</li> 1372 * 1373 * <li>If this project has an extension with the given name, return the extension.</li> 1374 * 1375 * <li>If this project's convention object has a property with the given name, return the value of the 1376 * property.</li> 1377 * 1378 * <li>If this project has an extra property with the given name, return the value of the property.</li> 1379 * 1380 * <li>If this project has a task with the given name, return the task.</li> 1381 * 1382 * <li>Search up through this project's ancestor projects for a convention property or extra property with the 1383 * given name.</li> 1384 * 1385 * <li>If not found, null value is returned.</li> 1386 * 1387 * </ol> 1388 * 1389 * @param propertyName The name of the property. 1390 * @return The value of the property, possibly null or null if not found. 1391 * @see Project#property(String) 1392 */ 1393 @Incubating @Nullable 1394 Object findProperty(String propertyName); 1395 1396 /** 1397 * <p>Returns the logger for this project. You can use this in your build file to write log messages.</p> 1398 * 1399 * @return The logger. Never returns null. 1400 */ 1401 Logger getLogger(); 1402 1403 /** 1404 * <p>Returns the {@link org.gradle.api.invocation.Gradle} invocation which this project belongs to.</p> 1405 * 1406 * @return The Gradle object. Never returns null. 1407 */ 1408 Gradle getGradle(); 1409 1410 /** 1411 * Returns the {@link org.gradle.api.logging.LoggingManager} which can be used to receive logging and to control the 1412 * standard output/error capture for this project's build script. By default, System.out is redirected to the Gradle 1413 * logging system at the QUIET log level, and System.err is redirected at the ERROR log level. 1414 * 1415 * @return the LoggingManager. Never returns null. 1416 */ 1417 LoggingManager getLogging(); 1418 1419 /** 1420 * <p>Configures an object via a closure, with the closure's delegate set to the supplied object. This way you don't 1421 * have to specify the context of a configuration statement multiple times. <p/> Instead of:</p> 1422 * <pre> 1423 * MyType myType = new MyType() 1424 * myType.doThis() 1425 * myType.doThat() 1426 * </pre> 1427 * <p/> you can do: 1428 * <pre> 1429 * MyType myType = configure(new MyType()) { 1430 * doThis() 1431 * doThat() 1432 * } 1433 * </pre> 1434 * 1435 * <p>The object being configured is also passed to the closure as a parameter, so you can access it explicitly if 1436 * required:</p> 1437 * <pre> 1438 * configure(someObj) { obj -> obj.doThis() } 1439 * </pre> 1440 * 1441 * @param object The object to configure 1442 * @param configureClosure The closure with configure statements 1443 * @return The configured object 1444 */ 1445 Object configure(Object object, Closure configureClosure); 1446 1447 /** 1448 * Configures a collection of objects via a closure. This is equivalent to calling {@link #configure(Object, 1449 * groovy.lang.Closure)} for each of the given objects. 1450 * 1451 * @param objects The objects to configure 1452 * @param configureClosure The closure with configure statements 1453 * @return The configured objects. 1454 */ 1455 Iterable<?> configure(Iterable<?> objects, Closure configureClosure); 1456 1457 /** 1458 * Configures a collection of objects via an action. 1459 * 1460 * @param objects The objects to configure 1461 * @param configureAction The action to apply to each object 1462 * @return The configured objects. 1463 */ 1464 <T> Iterable<T> configure(Iterable<T> objects, Action<? super T> configureAction); 1465 1466 /** 1467 * Returns a handler to create repositories which are used for retrieving dependencies and uploading artifacts 1468 * produced by the project. 1469 * 1470 * @return the repository handler. Never returns null. 1471 */ 1472 RepositoryHandler getRepositories(); 1473 1474 /** 1475 * <p>Configures the repositories for this project. 1476 * 1477 * <p>This method executes the given closure against the {@link RepositoryHandler} for this project. The {@link 1478 * RepositoryHandler} is passed to the closure as the closure's delegate. 1479 * 1480 * @param configureClosure the closure to use to configure the repositories. 1481 */ 1482 void repositories(@DelegatesTo(value = RepositoryHandler.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.artifacts.dsl.RepositoryHandler"}) Closure configureClosure); 1483 1484 /** 1485 * Returns the dependency handler of this project. The returned dependency handler instance can be used for adding 1486 * new dependencies. For accessing already declared dependencies, the configurations can be used. 1487 * 1488 * <h3>Examples:</h3> 1489 * See docs for {@link DependencyHandler} 1490 * 1491 * @return the dependency handler. Never returns null. 1492 * @see #getConfigurations() 1493 */ 1494 DependencyHandler getDependencies(); 1495 1496 /** 1497 * <p>Configures the dependencies for this project. 1498 * 1499 * <p>This method executes the given closure against the {@link DependencyHandler} for this project. The {@link 1500 * DependencyHandler} is passed to the closure as the closure's delegate. 1501 * 1502 * <h3>Examples:</h3> 1503 * See docs for {@link DependencyHandler} 1504 * 1505 * @param configureClosure the closure to use to configure the dependencies. 1506 */ 1507 void dependencies(@DelegatesTo(value = DependencyHandler.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.artifacts.dsl.DependencyHandler"}) Closure configureClosure); 1508 1509 /** 1510 * Returns the build script handler for this project. You can use this handler to query details about the build 1511 * script for this project, and manage the classpath used to compile and execute the project's build script. 1512 * 1513 * @return the classpath handler. Never returns null. 1514 */ 1515 ScriptHandler getBuildscript(); 1516 1517 /** 1518 * <p>Configures the build script classpath for this project. 1519 * 1520 * <p>The given closure is executed against this project's {@link ScriptHandler}. The {@link ScriptHandler} is 1521 * passed to the closure as the closure's delegate. 1522 * 1523 * @param configureClosure the closure to use to configure the build script classpath. 1524 */ 1525 void buildscript(@DelegatesTo(value = ScriptHandler.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.initialization.dsl.ScriptHandler"}) Closure configureClosure); 1526 1527 /** 1528 * Copies the specified files. The given closure is used to configure a {@link CopySpec}, which is then used to 1529 * copy the files. Example: 1530 * <pre> 1531 * copy { 1532 * from configurations.runtime 1533 * into 'build/deploy/lib' 1534 * } 1535 * </pre> 1536 * Note that CopySpecs can be nested: 1537 * <pre> 1538 * copy { 1539 * into 'build/webroot' 1540 * exclude '**&#47;.svn/**' 1541 * from('src/main/webapp') { 1542 * include '**&#47;*.jsp' 1543 * filter(ReplaceTokens, tokens:[copyright:'2009', version:'2.3.1']) 1544 * } 1545 * from('src/main/js') { 1546 * include '**&#47;*.js' 1547 * } 1548 * } 1549 * </pre> 1550 * 1551 * @param closure Closure to configure the CopySpec 1552 * @return {@link WorkResult} that can be used to check if the copy did any work. 1553 */ 1554 WorkResult copy(@DelegatesTo(value = CopySpec.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.file.CopySpec"}) Closure closure); 1555 1556 /** 1557 * Copies the specified files. The given action is used to configure a {@link CopySpec}, which is then used to 1558 * copy the files. 1559 * @see #copy(Closure) 1560 * @param action Action to configure the CopySpec 1561 * @return {@link WorkResult} that can be used to check if the copy did any work. 1562 */ 1563 WorkResult copy(Action<? super CopySpec> action); 1564 1565 /** 1566 * Creates a {@link CopySpec} which can later be used to copy files or create an archive. The given closure is used 1567 * to configure the {@link CopySpec} before it is returned by this method. 1568 * 1569 * <pre autoTested=''> 1570 * def baseSpec = copySpec { 1571 * from "source" 1572 * include "**&#47;*.java" 1573 * } 1574 * 1575 * task copy(type: Copy) { 1576 * into "target" 1577 * with baseSpec 1578 * } 1579 * </pre> 1580 * 1581 * @param closure Closure to configure the CopySpec 1582 * @return The CopySpec 1583 */ 1584 CopySpec copySpec(@DelegatesTo(value = CopySpec.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.file.CopySpec"}) Closure closure); 1585 1586 /** 1587 * Creates a {@link CopySpec} which can later be used to copy files or create an archive. The given action is used 1588 * to configure the {@link CopySpec} before it is returned by this method. 1589 * 1590 * @see #copySpec(Closure) 1591 * @param action Action to configure the CopySpec 1592 * @return The CopySpec 1593 */ 1594 CopySpec copySpec(Action<? super CopySpec> action); 1595 1596 /** 1597 * Creates a {@link CopySpec} which can later be used to copy files or create an archive. 1598 * 1599 * @return a newly created copy spec 1600 */ 1601 CopySpec copySpec(); 1602 1603 /** 1604 * Synchronizes the contents of a destination directory with some source directories and files. 1605 * The given action is used to configure a {@link CopySpec}, which is then used to synchronize the files. 1606 * 1607 * <p> 1608 * This method is like the {@link #copy(Action)} task, except the destination directory will only contain the files copied. 1609 * All files that exist in the destination directory will be deleted before copying files, unless a preserve option is specified. 1610 * 1611 * <p> 1612 * Example: 1613 * 1614 * <pre> 1615 * project.sync { 1616 * from 'my/shared/dependencyDir' 1617 * into 'build/deps/compile' 1618 * } 1619 * </pre> 1620 * Note that you can preserve output that already exists in the destination directory: 1621 * <pre> 1622 * project.sync { 1623 * from 'source' 1624 * into 'dest' 1625 * preserve { 1626 * include 'extraDir/**' 1627 * include 'dir1/**' 1628 * exclude 'dir1/extra.txt' 1629 * } 1630 * } 1631 * </pre> 1632 * 1633 * @param action Action to configure the CopySpec. 1634 * @since 4.0 1635 * @return {@link WorkResult} that can be used to check if the sync did any work. 1636 */ 1637 WorkResult sync(Action<? super CopySpec> action); 1638 1639 /** 1640 * Returns the evaluation state of this project. You can use this to access information about the evaluation of this 1641 * project, such as whether it has failed. 1642 * 1643 * @return the project state. Never returns null. 1644 */ 1645 ProjectState getState(); 1646 1647 /** 1648 * <p>Creates a container for managing named objects of the specified type. The specified type must have a public constructor which takes the name as a String parameter.<p> 1649 * 1650 * <p>All objects <b>MUST</b> expose their name as a bean property named "name". The name must be constant for the life of the object.</p> 1651 * 1652 * @param type The type of objects for the container to contain. 1653 * @param <T> The type of objects for the container to contain. 1654 * @return The container. 1655 */ 1656 <T> NamedDomainObjectContainer<T> container(Class<T> type); 1657 1658 /** 1659 * <p>Creates a container for managing named objects of the specified type. The given factory is used to create object instances.</p> 1660 * 1661 * <p>All objects <b>MUST</b> expose their name as a bean property named "name". The name must be constant for the life of the object.</p> 1662 * 1663 * @param type The type of objects for the container to contain. 1664 * @param factory The factory to use to create object instances. 1665 * @param <T> The type of objects for the container to contain. 1666 * @return The container. 1667 */ 1668 <T> NamedDomainObjectContainer<T> container(Class<T> type, NamedDomainObjectFactory<T> factory); 1669 1670 /** 1671 * <p>Creates a container for managing named objects of the specified type. The given closure is used to create object instances. The name of the instance to be created is passed as a parameter to 1672 * the closure.</p> 1673 * 1674 * <p>All objects <b>MUST</b> expose their name as a bean property named "name". The name must be constant for the life of the object.</p> 1675 * 1676 * @param type The type of objects for the container to contain. 1677 * @param factoryClosure The closure to use to create object instances. 1678 * @param <T> The type of objects for the container to contain. 1679 * @return The container. 1680 */ 1681 <T> NamedDomainObjectContainer<T> container(Class<T> type, @ClosureParams(value = SimpleType.class, options = {"java.lang.String"}) Closure factoryClosure); 1682 1683 /** 1684 * Allows adding DSL extensions to the project. Useful for plugin authors. 1685 * 1686 * @return Returned instance allows adding DSL extensions to the project 1687 */ 1688 ExtensionContainer getExtensions(); 1689 1690 /** 1691 * Provides access to resource-specific utility methods, for example factory methods that create various resources. 1692 * 1693 * @return Returned instance contains various resource-specific utility methods. 1694 */ 1695 ResourceHandler getResources(); 1696 1697 /** 1698 * Returns the software components produced by this project. 1699 * 1700 * @return The components for this project. 1701 */ 1702 @Incubating 1703 SoftwareComponentContainer getComponents(); 1704 1705 /** 1706 * Provides access to configuring input normalization. 1707 * 1708 * @since 4.0 1709 */ 1710 @Incubating 1711 InputNormalizationHandler getNormalization(); 1712 1713 /** 1714 * Configures input normalization. 1715 * 1716 * @since 4.0 1717 */ 1718 @Incubating 1719 void normalization(Action<? super InputNormalizationHandler> configuration); 1720 } View Code
一、关于feigin feigin是一种模板化,声明式的http客户端,feign可以通过注解绑定到接口上来简化Http请求访问。当然我们也可以在创建Feign对象时定制自定义解码器(xml或者json等格式解析)和错误处理。 二、添加SpringCloud对feign的支持 gradle配置: compile('org.springframework.cloud:spring-cloud-starter-feign') View Code feigin最基本使用方法: 1 interface GitHub { 2 @RequestLine("GET /repos/{owner}/{repo}/contributors") 3 List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo); 4 } 5 6 static class Contributor { 7 String login; 8 int contributions; 9 } 10 11 public static void main(String... args) { 12 GitHub github = Feign.builder() 13 .decoder(new GsonDecoder()) 14 .target(GitHub.class, "https://api.github.com"); 15 16 // Fetch and print a list of the contributors to this library. 17 List<Contributor> contributors = github.contributors("OpenFeign", "feign"); 18 for (Contributor contributor : contributors) { 19 System.out.println(contributor.login + " (" + contributor.contributions + ")"); 20 } 21 } View Code feign发送json与xml的格式的http请求: 1 interface LoginClient { 2 3 @RequestLine("POST /") 4 @Headers("Content-Type: application/xml") 5 @Body("<login \"user_name\"=\"{user_name}\" \"password\"=\"{password}\"/>") 6 void xml(@Param("user_name") String user, @Param("password") String password); 7 8 @RequestLine("POST /") 9 @Headers("Content-Type: application/json") 10 // json curly braces must be escaped! 11 @Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D") 12 void json(@Param("user_name") String user, @Param("password") String password); 13 } View Code 注意示例中需要添加对gson的支持 feign发送https信任所有证书的代码: 1 final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { 2 @Override 3 public void checkClientTrusted( 4 java.security.cert.X509Certificate[] chain, 5 String authType) { 6 } 7 8 @Override 9 public void checkServerTrusted( 10 java.security.cert.X509Certificate[] chain, 11 String authType) { 12 } 13 14 @Override 15 public java.security.cert.X509Certificate[] getAcceptedIssuers() { 16 return null; 17 } 18 }}; 19 final SSLContext sslContext = SSLContext.getInstance("TLSv1"); 20 sslContext.init(null, trustAllCerts, 21 new java.security.SecureRandom()); 22 // Create an ssl socket factory with our all-trusting manager 23 final SSLSocketFactory sslSocketFactory = sslContext 24 .getSocketFactory(); 25 Feign.builder().client(new Client.Default(sslSocketFactory, (s, sslSession) -> true)); View Code 三、在SpringCloud中使用Feign 比如说注册中心有如下服务: 1)application.yml的配置: spring: application: name: demo-consumer eureka: client: service-url: defaultZone: http://localhost:8080/eureka,http://localhost:8081/eureka server: port: 8090 View Code 2)创建接口 1 package com.bdqn.lyrk.consumer.demo.api; 2 3 import org.springframework.cloud.netflix.feign.FeignClient; 4 import org.springframework.web.bind.annotation.RequestMapping; 5 6 @FeignClient("demo") 7 public interface DemoConfigService { 8 9 @RequestMapping("/demo.do") 10 String demoService(); 11 } View Code 注意在接口上加上注解:@FeignClient("demo") 注解里的参数是在eureka注册的服务名 3)编写启动类: package com.bdqn.lyrk.consumer.demo; import com.bdqn.lyrk.consumer.demo.api.DemoConfigService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.context.ConfigurableApplicationContext; @EnableDiscoveryClient @EnableFeignClients @SpringBootApplication public class DemoConsumerProvider { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoConsumerProvider.class, args); DemoConfigService demoConfigService = applicationContext.getBean(DemoConfigService.class); System.out.println(demoConfigService.demoService()); } } View Code 注意在启动类上加上@EnableFeignClients来开启Feign功能 运行后输出: 此时我们可以发现使用feign客户端我们访问服务的代码简洁了好多 4) feign多参数设置方法 service-api模块的接口定义: public interface IBillService { @PostMapping("/queryBill") List<BillDTO> queryOrders(@RequestBody BillsVO billsVO); } 相关服务实现类: @RestController public class BillServiceImpl implements IBillService { @Autowired private BillMapper billMapper; @PostMapping("/queryBill") @Override public List<BillDTO> queryOrders(@RequestBody BillsVO billsVO) { return billMapper.query(BeanMap.create(billsVO)); } } 注意 需要在接口定义与实现类的参数上加@RequestBody注解 View Code 四、feign中的使用Hystrix 1) 在@FeignClient中有两个属性我们值得关注一下,它们分别是fallBack和fallBackFactory,当然我们系统里要添加Hystrix支持并且在属性文件里设置: feign.hystrix.enabled=true 同样我们要在启动类里加上@EnableCircuitBreaker注解打开Hystrix保护 2) fallBack属性很简单,用来设置降级方法,当feign请求服务失败时所调用的方法, 这里我给出接口的例子: 首先定义一个接口:IOrderService package com.bdqn.lyrk.service.api; import com.bdqn.lyrk.service.dto.OrderDTO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; public interface IOrderService { @GetMapping("/orderId/{orderId}") OrderDTO getOrderById(@PathVariable("orderId") Integer orderId); @GetMapping("/errorOrder") OrderDTO getErrorOrder(); } View Code 其次定义Feign的接口OrderServiceClient继承IOrderService package com.bdqn.lyrk.order.service.consumer.feign; import com.bdqn.lyrk.service.api.IOrderService; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.annotation.Primary; @Primary @FeignClient(value = "ORDER-SERVER", fallBack="FailedOrderServiceClientImpl.class") public interface OrderServiceClient extends IOrderService { } View Code 由于IOrderService不在同一个项目里,而且SpringCloud不推荐服务端和客户端使用同一个接口,所以我采用继承的方式,注意加上@Primary注解以免使用@Autowired时注入失败 在定义实现类: package com.bdqn.lyrk.order.service.consumer.feign; import com.bdqn.lyrk.service.dto.OrderDTO; import org.springframework.stereotype.Component; @Component public class FailedOrderServiceClientImpl implements OrderServiceClient { @Override public OrderDTO getOrderById(Integer orderId) { OrderDTO orderDTO = new OrderDTO(); orderDTO.setId(orderId); orderDTO.setOrderName("服务中失败的订单,id为:" + orderId); return null; } @Override public OrderDTO getErrorOrder() { OrderDTO orderDTO = new OrderDTO(); orderDTO.setOrderName("服务中失败的订单"); orderDTO.setId(-1); return orderDTO; } } View Code 最后@FeignClient中设置属性fallBack="FailedOrderServiceClientImpl.class" 就可以了 3) 当我们需要封装服务端的异常信息时,可以指定fallbackFactory属性,请看下面的例子: package com.bdqn.lyrk.order.service.consumer.feign; import com.bdqn.lyrk.service.dto.OrderDTO; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; @Component public class OrderServiceFallbackFactoryImpl implements FallbackFactory<OrderServiceClient> { @Override public OrderServiceClient create(Throwable cause) { return new OrderServiceClient() { @Override public OrderDTO getOrderById(Integer orderId) { OrderDTO orderDTO = new OrderDTO(); orderDTO.setOrderName(cause.getMessage()); return orderDTO; } @Override public OrderDTO getErrorOrder() { OrderDTO orderDTO = new OrderDTO(); orderDTO.setOrderName(cause.getMessage()); return orderDTO; } }; } } View Code 注意:FallbackFactory的泛型参数一定要指定为@FeignClient修饰的接口,同时不建议fallback与fallbackFactory同时使用 最后 我贴一下服务端的实现代码: package com.bdqn.lyrk.springcloud.order.service; import com.bdqn.lyrk.service.api.IOrderService; import com.bdqn.lyrk.service.dto.OrderDTO; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.TimeUnit; @RestController public class OrderServiceImpl implements IOrderService { @HystrixCommand(fallbackMethod = "errorDTO", commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")} ) @GetMapping("/orderId/{orderId}") @Override public OrderDTO getOrderById(@PathVariable("orderId") Integer orderId) { OrderDTO orderDTO = new OrderDTO(); orderDTO.setId(orderId); orderDTO.setOrderName("订单ID为" + orderId + "的订单"); try { TimeUnit.SECONDS.sleep(orderId); } catch (InterruptedException e) { throw new RuntimeException(e); } return orderDTO; } @Override public OrderDTO getErrorOrder() { System.out.println(1 / 0); return null; } public OrderDTO errorDTO(Integer orderId) { OrderDTO orderDTO = new OrderDTO(); orderDTO.setId(-1); orderDTO.setOrderName("错误的订单,请重试"); return orderDTO; } } View Code 对于Hystrix可以参考:SpringCloud学习之Hystrix
一。集群方案及部署思路: 如果是单节点的注册中心,是无法保证系统稳定性的,当然现在项目部署架构不可能是单节点的。 集群节点的部署思路:通过运行多个实例并请求他们相互注册,来完成注册中心的高可用性(结伴注册) 注意: 用于本机模拟的前期准备工作:将电脑上hosts 添加如下配置 (linux下位置:/etc/hosts): 127.0.0.1 localhost server1 server2 View Code 二 设计步骤 在这里简单创建一个项目:register-center-node1的项目工程,和我们先前的register-center项目工程一模一样 register-center-node1的application.yml配置: 1 server: 2 port: 8081 3 spring: 4 application: 5 name: register-center 6 eureka: 7 client: 8 service-url: 9 defaultZone: http://server1:8080/eureka 10 fetch-registry: true 11 instance: 12 hostname: server2 View Code register-center的application.yml配置: 1 server: 2 port: 8080 3 spring: 4 application: 5 name: register-center 6 eureka: 7 client: 8 service-url: 9 defaultZone: http://server2:8081/eureka 10 instance: 11 hostname: server1 View Code 注意以下几点: 与先前独立运行register-center不同,大家注意defaultZone属性,两个注册中心地址都指向对方进行结伴注册 去掉fetch-registry 与 register-with-eureka配置(其实这样做就会取对应的默认值,两个值均为true) 启动第一个注册中心时会报Cannot execute request on any known server的错误,暂时不管它,实际上eureka注册中心的ui界面是能打开的 所有注册中心的节点的spring.application.name必须保持一致。 当需要往注册中心集群注册服务时的写法:defaultZone:http://server1:8080/eureka,http://server2:8081/eureka 启动完毕后,访问地址:http://localhost:8080 得到如下界面: 我们可以看到注册中心地址已经标记为集群模式了
最近在给学生们讲Spring+Mybatis整合,根据有的学生反映还是基于注解实现整合便于理解,毕竟在先前的工作中团队里还没有人完全舍弃配置文件进行项目开发,由于这两个原因,我索性参考spring官方文档研究出完全基于注解整合ssm框架。毕竟无配置化也是Spring官方所推行的,要不SpringBoot存在的意义为何嘛 一。整合思路 1)目标:毫无保留的将配置文件的所有配置项改变注解加创建对象的方式实现 2)Spring提供的 @Bean @Configuration @ComponentScan @EnableTransactionManagement @EnableWebMvc 等 需要知道其含义 二。创建spring-mvc的web项目 1) 项目结构目录: 在这里web.xml里不写任何配置 三。在config包下分别创建配置类与属性文件 1. AppConfig.java package com.bdqn.lyrk.ssm.study.app; import com.alibaba.druid.pool.DruidDataSource; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; import java.io.IOException; /** * spring的配置类 * * @author chen.nie * @date 2018/6/24 **/ @Configuration //表明此类是配置类 @ComponentScan // 扫描自定义的组件(repository service component controller) @PropertySource("classpath:application.properties") // 读取application.properties @MapperScan("com.bdqn.lyrk.ssm.study.app.mapper") //扫描Mybatis的Mapper接口 @EnableTransactionManagement //开启事务管理 public class AppConfig { /** * 配置数据源 * * @date 2018/6/24 **/ @Bean public DataSource dataSource(PropertiesConfig propertiesConfig) { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername(propertiesConfig.getUserName()); dataSource.setPassword(propertiesConfig.getPassword()); dataSource.setUrl(propertiesConfig.getUrl()); dataSource.setDriverClassName(propertiesConfig.getDriverClass()); return dataSource; } /** * 配置mybatis的SqlSessionFactoryBean * * @param dataSource * @param propertiesConfig * @return */ @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource, PropertiesConfig propertiesConfig) throws IOException { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); sqlSessionFactoryBean.setTypeAliasesPackage(propertiesConfig.getMybatisTypeAliasPackages()); // 动态获取SqlMapper PathMatchingResourcePatternResolver classPathResource = new PathMatchingResourcePatternResolver(); sqlSessionFactoryBean.setMapperLocations(classPathResource.getResources(propertiesConfig.getMapperLocations())); return sqlSessionFactoryBean; } /** * 配置spring的声明式事务 * * @return */ @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource); return dataSourceTransactionManager; } @Bean public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer(); return propertySourcesPlaceholderConfigurer; } } View Code 没什么好说的,这里主要创建Spring与Mybatis整合的相关对象以及声明式事务切面,我们把配置文件中的东西通通用java代码创建,注意@Bean注解的使用 2.DispatcherConfig 1 package com.bdqn.lyrk.ssm.study.config; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.context.annotation.Bean; 5 import org.springframework.context.annotation.Configuration; 6 import org.springframework.web.servlet.config.annotation.EnableWebMvc; 7 import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 8 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 9 import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 10 import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; 11 import org.springframework.web.servlet.view.InternalResourceViewResolver; 12 import org.springframework.web.servlet.view.JstlView; 13 14 import java.util.Properties; 15 16 @Configuration 17 @EnableWebMvc 18 public class DispatcherConfig extends WebMvcConfigurerAdapter { 19 20 21 @Autowired 22 private PropertyConfig propertyConfig; 23 24 @Bean 25 public InternalResourceViewResolver internalResourceViewResolver() { 26 InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver(); 27 internalResourceViewResolver.setViewClass(JstlView.class); 28 internalResourceViewResolver.setPrefix(propertyConfig.getWebViewPrefix()); 29 internalResourceViewResolver.setSuffix(propertyConfig.getWebViewSuffix()); 30 return internalResourceViewResolver; 31 } 32 33 /** 34 * 设置统一错误处理要跳转的视图 35 * 36 * @return 37 */ 38 @Bean 39 public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { 40 SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver(); 41 Properties properties = new Properties(); 42 properties.getProperty("java.lang.Exception", "error"); 43 simpleMappingExceptionResolver.setExceptionMappings(properties); 44 return simpleMappingExceptionResolver; 45 } 46 47 /** 48 * 添加静态资源 49 * 50 * @param registry 51 */ 52 @Override 53 public void addResourceHandlers(ResourceHandlerRegistry registry) { 54 registry.addResourceHandler(propertyConfig.getWebStaticHandler()).addResourceLocations(propertyConfig.getWebStaticResource()).setCachePeriod(propertyConfig.getWebStaticCachedPeriod()); 55 } 56 57 /** 58 * 添加拦截器 59 * 60 * @param registry 61 */ 62 @Override 63 public void addInterceptors(InterceptorRegistry registry) { 64 super.addInterceptors(registry); 65 } 66 } View Code 此处配置SpringMVC的视图解析器,静态资源等,依旧照搬配置文件中的代码 3.PropertiesConfig package com.bdqn.lyrk.ssm.study.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; @Configuration @PropertySource("classpath:application.properties") public class PropertyConfig { @Value("${spring.datasource.url}") private String url; @Value("${spring.datasource.driver}") private String driver; @Value("${spring.datasource.user}") private String user; @Value("${spring.datasource.password}") private String password; @Value("${spring.web.view.prefix}") private String webViewPrefix; @Value("${spring.web.view.suffix}") private String webViewSuffix; @Value("${spring.web.static.handler}") private String webStaticHandler; @Value("${spring.web.static.resource}") private String webStaticResource; @Value("${spring.web.static.cache.period}") private Integer webStaticCachedPeriod; @Value("${mybatis.type.alias.package}") private String mybatisTypeAliasPackage; public String getWebViewPrefix() { return webViewPrefix; } public String getWebViewSuffix() { return webViewSuffix; } public String getWebStaticHandler() { return webStaticHandler; } public String getWebStaticResource() { return webStaticResource; } public Integer getWebStaticCachedPeriod() { return webStaticCachedPeriod; } public String getMybatisTypeAliasPackage() { return mybatisTypeAliasPackage; } public String getUrl() { return url; } public String getDriver() { return driver; } public String getUser() { return user; } public String getPassword() { return password; } @Bean public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } } View Code 此处用于读取application.properties的文件内容 注意@Value与@PropertySource的含义 4.MyWebAppInitializer 1 package com.bdqn.lyrk.ssm.study.config; 2 3 import org.springframework.web.filter.CharacterEncodingFilter; 4 import org.springframework.web.servlet.DispatcherServlet; 5 import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; 6 7 import javax.servlet.Filter; 8 import javax.servlet.ServletContext; 9 import javax.servlet.ServletException; 10 11 /** 12 * 初始化servlet WebApplicationContext 相关 13 * 14 * @author chen.nie 15 * @date 2017/12/28 16 **/ 17 public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { 18 19 @Override 20 protected Class<?>[] getRootConfigClasses() { 21 return new Class[]{AppConfig.class}; 22 } 23 24 @Override 25 protected Class<?>[] getServletConfigClasses() { 26 return new Class[]{DispatcherServlet.class}; 27 } 28 29 @Override 30 protected String[] getServletMappings() { 31 return new String[]{"/"}; 32 } 33 34 35 /** 36 * 添加过滤器 37 * 38 * @return 39 */ 40 @Override 41 protected Filter[] getServletFilters() { 42 CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); 43 characterEncodingFilter.setEncoding("UTF-8"); 44 characterEncodingFilter.setForceEncoding(true); 45 return new Filter[]{characterEncodingFilter}; 46 } 47 } View Code 在这里请大家关注一下这个类,这段代码的含义和配置SpringMVC的含义一样: 1 <web-app> 2 <context-param> 3 <param-name>contextConfigLocation</param-name> 4 <param-value>/WEB-INF/root-context.xml</param-value> 5 </context-param> 6 <servlet> 7 <servlet-name>dispatcher</servlet-name> 8 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 9 <init-param> 10 <param-name>contextConfigLocation</param-name> 11 <param-value></param-value> 12 </init-param> 13 <load-on-startup>1</load-on-startup> 14 </servlet> 15 <servlet-mapping> 16 <servlet-name>dispatcher</servlet-name> 17 <url-pattern>/</url-pattern> 18 </servlet-mapping> 19 <listener> 20 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 21 </listener> 22 </web-app> View Code 5. application.properties #数据库连接 spring.datasource.user=root spring.datasource.password=root spring.datasource.driver=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/MySchool?characterEncoding=utf-8&useSSL=false #web设置相关 spring.web.view.prefix=/WEB-INF/jsp/ spring.web.view.suffix=.jsp spring.web.static.handler=/assets/** spring.web.static.resource=classpath:/assets/ spring.web.static.cache.period=360000 #mybatis设置相关 mybatis.type.alias.package=com.bdqn.lyrk.ssm.study.entity View Code 6.创建MyBatis对应的mapper package com.bdqn.lyrk.ssm.study.mapper; import com.bdqn.lyrk.ssm.study.entity.StudentEntity; import org.apache.ibatis.annotations.Select; import java.util.List; public interface StudentMapper { @Select("select * from Student") List<StudentEntity> selectAll(); } View Code 7.创建业务逻辑 1 package com.bdqn.lyrk.ssm.study.service.impl; 2 3 import com.bdqn.lyrk.ssm.study.entity.StudentEntity; 4 import com.bdqn.lyrk.ssm.study.mapper.StudentMapper; 5 import com.bdqn.lyrk.ssm.study.service.IStudentService; 6 import org.springframework.beans.factory.annotation.Autowired; 7 import org.springframework.stereotype.Service; 8 import org.springframework.transaction.annotation.Transactional; 9 10 import java.util.List; 11 12 @Service 13 public class StudentServiceImpl implements IStudentService { 14 @Autowired 15 private StudentMapper studentMapper; 16 17 18 @Override 19 public List<StudentEntity> selectAll() { 20 return studentMapper.selectAll(); 21 } 22 23 @Transactional 24 @Override 25 public int save(StudentEntity studentEntity) { 26 return 0; 27 } 28 29 30 } View Code 8.创建Controller package com.bdqn.lyrk.ssm.study.controller; import com.bdqn.lyrk.ssm.study.entity.StudentEntity; import com.bdqn.lyrk.ssm.study.service.IStudentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import java.util.List; @Controller public class IndexController { @Autowired private IStudentService studentService; @GetMapping("/index") public String index(ModelMap modelMap) { List<StudentEntity> list = studentService.selectAll(); modelMap.put("students", list); return "index"; } } View Code 9.index.jsp文件中内容 <%-- Created by IntelliJ IDEA. User: chen.nie Date: 2017/12/23 Time: 下午8:40 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>$Title$</title> </head> <body> <c:forEach items="${students}" var="student"> ${student.stuName} </c:forEach> </body> </html> View Code 10.启动tomcat后访问http://localhost:8080/portal/index得到如下界面 OK!大功告成,注意前4步里面注解的运用,后面的步骤和往常的写法无异,想必大家都很熟悉了吧。
在以往的分布式开发当中,各个服务节点的监控必不可少。监控包含有很多方面,比如说:内存占用情况,节点是否健康等。在spring-boot会给我们提供相关资源监控叫做spring-boot-actuator, 通过执行器可以帮我管理和监控生产环境下的应用服务。 一。添加SpringBoot执行器的依赖(版本2.0.0.RELEASE) 添加gradle配置依赖: dependencies { compile('org.springframework.boot:spring-boot-starter-actuator') } View Code 二。关于SpringBoot的端点 端点是一个提供给我们监控应用程序的功能点,SpringBoot提供了一系列内置端点叫我们使用,举个例子:health端点为我们提供了一个对我们基础程序的一个健康状态的监控 每个端点都可以打开或关闭,绝大多数的应用都可以通过http请求进行访问,springboot包含很多内置内置端点。tips:参考官网的 ID Description Enabled by default auditevents Exposes audit events information for the current application. Yes beans Displays a complete list of all the Spring beans in your application. Yes conditions Shows the conditions that were evaluated on configuration and auto-configuration classes and the reasons why they did or did not match. Yes configprops Displays a collated list of all @ConfigurationProperties. Yes env Exposes properties from Spring’s ConfigurableEnvironment. Yes flyway Shows any Flyway database migrations that have been applied. Yes health Shows application health information. Yes httptrace Displays HTTP trace information (by default, the last 100 HTTP request-response exchanges). Yes info Displays arbitrary application info. Yes loggers Shows and modifies the configuration of loggers in the application. Yes liquibase Shows any Liquibase database migrations that have been applied. Yes metrics Shows ‘metrics’ information for the current application. Yes mappings Displays a collated list of all @RequestMapping paths. Yes scheduledtasks Displays the scheduled tasks in your application. Yes sessions Allows retrieval and deletion of user sessions from a Spring Session-backed session store. Not available when using Spring Session’s support for reactive web applications. Yes shutdown Lets the application be gracefully shutdown. No threaddump Performs a thread dump. Yes 有几点要补充说明一下: 1). 我们访问的端点监控的地址规范是:/actuator/{ID}的方式访问,比如说:health端点默认被映射的路径就是/actuator/health 2) 并不是所有端点都可以通过http请求访问,以下表格列举了各个端点的状态值: ID JMX Web auditevents Yes No beans Yes No conditions Yes No configprops Yes No env Yes No flyway Yes No health Yes Yes heapdump N/A No httptrace Yes No info Yes Yes jolokia N/A No logfile N/A No loggers Yes No liquibase Yes No metrics Yes No mappings Yes No prometheus N/A No scheduledtasks Yes No sessions Yes No shutdown Yes No threaddump Yes No 我们可以发现默认情况下只有health与info端点才能通过http请求访问,当然我们可以在属性文件中配置 management.endpoints.web.exposure.include 来开放可以访问的端点: management.endpoints.web.exposure.include=* management.endpoints.web.exposure.exclude=env,beans 3)当我们访问端点时,响应的数据会缓存一定的时间,我们可以通过这个属性进行配置: management.endpoint.<id>.cache.time-to-live=10s 4) 我们也可以自己定义监视路径,默认情况是:/actuator/xxx,通过如下属性可以设置: management.endpoints.web.base-path=/ 5) 关于保护敏感端点,首先我们要添加对spring-security的依赖,并设置进行安全验证的用户名,密码以及角色,如果不使用spring-security就要慎重考虑暴露端口的端点了 三。关于Health端点 health端点用于检测我们运行程序的健康状态,当程序宕机时,可以提供给开发人员相关的提示信息 默认情况下该端点只是显示简略的监控信息,不过我们可以通过management.endpoint.health.show-details属性来让其显示详细的监控信息 端点有如下几种状态: UP DOWN UNKNOW-SERVICE 从字面上我们很好理解 实现自定义健康监控: package com.bdqn.lyrk.springboot.study.monitor; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @Component public class SelfMonitor implements HealthIndicator { @Override public Health health() { return Health.up().withDetail("health","next").build(); } } View Code 例子很简单,主要是实现HealthIndicator接口,当我们访问:http://localhost:8080/actuator/health时,我们可以看到相关监控信息,如下图:
一。允许全部请求跨域许可的代码: 需要继承WebMvcConfigurerAdapter类 @Configuration public class MyWebAppConfigurer extends WebMvcConfigurerAdapter{ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**"); } } View Code 二。有针对性的配置: 同样需要继承需要继承WebMvcConfigurerAdapter类,设置允许跨域的地址与映射 @Configuration public class MyWebAppConfigurer extends WebMvcConfigurerAdapter{ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://192.168.1.97") .allowedMethods("GET", "POST") .allowCredentials(false).maxAge(3600); } } View Code 三。在Controller上加上@CrossOrigin注解 以上几种方案请使用spring4.2以上版本
一. 关于spring-cloud中的分布式配置 Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持。使用Config Server,您可以在所有环境中管理应用程序的外部属性 ,通常情况下我们可以把需要管理的配置文件放置在svn或者git上进行做统一的配置仓库。 二 创建git远程仓库远程仓库中创建所需的配置文件 本例子中是demo-local.yml , 配置文件内容如下: student: name: test age: 28 View Code 三 创建config-server端 1) 创建gradle模块config-server并添加gradle依赖 dependencies { compile('org.springframework.cloud:spring-cloud-config-server') } View Code 2)编辑application.yml配置 server: port: 8000 spring: cloud: config: server: git: uri: git@gitlab.com:xxxx/config.git enabled: true profiles: active: local View Code 其中spring.cloud.config.server.git是配置远程仓库的地址,如果不使用SSH协议请配置username和password属性 3)编写启动类 package com.bdqn.lyrk.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; @SpringBootApplication @EnableConfigServer public class ConfigServer { public static void main(String[] args) { SpringApplication.run(ConfigServer.class, args); } } View Code 注意在启动类上添加@EnableConfigServer注解 四 创建config-client端 1)创建gradle模块demo-server并添加gradle依赖 dependencies{ compile('org.springframework.cloud:spring-cloud-starter-config') } View Code 添加bootstrap.yml (Bootstrap.yml(bootstrap.properties)在application.yml(application.properties)之前加载) server: port: 8001 spring: cloud: config: uri: http://localhost:8000 profile: local application: name: demo View Code 注意几点: 配置服务从 /{name}/{profile}/{label} 提供属性源,客户端应用程序中的默认绑定 “name”= ${spring.application.name} “profile”= ${spring.profiles.active} (实际上是 Environment.getActiveProfiles() ) “label”=“master” 这里面的spring.application.name与远程仓库的配置文件名demo-local对应 编写DemoConfig类 1 package com.bdqn.lyrk.server.demo.config; 2 3 import org.springframework.boot.context.properties.ConfigurationProperties; 4 import org.springframework.context.annotation.Configuration; 5 6 @Configuration 7 @ConfigurationProperties(prefix = "student") 8 public class DemoConfig { 9 private String name; 10 private int age; 11 12 public String getName() { 13 return name; 14 } 15 16 public void setName(String name) { 17 this.name = name; 18 } 19 20 public int getAge() { 21 return age; 22 } 23 24 public void setAge(int age) { 25 this.age = age; 26 } 27 } View Code 注意 @ConfigurationProperties(prefix = "student")注解,该注解作用是:获取yml配置文件的前缀student,配置文件中余下部分用javabean的属性替换即可 编写启动类: 1 package com.bdqn.lyrk.server.demo; 2 3 import com.bdqn.lyrk.server.demo.config.DemoConfig; 4 import org.springframework.boot.SpringApplication; 5 import org.springframework.boot.autoconfigure.SpringBootApplication; 6 import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 7 import org.springframework.cloud.config.client.ConfigServicePropertySourceLocator; 8 import org.springframework.context.ApplicationContext; 9 10 @SpringBootApplication 11 public class DemoProvider { 12 13 public static void main(String[] args) { 14 ApplicationContext applicationContext = SpringApplication.run(DemoProvider.class, args); 15 DemoConfig demoConfig = applicationContext.getBean(DemoConfig.class); 16 System.out.println(demoConfig.getName()); 17 } 18 } View Code 运行后得到上述结果,此时我们已经从远程配置中获取到所需的信息了
一、soa简单介绍 1)面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。SOA是解决复杂业务模块,提高扩展性,维护性,可伸缩性的最基本方案。我们的业务服务可以根据需求通过网络对松散耦合的粗粒度应用组件进行分布式部署、组合和使用。服务层是SOA的基础,可以直接被应用调用,不同的服务通过不同的团队来维护。 有效的业务分割是soa基础中的基础 2)对于大型网站常见的架构模式: 分层:便于维护,各个模块之间有一定的独立性,属于横向切分,常见的为三层架构 分割:属于纵向切分,当项目业务复杂,功能越来越多时,可以将业务功能进行拆分,常见的是拆分成项目,部署在不同的服务器上,每个模块由独立的团队进行维护 分布式:分割与分层一个主要目的就是将切分后的模块进行分布式部署,分布式意味着使用更多机器完成相同的功能,机器多意味着cpu,内存也多,处理计算的能力与并发能力也就越大 集群:集群为了使用更多的机器来处理相同的服务,用以提高性能。 3)常用的分布式方案: 分布式应用和服务:最常见的方式,即将不同的业务服务部署到不同的机器上 分布式静态资源:动静分离。将css资源或者图片独立进行分布式部署 分布式存储:最常见的是数据库分布式部署,来分割海量的数据 分布式计算:比如说hadoop等大数据处理方案 4)分布式带来的问题: 分布式意味着服务调用必须通过网络,网络带宽的问题是服务之间调用必不可免的问题 多台服务器会存在数据一致性的问题以及分布式事务问题 服务器越多宕机的概率也就越大 维护成本以及开发成本的增加 5)注意分布式一定是根据需求及业务量而定的,切不可为了分布式而分布式 6)注册中心:管理服务地址,并提供调用者访问的组件,常见的有zookeeper eureka redis等 二、soa基本架构图(以spring-cloud为例) 1.注册中心: 保存服务提供方暴露的服务信息,常见的注册中心有zookeeper eureka 2.服务提供方:提供服务者 3.消费方:当需要调用远程服务接口时,必须在注册中心发现服务找到服务提供者,从而进行远程方法调用 三、SpringCloud项目构建 Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线)。使用Spring Cloud开发人员可以快速地支持实现这些模式的服务和应用程序。 1、通过访问http://start.spring.io来构建spring-cloud项目 注意:在Search for dependencies里添加最基本的soa依赖项: web EurekaServer Eureka Discovery 2、通过IDEA工具导入下载好的项目并依次创建一下几个项目模块: 这里说明一下: register-center:注册中心 service-api:接口契约 service-provider:服务提供方 service-consumer:服务消费方 四.实现步骤 1、实现注册中心 application.yml配置: server: port: 8000 eureka: client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://localhost:8000/eureka/ View Code 注意:在默认设置下,Eureka服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为。 register-with-eureka: 代表是否向eureka服务注册 fetch-registry: 是否从eureka注册中心拉取信息 service-url: eureka的服务地址 编写程序入口类,通过@EnableEurekaServer 激活Eureka服务器相关配置: package com.bdqn.springcloud.study.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class RegisterCenterProvider { public static void main(String[] args) { SpringApplication.run(RegisterCenterProvider.class, args); } } View Code 2 实现service-api 定义暴露服务的接口契约 package com.bdqn.springcloud.study.api; import com.bdqn.springcloud.study.dto.UserDTO; /** * UserService测试接口 * * @author chen.nie * @date 2017/11/13 */ public interface UserService { UserDTO findUser(); } View Code 定义DTO package com.bdqn.springcloud.study.dto; public class UserDTO { private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } View Code 3 实现服务提供方: gradle配置:先依赖service-api dependencies{ compile project(":service-api") } View Code application-yml的配置: server: port: 8001 spring: application: name: "service-provider-demo" eureka: instance: prefer-ip-address: true client: service-url: defaultZone: http://localhost:8000/eureka/ View Code 注意:这里面要配置应用名称 注册中心的服务地址 接口实现类: package com.bdqn.springcloud.study.provider.impl; import com.bdqn.springcloud.study.api.UserService; import com.bdqn.springcloud.study.dto.UserDTO; import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl implements UserService { @Override public UserDTO findUser() { UserDTO userDTO = new UserDTO(); userDTO.setName("张三丰"); userDTO.setId(10); return userDTO; } } View Code Controller: package com.bdqn.springcloud.study.provider.controller; import com.bdqn.springcloud.study.api.UserService; import com.bdqn.springcloud.study.dto.UserDTO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController public class UserController { @Resource private UserService userService; @GetMapping("/user") public UserDTO getUser() { return this.userService.findUser(); } } View Code 启动类: package com.bdqn.springcloud.study.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @EnableDiscoveryClient @SpringBootApplication public class ServiceProvider { public static void main(String[] args) { SpringApplication.run(ServiceProvider.class, args); } } View Code @EnableDiscoveryClient:用于启用发现客户端实现的注释打来浏览器访问:http://localhost:8000 我们看到刚才的接口服务已经成功注册至Eureka中心 4.实现服务消费方 gradle配置:先依赖service-api dependencies{ compile project(":service-api") compile 'org.springframework.cloud:spring-cloud-starter-eureka' } View Code application.yml配置: server: port: 8002 spring: application: name: "service-consumer-demo" eureka: instance: prefer-ip-address: true client: service-url: defaultZone: http://localhost:8000/eureka/ View Code 编写Controller: package com.bdqn.springcloud.study.consumer.controller; import com.bdqn.springcloud.study.dto.UserDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class ConsumerController { @Autowired private RestTemplate restTemplate; @Autowired private LoadBalancerClient loadBalancerClient; @GetMapping("/getUser") public String getUser() { ResponseEntity<UserDTO> responseEntity = restTemplate.getForEntity("http://SERVICE-PROVIDER-DEMO/user", UserDTO.class); UserDTO userDTO = responseEntity.getBody(); return userDTO.getName(); } } View Code 注意我们通过http://service-provider-demo/user/ 指定service-provider的application name,让系统从注册中心去发现服务。 5.编写启动项: package com.bdqn.springcloud.study.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableDiscoveryClient public class ServiceConsumer { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ServiceConsumer.class, args); } } View Code 在启动项里,我们配置了一个RestTemplate 主意 @LoadBalanced注解修饰的restTemplate,具备负载均衡能力的restTemplate,即每次都会用负载均衡算法,从可用服务列表中,挑一个进行调用。 启动完毕时,我们发现注册中心中多了一个服务: 当我们访问http://localhost:8002/getUser得到如下显示就说明我们的服务运行成功了: 当停止register-center时,我们发现该访问地址依然能够拿到结果,说明消费端本地是有缓存的
在前面的博文中,已经演示过springboot与Mybatis集成的实例,本篇再来探讨一下SpringBoot的基础。 一。关于SpringBoot SpringBoot可以基于Spring轻松创建可以“运行”的、独立的、生产级的应用程序。大多数Spring Boot应用程序需要很少的Spring配置。您可以使用Spring Boot创建可以使用java -jar或传统 war 包部署启动的Java应用程序。 SpringBoot主要目标是: 为所有的Spring开发者提供一个更快,更广泛接受的入门体验。 开始使用开箱即用的配置(极少配置甚至不用配置),所有配置都有默认值,同时也可以根据自己的需求进行配置。 提供大量项目中常见的一系列非功能特征(例如嵌入式服务器,安全性,指标,运行状况检查,外部化配置)。 绝对没有代码生成,也不需要XML配置。 总之就一句话,启动更方便,配置量更少 系统要求:虽然您可以在Java 6或7上使用 Spring Boot,但是建议用Java 8。 二。添加对spring-boot的支持 maven: <dependencyManagement> <dependencies> <!-- Override Spring Data release train provided by Spring Boot --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-releasetrain</artifactId> <version>Fowler-SR2</version> <scope>import</scope> <type>pom</type> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>1.5.8.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> View Code gradle: buildscript { ext { springBootVersion = '1.5.8.RELEASE' } repositories { maven { url = "http://maven.aliyun.com/nexus/content/groups/public" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' group = 'com.bdqn.lyrk.study' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { maven { url = "http://maven.aliyun.com/nexus/content/groups/public" } } dependencies { compile('org.springframework.boot:spring-boot-starter-web') testCompile('org.springframework.boot:spring-boot-starter-test') } View Code 注意在spring-boot里有一个叫做启动器的概念,命名是以spring-boot-starter-xxx打头 这个命名结构旨在帮助你快速找到一个启动器。上述配置中会有一个嵌入式servlet容器,这样在程序启动时可以自动运行servlet容器 三。关于属性文件 SpringBoot一个最大的好处就是对集成的企业级应用工程都提供了默认配置,绝大多数情况下我们用默认的配置就好了,如果我们想改变配置就需要SpringBoot的属性文件了。 SpringApplication将从以下位置的application.properties或者application.yml文件中加载属性,并将它们添加到Spring Environment中: 当前目录的/config子目录 当前目录 classpath中/config包 classpath root路径 该列表按优先级从高到低排序。 四。SpringBoot启动实例 package com.bdqn.lyrk.study.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author chen.nie */ @SpringBootApplication public class SpringBootRunner { public static void main(String[] args) throws Exception { SpringApplication.run(SpringBootRunner.class, args); } } View Code 经过对比我们发现,现在启动Spring更加简单了,在这里我们注意@SpringBootApplication注解,它相当于使用@Configuration,@EnableAutoConfiguration和@ComponentScan和他们的默认属性,熟悉Spring框架的朋友对第一个和第三个注解应该不陌生,@EnableAutoConfiguration这个注解通常放置在主类上,它隐式定义了某些项目的基本“搜索包”。 注意:springboot的启动类必须和其他加spring注解的包在同一个目录下 五。SpringBoot日志记录 Spring Boot使用Commons Logging进行所有内部日志记录,但基础日志实现开放。 默认配置提供了Java Util Logging,Log4J2和Logback。默认情况下,如果使用’Starters’,将会使用Logback。在配置文件里,是以logging.xxx打头,注意:SpringBoot默认只将日志输出到控制台,我们可以做以下的配置来输出到文件中。 配置日志级别: logging: level: debug path: ${user.home}/logs/${spring.application.name} View Code 当然我么可以以logging.level.<logger-name>=<level>的方式配置对应的日志级别,root的日志级别可以设置为:logging.level.root=值
1.关于solr索引数据同步 通常情况下,单节点部署的solr应用很难在并发量很大的情况下"久存",那么多节点部署提高Solr应用的负载量和响应时间势在必行。 solr索引同步有以下特点: ·影响复制的配置由单个文件solrconfig.xml控制 ·支持配置文件和索引文件的复制 ·跨平台工作,配置相同 ·与Solr紧密结合;管理页面提供了对复制各个方面更细粒度控制 ·基于java的复制特性作为请求处理程序实现。因此,配置复制类似于任何正常的请求处理程序。 当主节点索引更新时,所变更的数据会拷贝到所有子节点上 2.配置ReplicationHandler 在运行主从复制之前,应该设置处理程序初始化的参数: ·replicateAfter :SOLR会自行在以下操作行为发生后执行复制,有效的值为commit, optimize, startup ·backAfter:solr在以下操作后会发生备份,有效的值为commit, optimize, startup ·maxnumberofbackup:一个整数值,决定了该节点在接收备份命令时将保留的最大备份数量。 ·maxNumberOfBackups :指定要保留多少备份。这可以用来删除除最近的N个备份。 ·commitReserveDuration:如果提交非常频繁并且您的网络速度很慢,可以调整这个参数来保留增量索引的周期时间,默认是10秒 在${core.home}/conf/solrConfig.xml中进行主节点配置实例: <requestHandler name="/replication" class="solr.ReplicationHandler"> <lst name="master"> <str name="replicateAfter">commit</str> <str name="backupAfter">optimize</str> <str name="confFiles">schema.xml,stopwords.txt,elevate.xml</str> <str name="commitReserveDuration">00:00:10</str> </lst> <int name="maxNumberOfBackups">2</int> <lst name="invariants"> <str name="maxWriteMBPerSec">16</str> </lst> </requestHandler> 从节点配置: ·masterUrl:主节点的地址,从节点通过replication参数来发送同步指令 ·pollInterval:设置抓取间隔时间,用HH:mm:ss的格式设置 ·compression 可选值:external,internal(局域网推荐使用此值) <requestHandler name="/replication" class="solr.ReplicationHandler"> <lst name="slave"> <!-- fully qualified url for the replication handler of master. It is possible to pass on this as a request param for the fetchindex command --> <str name="masterUrl">http://remote_host:port/solr/core_name/replication</str> <!-- Interval in which the slave should poll master. Format is HH:mm:ss . If this is absent slave does not poll automatically. But a fetchindex can be triggered from the admin or the http API --> <str name="pollInterval">00:00:20</str> <!-- THE FOLLOWING PARAMETERS ARE USUALLY NOT REQUIRED--> <!-- To use compression while transferring the index files. The possible values are internal|external. If the value is 'external' make sure that your master Solr has the settings to honor the accept-encoding header. See here for details: http://wiki.apache.org/solr/SolrHttpCompression If it is 'internal' everything will be taken care of automatically. USE THIS ONLY IF YOUR BANDWIDTH IS LOW. THIS CAN ACTUALLY SLOWDOWN REPLICATION IN A LAN --> <str name="compression">internal</str> <!-- The following values are used when the slave connects to the master to download the index files. Default values implicitly set as 5000ms and 10000ms respectively. The user DOES NOT need to specify these unless the bandwidth is extremely low or if there is an extremely high latency --> <str name="httpConnTimeout">5000</str> <str name="httpReadTimeout">10000</str> <!-- If HTTP Basic authentication is enabled on the master, then the slave can be configured with the following --> <str name="httpBasicAuthUser">username</str> <str name="httpBasicAuthPassword">password</str> </lst> </requestHandler> 注意:从节点masterUrl属性应当配置为主节点的地址 3.关于solr主从复制之repeater模式 在上述配置中有一个弊端: ·一主多从模式,主节点存在宕机的风险,那么从节点会群龙无首(solr暂时未提供主节点选举策略) ·从节点很多的情况下,会严重拉低主节点的性能(占有主节点服务器网络资源,占有磁盘I/O,提升CPU占有率等) solr这里提供了一套机制,就是repeater(中转器模式),简单来说将一定量的solr服务器配置成即是主节点又是从节点的模式: 由图我们看到 从节点访问repeater(中转器)即可,因此从而减轻了主节点的压力,也一定程度上了解决单点故障。 配置实例如下: <requestHandler name="/replication" class="solr.ReplicationHandler"> <lst name="master"> <str name="replicateAfter">commit</str> <str name="confFiles">schema.xml,stopwords.txt,synonyms.txt</str> </lst> <lst name="slave"> <str name="masterUrl">http://master.solr.company.com:8983/solr/core_name/replication</str> <str name="pollInterval">00:00:60</str> </lst> </requestHandler> 从节点的masterUrl属性改成reapter的地址,另外replicateAfter必须设置为commit
一. solrj简介: solrj可以使Java应用程序很方便的访问与操作solr。solrj有几个核心类,分别为:1.SolrClient 2.SolrRequests 3.SolrQuerys 4.SolrReponses tips:该jar包可以在${solr.home}/dist/solrj-lib 找到 gralde配置: buildscript { ext { springBootVersion = '1.5.8.RELEASE' } repositories { maven { url = "http://maven.aliyun.com/nexus/content/groups/public" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' group = 'com.bdqn.lyrk.study' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { maven { url = "http://maven.aliyun.com/nexus/content/groups/public" } } dependencies { compile('org.springframework.boot:spring-boot-starter-data-solr') compile('org.springframework.boot:spring-boot-starter-web') compileOnly('org.projectlombok:lombok') testCompile('org.springframework.boot:spring-boot-starter-test') } View Code maven配置: <dependency> <groupId>org.apache.solr</groupId> <artifactId>solr-solrj</artifactId> <version>x.y.z</version> </dependency> View Code 二 核心类介绍: 1. SolrClient (操作solr的核心类): 由于SolrClient是抽象的,因此要连接solr的远程服务需要创建HttpSolrClient 或者 CloudSolrClient 对象,两者都是以Http请求与solr通信,只不过前者需要一个明确的solr地址信息,后者是基于zookeeper的地址并对solrCloud进行操作,在这里是基于前者的教程 创建代码: String urlString = "http://localhost:8983/solr/techproducts"; SolrClient solr = new HttpSolrClient.Builder(urlString).build(); solrClient常用方法: public UpdateResponse add(String collection, SolrInputDocument doc) throws SolrServerException, IOException 向solr中添加一条数据 参数: collection:在对应的core中添加数据 doc:该参数封装了具体要添加的值 public UpdateResponse addBean(String collection,Object obj) throws IOException, SolrServerException 将对应的bean添加至文档索引当中 public UpdateResponse commit(String collection) throws SolrServerException, IOException 提交本次操作的数据 public UpdateResponse rollback(String collection) throws SolrServerException, IOException 回滚本次操作的数据 public UpdateResponse deleteById(String collection, String id) throws SolrServerException, IOException 根据唯一索引删除索引信息 public UpdateResponse deleteByQuery(String collection, String query) throws SolrServerException, IOException 根据查询删除索引里的文档信息 public SolrDocument getById(String id, SolrParams params) throws SolrServerException, IOException 根据ID进行查询 public QueryResponse query(String collection, SolrParams params, METHOD method) throws SolrServerException, IOException 查询方法 参数: collection:查询的core solrParams:查询参数对象 method:Http请求方式 2.SolrDocumentBase 该类是抽象类,并实现Map接口,子类包含SolrDocument和SolrInputDocument。该类主要作用是代表了solr索引中的一条数据信息,对象中的field值必须在magage-schema中定义。通常需要操作solr索引字段时用到SolrInputDoucument 该类常用方法: void addField(String name, Object value) 向solr中索引中添加一个Field Object getFieldValue(String name) 根据field获取对应的第一个值或者一组值 Collection<String> getFieldNames() 获取所有的Fields信息 3.SolrParams 该类是抽象类,主要用于封装与solr请求时所传的参数信息。该类最常用的子类为SolrQuery, SolrQuery类提供了查询所需的分组,高亮,排序等方法 3.1 SolrInputDocument: void addField(String name, Object value) 在索引添加一条Field对应的值 void setField(String name, Object value) 在索引中更新一条Field对应的值 3.2 SolrQuery: ModifiableSolrParams set( String name, String ... val ) 设置对应的查询参数,具体见下方实例 SolrQuery addHighlightField(String f) 添加高亮显示字段 SolrQuery addFilterQuery(String ... fq) 添加过滤字段 SolrQuery addSort(String field, ORDER order) 添加排序字段 4.SolrResponse 该类是抽象类,主要用于solr对用户请求之后做出的响应。 三 简单用例: 实体类: package com.bdqn.lyrk.study.springboot.entity; import lombok.Data; import org.apache.solr.client.solrj.beans.Field; /** * @author chen.nie */ @Data public class StudentEntity { @Field("id" ) //对应solr中索引的字段 private String id; @Field("userName") private String userName; @Field("classId") private String classId; } View Code dao对应的实现类: package com.bdqn.lyrk.study.springboot.dao.solr; import com.bdqn.lyrk.study.springboot.dao.StudentDao; import com.bdqn.lyrk.study.springboot.entity.StudentEntity; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.client.solrj.response.UpdateResponse; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.params.CommonParams; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import org.springframework.util.StringUtils; import java.util.HashMap; import java.util.List; import java.util.Map; /** * dao针对于solr的实现 * @author chen.nie */ @Repository(value = "solrStudentDao") public class SolrStudentDaoImpl implements StudentDao { public static final String COLLECTION = "machines"; @Autowired private SolrClient solrClient; @Override public int save(StudentEntity studentEntity) throws Exception { SolrInputDocument solrInputDocument = new SolrInputDocument(); solrInputDocument.addField("id", studentEntity.getId()); solrInputDocument.addField("userName", studentEntity.getUserName()); solrClient.add(COLLECTION, solrInputDocument); UpdateResponse updateResponse = solrClient.commit(COLLECTION); return updateResponse.getStatus(); } @Override public List<StudentEntity> query(StudentEntity studentEntity) throws Exception { SolrQuery solrQuery = new SolrQuery(); solrQuery.set(CommonParams.Q, "userName:" + studentEntity.getUserName()); QueryResponse queryResponse = solrClient.query(COLLECTION, solrQuery); List<StudentEntity> list = queryResponse.getBeans(StudentEntity.class); return list; } @Override public int update(StudentEntity studentEntity) throws Exception { /* * 注意该类对solr对应字段的原子更新,必须设置对象对应的唯一值 */ SolrInputDocument solrInputDocument = new SolrInputDocument(); solrInputDocument.addField("id", studentEntity.getId()); if (StringUtils.hasLength(studentEntity.getUserName())) { Map<String, Object> map = new HashMap<>(1); map.put("set", studentEntity.getUserName()); solrInputDocument.addField("userName", map); } if (StringUtils.hasLength(studentEntity.getClassId())) { Map<String, Object> map = new HashMap<>(1); map.put("set", studentEntity.getClassId()); solrInputDocument.addField("classId", map); } solrClient.add(COLLECTION, solrInputDocument); UpdateResponse updateResponse = solrClient.commit(COLLECTION); return updateResponse.getStatus(); } } View Code 注意:操作的field必须为存储(store="true"),否则更新的索引会覆盖前面的索引
第一种采用转发的方式: package cn.jbit.download.servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class DownloadServlet extends HttpServlet { private static final long serialVersionUID = 6765085208899952414L; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String filedownload = "/upload/1/10213.jpg";//即将下载的文件的相对路径 String filedisplay = "10213.jpg";//下载文件时显示的文件保存名称 response.setContentType("application/x-download");//设置为下载application/x-download //response.setContentType("application/x-msdownload");//设置为下载application/x-msdownload //response.setContentType("application/octet-stream");//设置为下载application/octet-stream response.addHeader("Content-Disposition", "attachment;filename=" + filedisplay); try { RequestDispatcher rd = request.getRequestDispatcher(filedownload); if(rd != null) { rd.forward(request,response); } response.flushBuffer(); } catch (Exception e) { e.printStackTrace(); } } } 二:通过输出流的方式: package cn.jbit.download.servlet; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class DownloadOfIOServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String basePath = request.getSession().getServletContext().getRealPath("/upload"); String filedisplay = "helloworld.jpg"; String filedownload = basePath + File.separator + "helloworld.jpg"; response.setContentType("applicaiton/x-download"); response.addHeader("Content-Disposition", "attachment;filename="+filedisplay); InputStream is = null; OutputStream os = null; BufferedInputStream bis = null; BufferedOutputStream bos = null; is = new FileInputStream(new File(filedownload)); bis = new BufferedInputStream(is); os = response.getOutputStream(); bos = new BufferedOutputStream(os); byte[] b = new byte[1024]; int len = 0; while((len = bis.read(b)) != -1){ bos.write(b,0,len); } bis.close(); is.close(); bos.close(); os.close(); } } 第三种:通过超链接的方式(注意不推荐,因为会暴露下载文件的位置)
关于solr6.6搭建与配置可以参考 solr6.6初探之配置篇 在这里我们探讨一下分词的配置 一.关于分词 1.分词是指将一个中文词语拆成若干个词,提供搜索引擎进行查找,比如说:北京大学 是一个词那么进行拆分可以得到:北京与大学,甚至北京大学整个词也是一个语义 2.市面上常见的分词工具有 IKAnalyzer MMSeg4j Paoding等,这几个分词器各有优劣,大家可以自行研究 在这篇文章,我先演示IKAnalyzer分词器 下载:IKAnalyzer 二 拷贝相关Jar包与配置 1.下载得到后有如下文件: ext.dic是分词文件,这个是我们常操作的文件,可以在这个里面配置我们自己定义的词汇 IKAnalyzer.cfg.xml是配置查找词典的位置 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>IK Analyzer 扩展配置</comment> <!--用户可以在这里配置自己的扩展字典 --> <entry key="ext_dict">ext.dic;</entry> <!--用户可以在这里配置自己的扩展停止词字典--> <entry key="ext_stopwords">stopword.dic;</entry> </properties> 2. 将配置文件和词典拷贝至${solr.home}/server/solr-webapp/webapp/WEB-INF/classes 下,没有classes文件请手动创建 3.将jar文件拷贝至${solr.home}/server/solr-webapp/webapp/WEB-INF/libs下 4.在 ${solr.home}\${core.home}\conf\managed-schema 文件前增加如下配置 <!-- IK分词器 --> <fieldType name="text_ik" class="solr.TextField"> <analyzer type="index"> <tokenizer class="org.apache.lucene.analysis.ik.IKTokenizerFactory" useSmart="true"/> </analyzer> <analyzer type="query"> <tokenizer class="org.apache.lucene.analysis.ik.IKTokenizerFactory" useSmart="true"/> </analyzer> </fieldType> 三:验证分词 1.启动solr6.6 2.请在如下界面选择测试分词效果: 注意filedType一定选择我们配置的分词类型text_ik 附上ext.dic 可以看到我在对诛仙2进行分词时得到 诛仙和诛仙2的分解结果,注意一行一个分词
这个错误顾名思义:就是在给客户端做出提交响应完毕后,就不能再次使用request的转发。 代码原先是这样的: 1 package com.bdqn.jsp.study.web.filter; 2 3 import javax.servlet.*; 4 import javax.servlet.annotation.WebFilter; 5 import javax.servlet.http.HttpServletRequest; 6 import javax.servlet.http.HttpServletResponse; 7 import java.io.IOException; 8 9 @WebFilter(filterName = "checkLoginFilter", urlPatterns = "/*") 10 public class CheckLoginFilter implements Filter { 11 @Override 12 public void init(FilterConfig filterConfig) throws ServletException { 13 14 } 15 16 @Override 17 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 18 HttpServletRequest request = (HttpServletRequest) servletRequest; 19 HttpServletResponse response = (HttpServletResponse) servletResponse; 20 if (request.getRequestURI().contains("/user")) { 21 Object value = request.getSession().getAttribute("user"); 22 if (value == null) { 23 response.sendRedirect(request.getContextPath() + "/login.jsp"); 24 //return; 25 } 26 } 27 filterChain.doFilter(request, response); 28 } 29 30 @Override 31 public void destroy() { 32 33 } 34 } 注意这里是个filter主要作用:用于判断用户是否登录后才能访问相关页面,地址栏里有“/user”的地址信息,就认为改地址是登录后才能进行访问 然而在某一个servlet里有如下相关代码: if (returnValue != null) { request.getRequestDispatcher("/" + returnValue.toString()).forward(request, response); } 如果这么写就出问题了,因为在filter里response做出重定向后,还要执行doFilter方法,因此当执行servlet中的转发操作时就会报错了,因为在response.sendRedirect()会认为已经给response的commited属性设置成true了,因此不能再进行转发
一.关于观察者模式 1.将观察者与被观察者分离开来,当被观察者发生变化时,将通知所有观察者,观察者会根据这些变化做出对应的处理。 2.jdk里已经提供对应的Observer接口(观察者接口)与Observable(被观察者类)用于实现观察者模式 3.关于Observer接口,该接口只有一个update方法,当被观察者发生相关变化时,会通知所有的观察者,观察者接受到通知时,调用update方法进行处理。贴出源代码: 1 /* 2 * Copyright (c) 1994, 1998, Oracle and/or its affiliates. All rights reserved. 3 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20 * 21 * 22 * 23 * 24 */ 25 package java.util; 26 27 /** 28 * A class can implement the <code>Observer</code> interface when it 29 * wants to be informed of changes in observable objects. 30 * 31 * @author Chris Warth 32 * @see java.util.Observable 33 * @since JDK1.0 34 */ 35 public interface Observer { 36 /** 37 * This method is called whenever the observed object is changed. An 38 * application calls an <tt>Observable</tt> object's 39 * <code>notifyObservers</code> method to have all the object's 40 * observers notified of the change. 41 * 42 * @param o the observable object. 43 * @param arg an argument passed to the <code>notifyObservers</code> 44 * method. 45 */ 46 void update(Observable o, Object arg); 47 } View Code 4:关于被观察者Observable的常用方法: 1. public synchronized void addObserver(Observer o);//添加观察者对象 2. public void notifyObservers();//通知所有观察者 3. protected synchronized void setChanged();//设置观察项已经做出改变,此方法很重要 贴出源代码,注意内部实现: 1 /* 2 * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved. 3 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20 * 21 * 22 * 23 * 24 */ 25 26 package java.util; 27 28 /** 29 * This class represents an observable object, or "data" 30 * in the model-view paradigm. It can be subclassed to represent an 31 * object that the application wants to have observed. 32 * <p> 33 * An observable object can have one or more observers. An observer 34 * may be any object that implements interface <tt>Observer</tt>. After an 35 * observable instance changes, an application calling the 36 * <code>Observable</code>'s <code>notifyObservers</code> method 37 * causes all of its observers to be notified of the change by a call 38 * to their <code>update</code> method. 39 * <p> 40 * The order in which notifications will be delivered is unspecified. 41 * The default implementation provided in the Observable class will 42 * notify Observers in the order in which they registered interest, but 43 * subclasses may change this order, use no guaranteed order, deliver 44 * notifications on separate threads, or may guarantee that their 45 * subclass follows this order, as they choose. 46 * <p> 47 * Note that this notification mechanism has nothing to do with threads 48 * and is completely separate from the <tt>wait</tt> and <tt>notify</tt> 49 * mechanism of class <tt>Object</tt>. 50 * <p> 51 * When an observable object is newly created, its set of observers is 52 * empty. Two observers are considered the same if and only if the 53 * <tt>equals</tt> method returns true for them. 54 * 55 * @author Chris Warth 56 * @see java.util.Observable#notifyObservers() 57 * @see java.util.Observable#notifyObservers(java.lang.Object) 58 * @see java.util.Observer 59 * @see java.util.Observer#update(java.util.Observable, java.lang.Object) 60 * @since JDK1.0 61 */ 62 public class Observable { 63 private boolean changed = false; 64 private Vector<Observer> obs; 65 66 /** Construct an Observable with zero Observers. */ 67 68 public Observable() { 69 obs = new Vector<>(); 70 } 71 72 /** 73 * Adds an observer to the set of observers for this object, provided 74 * that it is not the same as some observer already in the set. 75 * The order in which notifications will be delivered to multiple 76 * observers is not specified. See the class comment. 77 * 78 * @param o an observer to be added. 79 * @throws NullPointerException if the parameter o is null. 80 */ 81 public synchronized void addObserver(Observer o) { 82 if (o == null) 83 throw new NullPointerException(); 84 if (!obs.contains(o)) { 85 obs.addElement(o); 86 } 87 } 88 89 /** 90 * Deletes an observer from the set of observers of this object. 91 * Passing <CODE>null</CODE> to this method will have no effect. 92 * @param o the observer to be deleted. 93 */ 94 public synchronized void deleteObserver(Observer o) { 95 obs.removeElement(o); 96 } 97 98 /** 99 * If this object has changed, as indicated by the 100 * <code>hasChanged</code> method, then notify all of its observers 101 * and then call the <code>clearChanged</code> method to 102 * indicate that this object has no longer changed. 103 * <p> 104 * Each observer has its <code>update</code> method called with two 105 * arguments: this observable object and <code>null</code>. In other 106 * words, this method is equivalent to: 107 * <blockquote><tt> 108 * notifyObservers(null)</tt></blockquote> 109 * 110 * @see java.util.Observable#clearChanged() 111 * @see java.util.Observable#hasChanged() 112 * @see java.util.Observer#update(java.util.Observable, java.lang.Object) 113 */ 114 public void notifyObservers() { 115 notifyObservers(null); 116 } 117 118 /** 119 * If this object has changed, as indicated by the 120 * <code>hasChanged</code> method, then notify all of its observers 121 * and then call the <code>clearChanged</code> method to indicate 122 * that this object has no longer changed. 123 * <p> 124 * Each observer has its <code>update</code> method called with two 125 * arguments: this observable object and the <code>arg</code> argument. 126 * 127 * @param arg any object. 128 * @see java.util.Observable#clearChanged() 129 * @see java.util.Observable#hasChanged() 130 * @see java.util.Observer#update(java.util.Observable, java.lang.Object) 131 */ 132 public void notifyObservers(Object arg) { 133 /* 134 * a temporary array buffer, used as a snapshot of the state of 135 * current Observers. 136 */ 137 Object[] arrLocal; 138 139 synchronized (this) { 140 /* We don't want the Observer doing callbacks into 141 * arbitrary code while holding its own Monitor. 142 * The code where we extract each Observable from 143 * the Vector and store the state of the Observer 144 * needs synchronization, but notifying observers 145 * does not (should not). The worst result of any 146 * potential race-condition here is that: 147 * 1) a newly-added Observer will miss a 148 * notification in progress 149 * 2) a recently unregistered Observer will be 150 * wrongly notified when it doesn't care 151 */ 152 if (!changed) 153 return; 154 arrLocal = obs.toArray(); 155 clearChanged(); 156 } 157 158 for (int i = arrLocal.length-1; i>=0; i--) 159 ((Observer)arrLocal[i]).update(this, arg); 160 } 161 162 /** 163 * Clears the observer list so that this object no longer has any observers. 164 */ 165 public synchronized void deleteObservers() { 166 obs.removeAllElements(); 167 } 168 169 /** 170 * Marks this <tt>Observable</tt> object as having been changed; the 171 * <tt>hasChanged</tt> method will now return <tt>true</tt>. 172 */ 173 protected synchronized void setChanged() { 174 changed = true; 175 } 176 177 /** 178 * Indicates that this object has no longer changed, or that it has 179 * already notified all of its observers of its most recent change, 180 * so that the <tt>hasChanged</tt> method will now return <tt>false</tt>. 181 * This method is called automatically by the 182 * <code>notifyObservers</code> methods. 183 * 184 * @see java.util.Observable#notifyObservers() 185 * @see java.util.Observable#notifyObservers(java.lang.Object) 186 */ 187 protected synchronized void clearChanged() { 188 changed = false; 189 } 190 191 /** 192 * Tests if this object has changed. 193 * 194 * @return <code>true</code> if and only if the <code>setChanged</code> 195 * method has been called more recently than the 196 * <code>clearChanged</code> method on this object; 197 * <code>false</code> otherwise. 198 * @see java.util.Observable#clearChanged() 199 * @see java.util.Observable#setChanged() 200 */ 201 public synchronized boolean hasChanged() { 202 return changed; 203 } 204 205 /** 206 * Returns the number of observers of this <tt>Observable</tt> object. 207 * 208 * @return the number of observers of this object. 209 */ 210 public synchronized int countObservers() { 211 return obs.size(); 212 } 213 } View Code 5.举一个例子吧:当婴儿哭泣时,则通知家人来哄宝宝,那么这里很明显婴儿是一个被观察者,当婴儿哭泣时,立刻通知家人(观察者) package com.bdqn.s2.javaoop.study.proxy; import java.lang.reflect.Proxy; import java.util.Observable; import java.util.Observer; /** * 婴儿类,被观察者 */ public class Baby extends Observable { private int hungry; private String name; public String getName() { return name; } public Baby(String name, int hungry) { this.hungry = hungry; this.name = name; addObserver(new Parents());//添加观察者对象,需要家长监管 } /** * 婴儿开始哭泣 */ public void cry() { if (hungry < 100) { System.out.printf("baby%s饿了,开始哭泣...%n", name); setChanged();//饥饿值过低,触发变化,此方法必须被调用 notifyObservers();//通知观察者 } } } /** * 家长,观察者 */ class Parents implements Observer { @Override public void update(Observable o, Object arg) { if (o instanceof Baby) { Baby baby = (Baby) o; System.out.println(baby.getName()+"开始哭泣,赶紧哄宝宝啦"); } } } public class Main { public static void main(String[] args) { Baby baby = new Baby("豆豆",9); baby.cry(); } } /* 输出结果 baby豆豆饿了,开始哭泣... 豆豆开始哭泣,赶紧哄宝宝啦 */ 二 关于动态代理模式 1)代理模式是设计模式中非常常见的一种模式,这种模式可以实现对原有方法的扩展,举个例子经纪人可以替明星们办理一些事情,那么此时经纪人可以视为明星的代理。 2)代理模式可以分为静态代理和动态代理,在这里我们只对JDK提供的动态代理进行讨论。 3)由于JDK提供的代理模式所代理的类继承了Proxy,因此我们只能接口进行代理,针对类的代理可以自行参考cglib框架 4)InvocationHandler:是代理实例的调用处理程序 实现的接口。 每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。 //proxy:代理类,method:代理执行的方法 args:方法参数 public Object invoke(Object proxy, Method method, Object[] args); 5)Proxy:该类主要是获取或者新创建动态代理对象 //该方法主要用于获取代理对象,注意一定是针对接口进行代理 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 6)针对上述例子进行改造:添加保姆类并改造Baby类的构造方法: package com.bdqn.s2.javaoop.study.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Observer; /** * 保姆类 */ public class Nanny implements InvocationHandler { private Observer parents; public Nanny(){ parents = new Parents(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("保姆开始照顾孩子"); Object object = method.invoke(parents, args); return object; } } Baby类构造函数改造: public Baby(String name, int hungry) { this.hungry = hungry; this.name = name; addObserver((Observer) Proxy.newProxyInstance(Baby.class.getClassLoader(),new Class[]{Observer.class},new Nanny())); } 输出结果: baby豆豆饿了,开始哭泣... 保姆开始照顾孩子 豆豆开始哭泣,赶紧哄宝宝啦
一.solr的简介 1) solr是企业级应用的全文检索项目,它是基于Apache Lucence搜索引擎开发出来的用于搜索的应用工程 2) solr最新版本6.6 下载地址:下载地址 二 启动与配置solr 1) 下载并解压文件后会得到以下界面: 我们重点关注以下几个文件夹: 1.bin 放置solr的相关执行脚本,在solr5.0版本以前,部署过程相当麻烦,好在Apache帮我们简化了相关solr的配置 2.example :这个文件夹里放置的一些solr应用实例。对于我们当然可以在实际的应用中将示例借来使用 3.server:其实就是tomcat目录,在这里部署了solr的项目 下一步进入bin文件夹里运行 ./solr start $ ./solr start Waiting up to 180 seconds to see Solr running on port 8983 [\] Started Solr server on port 8983 (pid=2725). Happy searching! 在浏览器输入 localhost:8983/solr 就可以展示如下界面 当运行成功后,紧接着要运行命令: $ ./solr create -c helloworld 创建一个名字为 helloworld的core (core相当于一个搜索项目工程) 三.配置dataimport从数据库获取数据 1.在${solr.home}/example/example-DIH/solr/db/conf下拷贝solrconfig.xml与db-data-config.xml文件至${solr.home}/server/solr/${core.name}/conf下,注意:${solr.home}指solr的安装目录 ${core.name}指创建的core的名字,本例中是helloworld 2.编辑db-data-config.xml <dataConfig> <dataSource driver="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/house?charactorEncoding=utf-8" user="root" password="" /> <document> <entity name="item" query="select * from topics" pk="topic_id" deltaQuery="select * where updatedate > '${dataimporter.last_index_time}'"> <field column="topic_id" name="topicId" /> <field column="topic" name="topic" /> <field column="updatedate" name="ldate" dateTimeFormat="yyyy-MM-dd HH:mm:ss"/> </entity> </document> </dataConfig> 该配置文件配置数据源,以及查询数据表中数据 注意以下几点: .保证查询的字段必须包含数据表的主键 .配置文件中的field节点对应数据表中的列名及索引名(name) .datasource节点中配置数据源 entity节点配置一个实体 deltaQuery属性是配置增量获取数据的SQL .注意一定要将数据库的驱动拷贝到${solr.home}/server/solr-webapp/webapp/WEB-INF/lib 下 3.编辑${solr.home}/server/solr/${core.name}/conf/managed-schema 注意修改unqiuekey节点值为topicId (对应<field name="topicId" column="topic_id" />中的name属性) 并增加以下配置: <field name="topicId" type="long" multiValued="false" indexed="true" required="true" stored="true"/> <field name="ldate" type="date" indexed="true" stored="true"/> <field name="topic" type="text_general" docValues="false" indexed="true" stored="true"/> 注意: 必须修改unqiueKey的节点内容为对应的主键信息,这一步很重要 field节点对应db-data-import.xml中的field节点 其中他们的name属性保持一致 四 将数据库的数据导入到solr中 在工作台上运行该功能即可,需要标注的地方都标红: 五:验证是否成功:
一.spring boot集成Mybatis gradle配置: //gradle配置: compile("org.springframework.boot:spring-boot-starter-web:1.4.2.RELEASE") compile ('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.0'){ exclude group:'org.springframework' } compile("mysql:mysql-connector-java:5.1.39") application.yml配置 spring: profiles: active: local mybatis: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl cache-enabled: true mapper-locations: classpath:mapper/*.xml application-local.yml配置 spring: datasource: username: root password: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/house?characterEncoding=utf8 server: port: 8082 注意此处配置文件最好在classpath的根目录下 三.spring-boot启动代码: package com.lyhs.machineparts.run; import com.lyhs.machineparts.entity.User; import com.lyhs.machineparts.mapper.UserMapper; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ComponentScan; import java.sql.SQLException; import java.util.List; /** * 1.注意@MapperScan注解必须在spring-boot处标注,作用是扫描Mapper接口并创建其代理对象,作用类似于MapperScannerConfigure * 2.spring-boot对象池自动将根据配置文件自动创建SqlSessionFactoryBean与SqlSessionTemplate对象 * 3.spring-boot的好处省略绝大多数代码及配置文件 * Created by niechen on 17/7/16. */ @SpringBootApplication @ComponentScan("com.lyhs") @MapperScan(basePackages = "com.lyhs.machineparts.mapper") public class PortalProvider { public static void main(String[] args) throws SQLException { ApplicationContext applicationContext = SpringApplication.run(PortalProvider.class, args); UserMapper userMapper = applicationContext.getBean(UserMapper.class); // System.out.println(houseMapper.toString()); List<User> users = userMapper.selectAll(); for (User user : users) { System.out.println(user.getName()); } } }
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。 3.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0 4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num=10 or num=20 可以这样查询: select id from t where num=10 union all select id from t where num=20 5.下面的查询也将导致全表扫描: select id from t where name like '%abc%' 若要提高效率,可以考虑全文检索。 6.in 和 not in 也要慎用,否则会导致全表扫描,如: select id from t where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了: select id from t where num between 1 and 3 7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描: select id from t where num=@num 可以改为强制查询使用索引: select id from t with(index(索引名)) where num=@num 8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如: select id from t where num/2=100 应改为: select id from t where num=100*2 9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如: select id from t where substring(name,1,3)='abc'--name以abc开头的id select id from t where datediff(day,createdate,'2005-11-30')=0--'2005-11-30'生成的id 应改为: select id from t where name like 'abc%' select id from t where createdate>='2005-11-30' and createdate<'2005-12-1' 10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。 11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。 12.不要写一些没有意义的查询,如需要生成一个空表结构: select col1,col2 into #t from t where 1=0 这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样: create table #t(...) 13.很多时候用 exists 代替 in 是一个好的选择: select num from a where num in(select num from b) 用下面的语句替换: select num from a where exists(select 1 from b where num=a.num) 14.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。 15.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。 16.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。 17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。 18.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。 19.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。 20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。 21.避免频繁创建和删除临时表,以减少系统表资源的消耗。 22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。 23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。 24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。 25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。 26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。 27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。 28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。 29.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。 30.尽量避免大事务操作,提高系统并发能力。