国产一区二区三区免费视频,日韩电影中文 亚洲精品乱码,亚洲一区三区http://www.aygfsteel.com/paulwong/category/55117.htmlzh-cnTue, 18 Oct 2022 10:16:40 GMTTue, 18 Oct 2022 10:16:40 GMT60MONGODB SPRING DISTINCThttp://www.aygfsteel.com/paulwong/archive/2022/10/18/450835.htmlpaulwongpaulwongTue, 18 Oct 2022 02:22:00 GMThttp://www.aygfsteel.com/paulwong/archive/2022/10/18/450835.htmlhttp://www.aygfsteel.com/paulwong/comments/450835.htmlhttp://www.aygfsteel.com/paulwong/archive/2022/10/18/450835.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/450835.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/450835.html
    private boolean needReorderCheck(String requestId) {
        boolean result = false;
//        try(MongoCursor<String> mongoCursor = 
//                mongoTemplate.getCollection(mongoTemplate.getCollectionName(AccountNumProductLineIndex.class))
//                             .distinct(KEY, Filters.eq(REQUEST_ID, requestId), String.class)
//                             .iterator()
//                )
        try(MongoCursor<Document> mongoCursor = 
                mongoTemplate.getCollection(mongoTemplate.getCollectionName(AccountNumProductLineIndex.class))
                             .aggregate(
                                 Arrays.asList(
                                    Aggregates.project(
                                                    Projections.fields(
                                                                    Projections.excludeId(),
                                                                   Projections.include(KEY),
                                                                   Projections.include(REQUEST_ID)
                                                                )
                                               ),
                                    Aggregates.match(Filters.eq(REQUEST_ID, requestId)),
                                    Aggregates.group("$" + KEY)
                                 )
                              )
                             .allowDiskUse(true)
                             .iterator();
        )
        {
            String key = null;
            boolean breakMe = false;
            LOGGER.info("needReorderCheck.key --> start");
            while(mongoCursor.hasNext()) {
                if(breakMe) {
                    mongoCursor.close();
                    break;
                }
                Document keyDocument = mongoCursor.next();
                key = keyDocument.getString("_id");
//                key = mongoCursor.next().getString(KEY);
//                LOGGER.info("needReorderCheck.keyDocument --> {}, key --> {}", keyDocument, key);
                try(MongoCursor<Document> indexMongoCursor = 
                        mongoTemplate.getCollection(AccountNumProductLineIndex.COLLECTION_NAME)
                                        .find(Filters.and(Filters.eq(REQUEST_ID, requestId), Filters.eq(KEY, key)))
                                        .iterator()
                )
                {
                    int preIndex = -1, currentIndex = -1;
                    Document preIndexDocument = null, currentIndexDocument;
                    while(indexMongoCursor.hasNext()) {
                        currentIndexDocument = indexMongoCursor.next();
//                        System.out.println(currentIndexDocument.toJson());
                        if(preIndexDocument != null) {
                             currentIndex = currentIndexDocument.getInteger(INDEX);
                             preIndex = preIndexDocument.getInteger(INDEX);
                             if(currentIndex - preIndex > 1) {
                                indexMongoCursor.close();
                                breakMe = true;
                                result = true;
                                break;
                            }
                        }
                        preIndexDocument = currentIndexDocument;
                    }
                }
            }
        }
        
        return result;
    }



paulwong 2022-10-18 10:22 發表評論
]]>
SPRING JSON TIMEZONE問題大匯總http://www.aygfsteel.com/paulwong/archive/2022/09/22/450823.htmlpaulwongpaulwongThu, 22 Sep 2022 05:18:00 GMThttp://www.aygfsteel.com/paulwong/archive/2022/09/22/450823.htmlhttp://www.aygfsteel.com/paulwong/comments/450823.htmlhttp://www.aygfsteel.com/paulwong/archive/2022/09/22/450823.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/450823.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/450823.html@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone="America/Phoenix")
private Date date;


paulwong 2022-09-22 13:18 發表評論
]]>
Downloading Large Files using Spring WebClienthttp://www.aygfsteel.com/paulwong/archive/2022/09/22/450822.htmlpaulwongpaulwongThu, 22 Sep 2022 05:14:00 GMThttp://www.aygfsteel.com/paulwong/archive/2022/09/22/450822.htmlhttp://www.aygfsteel.com/paulwong/comments/450822.htmlhttp://www.aygfsteel.com/paulwong/archive/2022/09/22/450822.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/450822.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/450822.htmlhttps://www.amitph.com/spring-webclient-large-file-download/

https://github.com/amitrp/spring-examples/blob/main/spring-webflux-webclient/src/main/java/com/amitph/spring/webclients/service/FileDownloaderWebClientService.java

import lombok.RequiredArgsConstructor;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;

@Service
@RequiredArgsConstructor
public class FileDownloaderWebClientService {
    private final WebClient webClient;

    /**
     * Reads the complete file in-memory. Thus, only useful for very large file
     
*/
    public void downloadUsingByteArray(Path destination) throws IOException {
        Mono<byte[]> monoContents = webClient
                .get()
                .uri("/largefiles/1")
                .retrieve()
                .bodyToMono(byte[].class);

        Files.write(destination, Objects.requireNonNull(monoContents.share().block()),
                StandardOpenOption.CREATE);
    }

    /**
     * Reading file using Mono will try to fit the entire file into the DataBuffer.
     * Results in exception when the file is larger than the DataBuffer capacity.
     
*/
    public void downloadUsingMono(Path destination) {
        Mono<DataBuffer> dataBuffer = webClient
                .get()
                .uri("/largefiles/1")
                .retrieve()
                .bodyToMono(DataBuffer.class);

        DataBufferUtils.write(dataBuffer, destination,
                StandardOpenOption.CREATE)
                .share().block();
    }

    /**
     * Having using Flux we can download files of any size safely.
     * Optionally, we can configure DataBuffer capacity for better memory utilization.
     
*/
    public void downloadUsingFlux(Path destination) {
        Flux<DataBuffer> dataBuffer = webClient
                .get()
                .uri("/largefiles/1")
                .retrieve()
                .bodyToFlux(DataBuffer.class);

        DataBufferUtils.write(dataBuffer, destination,
                StandardOpenOption.CREATE)
                .share().block();
    }
}


paulwong 2022-09-22 13:14 發表評論
]]>
SPRING INTEGRATION - ENRICHhttp://www.aygfsteel.com/paulwong/archive/2021/09/21/435976.htmlpaulwongpaulwongTue, 21 Sep 2021 05:40:00 GMThttp://www.aygfsteel.com/paulwong/archive/2021/09/21/435976.htmlhttp://www.aygfsteel.com/paulwong/comments/435976.htmlhttp://www.aygfsteel.com/paulwong/archive/2021/09/21/435976.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435976.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435976.html
package org.springframework.integration.stackoverflow.enricher;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Transformers;
import org.springframework.integration.http.dsl.Http;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class SpringIntegrationEnricherApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringIntegrationEnricherApplication.class, args);
    }

    @Bean
    public IntegrationFlow jsonEnricherFlow(RestTemplate restTemplate) {
        return IntegrationFlows.from(Function.class)
                .transform(Transformers.fromJson(Map.class))
                .enrich((enricher) -> enricher
                        .<Map<String, ?>>requestPayload((message) ->
                                ((List<?>) message.getPayload().get("attributeIds"))
                                        .stream()
                                        .map(Object::toString)
                                        .collect(Collectors.joining(",")))
                        .requestSubFlow((subFlow) ->
                                subFlow.handle(
                                        Http.outboundGateway("/attributes?id={ids}", restTemplate)
                                                .httpMethod(HttpMethod.GET)
                                                .expectedResponseType(Map.class)
                                                .uriVariable("ids", "payload")))
                        .propertyExpression("attributes", "payload.attributes"))
                .<Map<String, ?>, Map<String, ?>>transform(
                        (payload) -> {
                            payload.remove("attributeIds");
                            return payload;
                        })
                .transform(Transformers.toJson())
                .get();
    }

}

https://stackoverflow.com/questions/58205432/spring-integration-enrich-transform-message-using-rest-call

https://www.tabnine.com/web/assistant/code/rs/5c781b6ae70f87000197ab9f#L312



paulwong 2021-09-21 13:40 發表評論
]]>
Spring Boot 2.x基礎教程:使用LDAP來管理用戶與組織數據http://www.aygfsteel.com/paulwong/archive/2021/06/21/435902.htmlpaulwongpaulwongMon, 21 Jun 2021 01:21:00 GMThttp://www.aygfsteel.com/paulwong/archive/2021/06/21/435902.htmlhttp://www.aygfsteel.com/paulwong/comments/435902.htmlhttp://www.aygfsteel.com/paulwong/archive/2021/06/21/435902.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435902.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435902.htmlhttp://blog.didispace.com/spring-boot-learning-24-6-2/


paulwong 2021-06-21 09:21 發表評論
]]>
SRPING自帶的事件監聽機制http://www.aygfsteel.com/paulwong/archive/2021/04/09/435851.htmlpaulwongpaulwongFri, 09 Apr 2021 06:55:00 GMThttp://www.aygfsteel.com/paulwong/archive/2021/04/09/435851.htmlhttp://www.aygfsteel.com/paulwong/comments/435851.htmlhttp://www.aygfsteel.com/paulwong/archive/2021/04/09/435851.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435851.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435851.html
import lombok.Getter;
import org.springframework.context.ApplicationEvent;


@Getter
public class JavaStackEvent extends ApplicationEvent {

    /**
     * Create a new {
@code ApplicationEvent}.
     *
     * 
@param source the object on which the event initially occurred or with
     *               which the event is associated (never {
@code null})
     
*/
    public JavaStackEvent(Object source) {
        super(source);
    }


}

定義一個此事件觀察者,即感興趣者:
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;

/**
 * 觀察者:讀者粉絲
 
*/
@RequiredArgsConstructor
public class ReaderListener implements ApplicationListener<JavaStackEvent> {

    @NonNull
    private String name;

    private String article;

    @Async
    @Override
    public void onApplicationEvent(JavaStackEvent event) {
        // 更新文章
        updateArticle(event);
    }

    private void updateArticle(JavaStackEvent event) {
        this.article = (String) event.getSource();
        System.out.printf("我是讀者:%s,文章已更新為:%s\n", this.name, this.article);
    }

}

注冊感興趣者(將自身注入SPRING容器則完成注冊),并制定發布機制(通過CONTEXT發布事件):
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class ObserverConfiguration {

    @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext context) {
        return (args) -> {
            log.info("發布事件:什么是觀察者模式?");
            context.publishEvent(new JavaStackEvent("什么是觀察者模式?"));
        };
    }

    @Bean
    public ReaderListener readerListener1(){
        return new ReaderListener("小明");
    }

    @Bean
    public ReaderListener readerListener2(){
        return new ReaderListener("小張");
    }

    @Bean
    public ReaderListener readerListener3(){
        return new ReaderListener("小愛");
    }

}


paulwong 2021-04-09 14:55 發表評論
]]>
JSR-303 Bean Validation - Date String Validation http://www.aygfsteel.com/paulwong/archive/2021/02/25/435810.htmlpaulwongpaulwongThu, 25 Feb 2021 01:44:00 GMThttp://www.aygfsteel.com/paulwong/archive/2021/02/25/435810.htmlhttp://www.aygfsteel.com/paulwong/comments/435810.htmlhttp://www.aygfsteel.com/paulwong/archive/2021/02/25/435810.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435810.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435810.html其實可以新加一個方法返回Date類型,再配合@Future@Past 進行驗證。

@Future(message = "Invalid CN_ID_INFO.EXPIRE_DATE.")
private LocalDate getValidExpireDate() {
    try {
        return LocalDate.parse(this.dateString, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    } catch (Exception e) {
        return null;
    }
}

此方法對dateString進行解釋,返回LocalDate,如果dateString為空或格式錯誤,則返回空,再配合@Future 進行是否未來日期的驗證。



paulwong 2021-02-25 09:44 發表評論
]]>
JSR-303 Bean Validation - Conditional Validationhttp://www.aygfsteel.com/paulwong/archive/2021/02/25/435809.htmlpaulwongpaulwongThu, 25 Feb 2021 01:24:00 GMThttp://www.aygfsteel.com/paulwong/archive/2021/02/25/435809.htmlhttp://www.aygfsteel.com/paulwong/comments/435809.htmlhttp://www.aygfsteel.com/paulwong/archive/2021/02/25/435809.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435809.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435809.html這種方法避免了自定義校驗器而增加類。
https://www.chkui.com/article/java/java_bean_validation

@AssertTrue(message = "Missing BANK_CARD_IMG_INFO.IMG")
private Boolean getValidImg() {
    if(YNEnum.Y.code.equals(super.getNeedChecked())) {
            return StringUtils.hasText(this.img);
        }
        return null;//igore checking.
}

這個是當needChecked為Y的時候才執行檢查img變量是否為空。
有幾點注意:
  1. 方法名稱要以get開頭
  2. 返回類型用Boolean,而不用boolean
  3. 返回值有三種:true,false,null如果是null則當成通過,與true的結果一樣


paulwong 2021-02-25 09:24 發表評論
]]>
SPRING BOOT單元測試之@ActiveProfileshttp://www.aygfsteel.com/paulwong/archive/2021/02/04/435792.htmlpaulwongpaulwongThu, 04 Feb 2021 07:31:00 GMThttp://www.aygfsteel.com/paulwong/archive/2021/02/04/435792.htmlhttp://www.aygfsteel.com/paulwong/comments/435792.htmlhttp://www.aygfsteel.com/paulwong/archive/2021/02/04/435792.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435792.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435792.html在單元測試的代碼中用這個標簽指定用哪個profile,如
@ActiveProfiles({"embedded-mongodb","test"})

但這樣做法,由于@ActiveProfiles這個標簽是final的,如果要測試別的profile,只能復制另一份單元測試代碼,再改此標簽。

比較靈活的做法是用default profile,default profile是如果沒指定任何profile,則會默認用這個。在application-default.yaml中再指定需激活的profile。
spring:
   profiles:
      active: test,embedded-mongodb

如果要測試別的profile,可以指定環境變量的方式覆蓋:
-Dspring.profiles.active=error,embedded-mongodb

為了安全起見,將application-default.yaml放在測試目錄中:src\test\resources。

Setting default Spring profile for tests with override option
https://blog.inspeerity.com/spring/setting-default-spring-profile-for-tests-with-override-option/






paulwong 2021-02-04 15:31 發表評論
]]>
JSR-303 Bean Validationhttp://www.aygfsteel.com/paulwong/archive/2021/01/28/435786.htmlpaulwongpaulwongThu, 28 Jan 2021 02:35:00 GMThttp://www.aygfsteel.com/paulwong/archive/2021/01/28/435786.htmlhttp://www.aygfsteel.com/paulwong/comments/435786.htmlhttp://www.aygfsteel.com/paulwong/archive/2021/01/28/435786.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435786.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435786.html
JSR-303 Bean Validation則提供了這樣的便捷。

只要在JAVA BEAN中需要驗證的字段加@NotNull這種標簽,然后在SERVISE中的輸入參數中加@Valid標簽,則就激活驗證流程。
也可以編程的方式自己驗證:
@MessageEndpoint
//@Validated
public class MqMessageCcdValidator {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(MqMessageCcdValidator.class);
    
    @Autowired
    private Validator validator;
    
    @ServiceActivator
    public MqMessage<CcdRequest> validate(/* @Valid */ Message<MqMessage<CcdRequest>> requestMessage) {
        Set<ConstraintViolation<MqMessage<CcdRequest>>> set = validator.validate(requestMessage.getPayload());
        if(CollectionUtils.isNotEmpty(set)) {
            CompositeException compositeException = new CompositeException();
            set.forEach(
                constraintViolation -> {
                                            LOGGER.info("{}", constraintViolation);
                                            ReqInfoValidationException exception =
                                                    new ReqInfoValidationException(constraintViolation.getMessage());
                                            compositeException.addException(exception);
                                       }
            );
            throw new MessageHandlingException(requestMessage, compositeException);
        }
        
        return requestMessage.getPayload();
    }

}

自定義驗證規則
可用標簽來做,以下為驗證手機號的規則:
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.Pattern;

@Retention(RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Constraint(validatedBy = {})
@ReportAsSingleViolation
@Pattern(regexp = "^1[3-9]\\d{9}$")
public @interface ChinaPhone {
    String message() default "Invalid Chinese mobile No.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

如果比較復雜的驗證規則,則參見:
https://reflectoring.io/bean-validation-with-spring-boot/#implementing-a-custom-validator

How to use Java Bean Validation in Spring Boot
https://nullbeans.com/how-to-use-java-bean-validation-in-spring-boot/

Complete Guide to Validation With Spring Boot
https://reflectoring.io/bean-validation-with-spring-boot/

Spring JMS Validate Messages using JSR-303 Bean Validation
https://memorynotfound.com/spring-jms-validate-messages-jsr-303-bean-validation/

Spring REST Validation Example
https://mkyong.com/spring-boot/spring-rest-validation-example/

Spring Boot 整合 Bean Validation 校驗數據

https://blog.csdn.net/wangzhihao1994/article/details/108403732


paulwong 2021-01-28 10:35 發表評論
]]>
SPRING INTEGRATION子FLOWhttp://www.aygfsteel.com/paulwong/archive/2020/10/15/435692.htmlpaulwongpaulwongThu, 15 Oct 2020 03:29:00 GMThttp://www.aygfsteel.com/paulwong/archive/2020/10/15/435692.htmlhttp://www.aygfsteel.com/paulwong/comments/435692.htmlhttp://www.aygfsteel.com/paulwong/archive/2020/10/15/435692.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435692.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435692.html split之后,可以將message分給不同的子flow處理,配置如下:
@Bean
public IntegrationFlow parallelSplitRouteAggregateFlow() {
    return IntegrationFlows
            .from(Http.inboundGateway("/trigger"))
            .handle((p, h) -> Arrays.asList(1, 2, 3))
            .split()
            .channel(MessageChannels.executor(Executors.newCachedThreadPool()))
            .<Integer, Boolean>route(o -> o % 2 == 0, m -> m
                    .subFlowMapping(true, sf -> sf.gateway(oddFlow()))
                    .subFlowMapping(false, sf -> sf.gateway(evenFlow())))
            .aggregate()
            .get();
}

@Bean
public IntegrationFlow oddFlow() {
    return flow -> flow.<Integer>handle((payload, headers) -> "odd");
}

@Bean
public IntegrationFlow evenFlow() {
    return flow -> flow.<Integer>handle((payload, headers) -> "even");
}


https://stackoverflow.com/questions/50121384/spring-integration-parallel-split-route-aggregate-flow-fails-due-to-one-way-mess

paulwong 2020-10-15 11:29 發表評論
]]>
Spring Retry框架——看這篇就夠了http://www.aygfsteel.com/paulwong/archive/2020/09/15/435662.htmlpaulwongpaulwongTue, 15 Sep 2020 05:39:00 GMThttp://www.aygfsteel.com/paulwong/archive/2020/09/15/435662.htmlhttp://www.aygfsteel.com/paulwong/comments/435662.htmlhttp://www.aygfsteel.com/paulwong/archive/2020/09/15/435662.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435662.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435662.htmlhttps://my.oschina.net/marvelcode/blog/4563352



paulwong 2020-09-15 13:39 發表評論
]]>
Springboot+proguard+maven 混淆http://www.aygfsteel.com/paulwong/archive/2020/08/13/435639.htmlpaulwongpaulwongThu, 13 Aug 2020 02:53:00 GMThttp://www.aygfsteel.com/paulwong/archive/2020/08/13/435639.htmlhttp://www.aygfsteel.com/paulwong/comments/435639.htmlhttp://www.aygfsteel.com/paulwong/archive/2020/08/13/435639.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435639.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435639.htmlhttps://blog.csdn.net/qq_35981283/article/details/78529929



paulwong 2020-08-13 10:53 發表評論
]]>
如何優雅地停止SPRING BATCH中的REMOTE CHUNKING JOBhttp://www.aygfsteel.com/paulwong/archive/2020/06/23/435547.htmlpaulwongpaulwongTue, 23 Jun 2020 03:00:00 GMThttp://www.aygfsteel.com/paulwong/archive/2020/06/23/435547.htmlhttp://www.aygfsteel.com/paulwong/comments/435547.htmlhttp://www.aygfsteel.com/paulwong/archive/2020/06/23/435547.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435547.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435547.html 1、什么時候發出停止指令
2、如何等待遠程STEP的完成

一般停止JOB,可用JobOperator.stop(long executionId)來停止,但這個無法確定什么時候發出停止指令,如果是在CHUNK的處理中途發出,則會出現回滾的現象。
BATCH_STEP_EXECUTION thead tr {background-color: ActiveCaption; color: CaptionText;} th, td {vertical-align: top; font-family: "Tahoma", Arial, Helvetica, sans-serif; font-size: 8pt; padding: 4px; } table, td {border: 1px solid silver;} table {border-collapse: collapse;} thead .col0 {width: 173px;} .col0 {text-align: right;} thead .col1 {width: 82px;} .col1 {text-align: right;} thead .col2 {width: 282px;} thead .col3 {width: 164px;} .col3 {text-align: right;} thead .col4 {width: 161px;} thead .col5 {width: 161px;} thead .col6 {width: 109px;} thead .col7 {width: 127px;} .col7 {text-align: right;} thead .col8 {width: 109px;} .col8 {text-align: right;} thead .col9 {width: 118px;} .col9 {text-align: right;} thead .col10 {width: 117px;} .col10 {text-align: right;} thead .col11 {width: 142px;} .col11 {text-align: right;} thead .col12 {width: 150px;} .col12 {text-align: right;} thead .col13 {width: 166px;} .col13 {text-align: right;} thead .col14 {width: 137px;} .col14 {text-align: right;} thead .col15 {width: 109px;} thead .col16 {width: 156px;} thead .col17 {width: 161px;}
STEP_EXECUTION_ID VERSION STEP_NAME JOB_EXECUTION_ID START_TIME END_TIME STATUS COMMIT_COUNT READ_COUNT FILTER_COUNT WRITE_COUNT READ_SKIP_COUNT WRITE_SKIP_COUNT PROCESS_SKIP_COUNT ROLLBACK_COUNT EXIT_CODE EXIT_MESSAGE LAST_UPDATED
2304 169 step2HandleXXX 434 2020-06-22 16:27:54 2020-06-22 16:32:46 STOPPED 167 5010 0 4831 0 155 0 161 STOPPED org.springframework.batch.core.JobInterruptedException 2020-06-22 16:32:46


另外SPRING BATCH也不會等遠程STEP執行完成,就將JOB的狀態設為Complete。

發出停止的指令應通過ChunkListener達成:

public class ItemMasterChunkListener extends ChunkListenerSupport{
    
    private static final Logger log = LoggerFactory.getLogger(ItemMasterChunkListener.class);
    
    
    @Override
    public void beforeChunk(ChunkContext context) {
        log.info("ItemMasterProcessor.beforeChunk");
    }


    @Override
    public void afterChunk(ChunkContext context) {
        log.info("ItemMasterProcessor.afterChunk");
        if(XXXX.isStoppingOrPausing()) {
            log.info("context.getStepContext().getStepExecution().setTerminateOnly()");
            context.getStepContext().getStepExecution().setTerminateOnly();
        }
    }


    @Override
    public void afterChunkError(ChunkContext context) {
        log.info("ItemMasterProcessor.afterChunkError");
    }


}


配置BEAN:

@Bean
@StepScope
public ItemMasterChunkListener novaXItemMasterChunkListener() {
     return new ItemMasterChunkListener();
}
    
this.masterStepBuilderFactory
                    .<X, X>get("step2Handle")
                    .listener(itemMasterChunkListener())
                    .build();


由于是在CHUNK完成的時候發出停止指令,就不會出現ROLLBACK的情況。

等待遠程STEP完成,通過讀取MQ上的MESSAGE是否被消費完成,PENDDING的MESSAGE為0的條件即可。

public class JobExecutionListenerSupport implements JobExecutionListener {

    /* (non-Javadoc)
     * @see org.springframework.batch.core.domain.JobListener#afterJob()
     
*/
    @Override
    public void afterJob(JobExecution jobExecution) {
        Integer totalPendingMessages = 0;
        String queueName = "";
        
        
        String messageSelector = "JOB_EXECUTION_ID=" + jobExecution.getJobInstance().getInstanceId();
        do{
            totalPendingMessages = 
                    this.jmsTemplate.browseSelected(queueName, messageSelector, 
                                (session, browser) -> 
                                    Collections.list(browser.getEnumeration()).size()
                            );
            
            String brokerURL = null;
            if(jmsTemplate.getConnectionFactory() instanceof JmsPoolConnectionFactory) {
                JmsPoolConnectionFactory connectionFactory =
                        (JmsPoolConnectionFactory)jmsTemplate.getConnectionFactory();
                ActiveMQConnectionFactory activeMQConnectionFactory =
                        (ActiveMQConnectionFactory)connectionFactory.getConnectionFactory();
                brokerURL = activeMQConnectionFactory.getBrokerURL();
            } else if(jmsTemplate.getConnectionFactory() instanceof CachingConnectionFactory) {
                CachingConnectionFactory connectionFactory =
                        (CachingConnectionFactory)jmsTemplate.getConnectionFactory();
                ActiveMQConnectionFactory activeMQConnectionFactory =
                        (ActiveMQConnectionFactory)connectionFactory.getTargetConnectionFactory();
                brokerURL = activeMQConnectionFactory.getBrokerURL();
            }
            
            LOGGER.info("queueName = {}, {}, totalPendingMessages = {}, url={}", 
                    queueName, messageSelector, totalPendingMessages, brokerURL);
            Assert.notNull(totalPendingMessages, "totalPendingMessages must not be null.");
            try {
                Thread.sleep(5_000);
            } catch (InterruptedException e) {
                LOGGER.error(e.getMessage(), e);
            }
        } while(totalPendingMessages.intValue() > 0);
        
    }

    /* (non-Javadoc)
     * @see org.springframework.batch.core.domain.JobListener#beforeJob(org.springframework.batch.core.domain.JobExecution)
     
*/
    @Override
    public void beforeJob(JobExecution jobExecution) {
    }

}


這樣整個JOB就能無異常地停止,且會等待遠程STEP完成。

Reference:
https://docs.spring.io/spring-batch/docs/4.1.3.RELEASE/reference/html/common-patterns.html#stoppingAJobManuallyForBusinessReasons

https://stackoverflow.com/questions/13603949/count-number-of-messages-in-a-jms-queue

https://stackoverflow.com/questions/55499965/spring-batch-stop-job-execution-from-external-class

https://stackoverflow.com/questions/34621885/spring-batch-pollable-channel-with-replies-contains-chunkresponses-even-if-job




paulwong 2020-06-23 11:00 發表評論
]]>
Spring Boot Data Mongodb Starter自動配置那些坑http://www.aygfsteel.com/paulwong/archive/2020/03/17/435271.htmlpaulwongpaulwongTue, 17 Mar 2020 01:39:00 GMThttp://www.aygfsteel.com/paulwong/archive/2020/03/17/435271.htmlhttp://www.aygfsteel.com/paulwong/comments/435271.htmlhttp://www.aygfsteel.com/paulwong/archive/2020/03/17/435271.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435271.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435271.html

正好做Mongodb主從復制嘗試使用Spring Boot Data Mongodb Starter插件鏈接訪問Mongodb數據庫集群。

遇到的坑:

  • spring.data.mongodb.host和spring.data.mongodb.port形式不適合集群配置,會報host無法識別異常
  • spring.data.mongodb.uri中經常拋出authentication failed異常


解決辦法:

  1.  對于第一個坑,請使用spring.data.mongodb.uri。如果使用了uri,則其余的host/username/password/db/auth-db這些全部無效。
  2.  對于第二個坑,請在spring.data.mongodb.uri中指定replicaSet和authsource,另外記得把所有集群節點服務器地址都列全。
    如果auth-db和db是同一個,則無需加authsource,如果不同,則加authsource=admin


我沒有把authsource指定,所以一直報authentication failed異常。然后只好一點點去發掘問題點,最后查到在com.mongodb.ConnectionString類中的createCredentials中

private MongoCredential createCredentials(final Map<String, List<String>> optionsMap, final String userName,
                                              final char[] password) {
        AuthenticationMechanism mechanism = null;
        String authSource = (database == null) ? "admin" : database;
        String gssapiServiceName = null;
        String authMechanismProperties = null;

        for (final String key : AUTH_KEYS) {
            String value = getLastValue(optionsMap, key);

            if (value == null) {
                continue;
            }

            if (key.equals("authmechanism")) {
                mechanism = AuthenticationMechanism.fromMechanismName(value);
            } else if (key.equals("authsource")) {
                authSource = value;
            } else if (key.equals("gssapiservicename")) {
                gssapiServiceName = value;
            } else if (key.equals("authmechanismproperties")) {
                authMechanismProperties = value;
            }
        }


        MongoCredential credential = null;
        if (mechanism != null) {
            switch (mechanism) {
                case GSSAPI:
                    credential = MongoCredential.createGSSAPICredential(userName);
                    if (gssapiServiceName != null) {
                        credential = credential.withMechanismProperty("SERVICE_NAME", gssapiServiceName);
                    }
                    break;
                case PLAIN:
                    credential = MongoCredential.createPlainCredential(userName, authSource, password);
                    break;
                case MONGODB_CR:
                    credential = MongoCredential.createMongoCRCredential(userName, authSource, password);
                    break;
                case MONGODB_X509:
                    credential = MongoCredential.createMongoX509Credential(userName);
                    break;
                case SCRAM_SHA_1:
                    credential = MongoCredential.createScramSha1Credential(userName, authSource, password);
                    break;
                default:
                    throw new UnsupportedOperationException(format("The connection string contains an invalid authentication mechanism'. "
                                                                           + "'%s' is not a supported authentication mechanism",
                            mechanism));
            }
        } else if (userName != null) {
            credential = MongoCredential.createCredential(userName, authSource, password);
        }

        if (credential != null && authMechanismProperties != null) {
            for (String part : authMechanismProperties.split(",")) {
                String[] mechanismPropertyKeyValue = part.split(":");
                if (mechanismPropertyKeyValue.length != 2) {
                    throw new IllegalArgumentException(format("The connection string contains invalid authentication properties. "
                            + "'%s' is not a key value pair", part));
                }
                String key = mechanismPropertyKeyValue[0].trim().toLowerCase();
                String value = mechanismPropertyKeyValue[1].trim();
                if (key.equals("canonicalize_host_name")) {
                    credential = credential.withMechanismProperty(key, Boolean.valueOf(value));
                } else {
                    credential = credential.withMechanismProperty(key, value);
                }
            }
        }
        return credential;
    }


authSource默認會指向我們目標數據的數據庫。然而在身份驗證機制中我們通常需要指向admin。(非常想報粗口,代碼作者在這里腦袋被men擠了么)。所以需要強制指定authSource中指定。具體指定方式如下:

 

 

 

 

mongodb://{用戶名}:{密碼}@{host1}:27017,{host2}:27017,{host3}:27017/{目標數據庫}?replicaSet={復制集名稱}&write=1&readPreference=primary&authsource={授權數據庫}


paulwong 2020-03-17 09:39 發表評論
]]>
5 ways to customize Spring MVC JSON/XML outputhttp://www.aygfsteel.com/paulwong/archive/2020/03/08/435236.htmlpaulwongpaulwongSun, 08 Mar 2020 07:55:00 GMThttp://www.aygfsteel.com/paulwong/archive/2020/03/08/435236.htmlhttp://www.aygfsteel.com/paulwong/comments/435236.htmlhttp://www.aygfsteel.com/paulwong/archive/2020/03/08/435236.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435236.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435236.htmlhttps://mostafa-asg.github.io/post/customize-json-xml-spring-mvc-output/



paulwong 2020-03-08 15:55 發表評論
]]>
SPRING BOOT 環境下減少中間件依賴的UNIT測試http://www.aygfsteel.com/paulwong/archive/2020/02/07/435067.htmlpaulwongpaulwongFri, 07 Feb 2020 02:28:00 GMThttp://www.aygfsteel.com/paulwong/archive/2020/02/07/435067.htmlhttp://www.aygfsteel.com/paulwong/comments/435067.htmlhttp://www.aygfsteel.com/paulwong/archive/2020/02/07/435067.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/435067.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/435067.htmlSPRING BOOT 環境下,測試有時會依賴于外部的中間件,如Mysql,Activemq,Mongodb等,那如何能減少這種依賴呢?
SPRING BOOT其實已經實現了自動化配置。

Mongodb

SPRING BOOT的自動化配置文件:org.springframework.boot.autoconfigure.mongo.embeddedEmbedded.MongoAutoConfiguration.java

在pom.xml中新增一test profile,并添加相應jar包,這樣可防止對其他profile的影響,如果是在Eclipse跑測試,需在Project的屬性中指定Active Profile為test,以覆蓋pom.xml的定義。
這種方式即使是使用SPRING DATA MONGODB的REPOSITORY也是適用的。

    <profile>
        <id>test</id>
        <dependencies>
            <dependency>
                <groupId>de.flapdoodle.embed</groupId>
                <artifactId>de.flapdoodle.embed.mongo</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
        <activation>
            <activeByDefault>false</activeByDefault>
        </activation>
    </profile>
在application-test.yaml中添加端口,其他如IP那些信息都不需要
spring:
   data:
      mongodb:
         port: 27017

unit test config

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.util.FileCopyUtils;

@Configuration
@Profile({"test", "integrationTest"})
@EnableMongoRepositories(
        basePackages = {"paul.com.repository"
        }
)
public class EmbeddedDataSourceConfiguration {
    
    @Value("classpath:/initdata/USER.json")
    private Resource userResource;

    @Value("classpath:/initdata/MEMBERS.json")
    private Resource membersResource;
    
    @Autowired
    private ResourceLoader resourceLoader;
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private MongoTemplate  mongoTemplate;
    
    @PostConstruct
    protected void initialize() throws FileNotFoundException, IOException {
        this.initializeHsqldb();
        this.initializeMongodb();
    }
    
    private void initializeHsqldb() {
        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        populator.addScript(resourceLoader.getResource("classpath:/org/springframework/batch/core/schema-hsqldb.sql"));
        populator.setContinueOnError(true);
        DatabasePopulatorUtils.execute(populator , dataSource);
    }
    
    private void initializeMongodb() throws FileNotFoundException, IOException {
        this.saveResource(userResource, "USER");
        
        this.saveDocumentList(membersResource, "MEMBER");
    }
    
    private void saveResource(Resource resource, String collectionName) {
        String resourceJson = this.asString(resource);
        Document resourceDocument = Document.parse(resourceJson);
        this.mongoTemplate.save(resourceDocument, collectionName);
    }
    
    private void saveDocumentList(Resource resource, String collectionName) {
        String resourceJson = this.asString(resource);
        Document resourceDocument = Document.parse("{ \"list\":" + resourceJson + "}");
        List<Document> documentList = resourceDocument.get("list", List.class);
        documentList.forEach(document -> this.mongoTemplate.save(document, collectionName));
    }
    
    private String asString(Resource resource) {
        try (Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) {
            return FileCopyUtils.copyToString(reader);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
    
//    @Bean(destroyMethod="close")
//    public DataSource dataSource() {
//        BasicDataSource dataSource = new BasicDataSource();
//        dataSource.setDriverClassName(environment.getProperty("batch.jdbc.driver"));
//        dataSource.setUrl(environment.getProperty("batch.jdbc.url"));
//        dataSource.setUsername(environment.getProperty("batch.jdbc.user"));
//        dataSource.setPassword(environment.getProperty("batch.jdbc.password"));
//        return dataSource;
//    }
}

ActiveMQ

只需更改application-test.yml中的brokerUrl為vm://embedded即可
spring:
   activemq:
      broker-url: vm://embedded?broker.persistent=false,useShutdownHook=false
      in-memory: true
      non-blocking-redelivery: true
      #packages:
        #trust-all: false
        #trusted: com.memorynotfound
      pool:
        block-if-full: true
        block-if-full-timeout: -1
        create-connection-on-startup: true
        enabled: false
        expiry-timeout: 0
        idle-timeout: 30000
        max-connections: 1
        maximum-active-session-per-connection: 500
        reconnect-on-exception: true
        time-between-expiration-check: -1
        use-anonymous-producers: true
        user: admin
        #password: ENC(hWJHuMyhydTqyF32neasTw==)
        password: admin

關系型數據庫

將在application-test.yml中的數據庫信息刪除,同時在pom.xml中添加jar包依賴,這邊是采用HSQL數據庫
    <profile>
        <id>test</id>
        <dependencies>
            <dependency>
                <groupId>org.hsqldb</groupId>
                <artifactId>hsqldb</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
        <activation>
            <activeByDefault>false</activeByDefault>
        </activation>
    </profile>

非SPRING BOOT/SPRING的純JDK環境可參考
https://github.com/yandex-qatools/embedded-services

https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo

https://github.com/jonyfs/spring-boot-data-embedded-mongodb/blob/master/src/main/java/br/com/jonyfs/spring/boot/data/embedded/mongodb/config/MongoConfig.java

ActiveMQ:
https://memorynotfound.com/spring-boot-embedded-activemq-configuration-example/

paulwong 2020-02-07 10:28 發表評論
]]>
How to provide a Spring Boot "fat JAR" with external dependencieshttp://www.aygfsteel.com/paulwong/archive/2019/12/27/434997.htmlpaulwongpaulwongFri, 27 Dec 2019 07:47:00 GMThttp://www.aygfsteel.com/paulwong/archive/2019/12/27/434997.htmlhttp://www.aygfsteel.com/paulwong/comments/434997.htmlhttp://www.aygfsteel.com/paulwong/archive/2019/12/27/434997.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/434997.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/434997.html
http://www.codevomit.xyz/bootlog/blog/how-to-provide-spring-boot-fat-jar


https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-executable-jar-format.html

paulwong 2019-12-27 15:47 發表評論
]]>
防止在SPRING BOOT的配置文件中使用明文存儲密碼http://www.aygfsteel.com/paulwong/archive/2019/11/26/434931.htmlpaulwongpaulwongTue, 26 Nov 2019 07:13:00 GMThttp://www.aygfsteel.com/paulwong/archive/2019/11/26/434931.htmlhttp://www.aygfsteel.com/paulwong/comments/434931.htmlhttp://www.aygfsteel.com/paulwong/archive/2019/11/26/434931.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/434931.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/434931.htmlhttps://stackoverflow.com/questions/37404703/spring-boot-how-to-hide-passwords-in-properties-file

工作隨筆——jasypt-spring-boot使用
https://www.cnblogs.com/zz0412/p/jasypt-001.html

Get史上最優雅加密方式!沒有之一!
https://www.jianshu.com/p/64ceda636e81

使用Jasypt對SpringBoot配置文件加密
https://www.jianshu.com/p/323ec96c46d2




paulwong 2019-11-26 15:13 發表評論
]]>
MAVEN打多環境包http://www.aygfsteel.com/paulwong/archive/2019/11/21/434920.htmlpaulwongpaulwongThu, 21 Nov 2019 08:40:00 GMThttp://www.aygfsteel.com/paulwong/archive/2019/11/21/434920.htmlhttp://www.aygfsteel.com/paulwong/comments/434920.htmlhttp://www.aygfsteel.com/paulwong/archive/2019/11/21/434920.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/434920.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/434920.html
如果是SPRING BOOT工程,部署時,要求配置文件不打進jar包中,要有sh啟動命令文件,最終產生一個ZIP包,包含所有需要的東西。這時就需要善用MAVEN的resource插件、assembly插件和jar插件了。

resource插件能重定義配置文件在output時的文件夾路徑,用profile的變量值替換配置文件中的占位符。
但要更改點位符的默認定義:
<properties>
        <resource.delimiter>${}</resource.delimiter>
    </properties>


jar插件能排除生成在classes文件夾中不要的文件被打進jar包中。

assembly插件能定義ZIP包中需要包含哪些文件。

<resources>  
        <resource>  
            <directory>src/main/bin</directory>
            <!--表明此文件夾中如有占位符,則會取pom中的profile中的值代替-->
            <filtering>true</filtering>
            <includes>  
                <include>*.sh</include>
            </includes>
        </resource>  
</resources>  

<plugin>  
        <artifactId>maven-jar-plugin</artifactId>  
        <configuration>
            <!--bin/文件夾不會被打進jar包-->
            <excludes>  
                <exclude>bin/</exclude>
            </excludes>
        </configuration>  
</plugin>  

<fileSet>  
        <!--定義bin/文件夾被打進zip包-->
        <directory>${build.outputDirectory}/bin</directory>  
        <outputDirectory>bin</outputDirectory>
        <fileMode>0755</fileMode>  
</fileSet>  


maven打包加時間戳
https://blog.csdn.net/z410970953/article/details/50680603

paulwong 2019-11-21 16:40 發表評論
]]>
SPRING BOOT 打包部署指南http://www.aygfsteel.com/paulwong/archive/2019/06/13/433857.htmlpaulwongpaulwongThu, 13 Jun 2019 07:22:00 GMThttp://www.aygfsteel.com/paulwong/archive/2019/06/13/433857.htmlhttp://www.aygfsteel.com/paulwong/comments/433857.htmlhttp://www.aygfsteel.com/paulwong/archive/2019/06/13/433857.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/433857.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/433857.htmlhttps://segmentfault.com/a/1190000017386408

paulwong 2019-06-13 15:22 發表評論
]]>
Spring Boot Admin資源http://www.aygfsteel.com/paulwong/archive/2017/03/21/432394.htmlpaulwongpaulwongTue, 21 Mar 2017 14:10:00 GMThttp://www.aygfsteel.com/paulwong/archive/2017/03/21/432394.htmlhttp://www.aygfsteel.com/paulwong/comments/432394.htmlhttp://www.aygfsteel.com/paulwong/archive/2017/03/21/432394.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/432394.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/432394.htmlhttp://blog.csdn.net/kinginblue/article/details/52132113


paulwong 2017-03-21 22:10 發表評論
]]>
微服務框架Spring Cloudhttp://www.aygfsteel.com/paulwong/archive/2016/09/11/431788.htmlpaulwongpaulwongSun, 11 Sep 2016 12:49:00 GMThttp://www.aygfsteel.com/paulwong/archive/2016/09/11/431788.htmlhttp://www.aygfsteel.com/paulwong/comments/431788.htmlhttp://www.aygfsteel.com/paulwong/archive/2016/09/11/431788.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/431788.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/431788.html2016

paulwong 2016-09-11 20:49 發表評論
]]>
JHipsterhttp://www.aygfsteel.com/paulwong/archive/2016/09/11/431787.htmlpaulwongpaulwongSun, 11 Sep 2016 08:40:00 GMThttp://www.aygfsteel.com/paulwong/archive/2016/09/11/431787.htmlhttp://www.aygfsteel.com/paulwong/comments/431787.htmlhttp://www.aygfsteel.com/paulwong/archive/2016/09/11/431787.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/431787.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/431787.htmlhttp://jhipster.cn/

paulwong 2016-09-11 16:40 發表評論
]]>
Spring Boot 性能優化http://www.aygfsteel.com/paulwong/archive/2016/09/11/431786.htmlpaulwongpaulwongSun, 11 Sep 2016 08:37:00 GMThttp://www.aygfsteel.com/paulwong/archive/2016/09/11/431786.htmlhttp://www.aygfsteel.com/paulwong/comments/431786.htmlhttp://www.aygfsteel.com/paulwong/archive/2016/09/11/431786.html#Feedback0http://www.aygfsteel.com/paulwong/comments/commentRss/431786.htmlhttp://www.aygfsteel.com/paulwong/services/trackbacks/431786.html摘要
Spring 框架給企業軟件開發者提供了常見問題的通用解決方案,包括那些在未來開發中沒有意識到的問題。但是,它構建的 J2EE 項目變得越來越臃腫,逐漸被 Spring Boot 所替代。Spring Boot 讓我們創建和運行項目變得更為迅速,現在已經有越來越多的人使用它。我們已經在幾個項目中使用了 Spring Boot ,今天我們就來一起討論一下如何改進 Spring Boot 應用的性能。

Spring 框架給企業軟件開發者提供了常見問題的通用解決方案,包括那些在未來開發中沒有意識到的問題。但是,它構建的 J2EE 項目變得越來越臃腫,逐漸被 Spring Boot 所替代。Spring Boot 讓我們創建和運行項目變得更為迅速,現在已經有越來越多的人使用它。我們已經在幾個項目中使用了 Spring Boot ,今天我們就來一起討論一下如何改進 Spring Boot 應用的性能。

首先,從之前我在開發中遇到的一個問題說起。在一次查看項目運行日志的時候,我偶然發現了一個問題,日志里顯示這個項目總是加載 Velocity 模板引擎,但實際上這個項目是一個沒有 web 頁面的 REST Service 項目。于是我花了一點時間去尋找產生這個問題的原因,以及如何改進 Spring Boot 應用的性能。在查找了相關的資料后,我得出的結論如下:

組件自動掃描帶來的問題

默認情況下,我們會使用 @SpringBootApplication 注解來自動獲取的應用的配置信息,但這樣也會給應用帶來一些副作用。使用這個注解后,會觸發自動配置( auto-configuration )和 組件掃描 ( component scanning),這跟使用 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 三個注解的作用是一樣的。這樣做給開發帶來方便的同時,也會有兩方面的影響:

1、會導致項目啟動時間變長。當啟動一個大的應用程序,或將做大量的集成測試啟動應用程序時,影響會特別明顯。

2、會加載一些不需要的多余的實例(beans)。

3、會增加 CPU 消耗。

針對以上兩個情況,我們可以移除 @SpringBootApplication 和 @ComponentScan 兩個注解來禁用組件自動掃描,然后在我們需要的 bean 上進行顯式配置:

// 移除 @SpringBootApplication and @ComponentScan, 用 @EnableAutoConfiguration 來替代
@Configuration
@EnableAutoConfiguration
public class SampleWebUiApplication {

    
// 

    
// 用 @Bean 注解明確顯式配置,以便被 Spring 掃描到
    @Bean
    
public MessageController messageController(MessageRepository messageRepository) {
        
return new MessageController(messageRepository);
    }

如何避免組件自動掃描帶來的問題

我們在上面提到,@SpringBootApplication 注解的作用跟 @EnableAutoConfiguration 注解的作用是相當的,那就意味著它也能帶來上述的三個問題。要避免這些問題,我們就要知道我們需要的組件列表是哪些,可以用 -Ddebug 的方式來幫助我們明確地定位:

mvn spring-boot:run -Ddebug … ========================= AUTO-CONFIGURATION REPORT =========================   Positive matches: -----------------     DispatcherServletAutoConfiguration       - @ConditionalOnClass classes found: org.springframework.web.servlet.DispatcherServlet (OnClassCondition)       - found web application StandardServletEnvironment (OnWebApplicationCondition)  ... 

接著拷貝 Positive matches 中列出的信息:

DispatcherServletAutoConfiguration 
EmbeddedServletContainerAutoConfiguration
ErrorMvcAutoConfiguration
HttpEncodingAutoConfiguration
HttpMessageConvertersAutoConfiguration
JacksonAutoConfiguration
JmxAutoConfiguration
MultipartAutoConfiguration
ServerPropertiesAutoConfiguration
PropertyPlaceholderAutoConfiguration
ThymeleafAutoConfiguration
WebMvcAutoConfiguration
WebSocketAutoConfiguration

然后來更新項目配置,顯式地引入這些組件,引入之后,再運行一下應用確保沒有錯誤發生:

@Configuration
@Import({
        DispatcherServletAutoConfiguration.
class,
        EmbeddedServletContainerAutoConfiguration.
class,
        ErrorMvcAutoConfiguration.
class,
        HttpEncodingAutoConfiguration.
class,
        HttpMessageConvertersAutoConfiguration.
class,
        JacksonAutoConfiguration.
class,
        JmxAutoConfiguration.
class,
        MultipartAutoConfiguration.
class,
        ServerPropertiesAutoConfiguration.
class,
        PropertyPlaceholderAutoConfiguration.
class,
        ThymeleafAutoConfiguration.
class,
        WebMvcAutoConfiguration.
class,
        WebSocketAutoConfiguration.
class,
})
public class SampleWebUiApplication {}


在上面的代碼中,我們可以刪掉我們不需要的組件信息,來提高應用的性能,比如在我的項目中,不需要 JMX 和 WebSocket 功能,我就刪掉了它們。刪掉之后,再次運行項目,確保一切正常。

將Servlet容器變成Undertow

默認情況下,Spring Boot 使用 Tomcat 來作為內嵌的 Servlet 容器。我們可以啟動項目,然后用 VisualVM 或者 JConsole 來查看應用所占的內存情況:

Spring Boot 性能優化

以上是我使用 Spring Boot 的默認方式啟動應用后,用 VisualVM 監控到的內存的占用情況:堆內存占用 110M,16 個線程被開啟。

可以將 Web 服務器切換到 Undertow 來提高應用性能。Undertow 是一個采用 Java 開發的靈活的高性能 Web 服務器,提供包括阻塞和基于 NIO 的非堵塞機制。Undertow 是紅帽公司的開源產品,是 Wildfly 默認的 Web 服務器。首先,從依賴信息里移除 Tomcat 配置:

<exclusions>
        
<exclusion>
                
<groupId>org.springframework.boot</groupId>
                
<artifactId>spring-boot-starter-tomcat</artifactId>
        
</exclusion>
</exclusions>


然后添加 Undertow:

<dependency>
        
<groupId>org.springframework.boot</groupId>
        
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>


啟動項目后,用 VisualVM 監控到的信息顯示:堆內存占用 90M,13個線程被開啟。

Spring Boot 性能優化

總結

這些都是我們在項目開發中使用到的一些優化 Spring Boot 應用的小技巧,對于大的應用性能的提高還是很明顯的。大家可以嘗試一下,然后告訴我們你的測試結果。

最后,附上代碼,大家可以去這里下載:spring-boot-performance

文中大部分內容參考英國一個架構師的博客 和 DZone 近期發布的文章,在此感謝兩位大牛。參考文章及鏈接:

(1)Spring Boot 性能優化:Spring Boot Performance

(2)Spring Boot 內存優化:Spring Boot Memory Performance

(3)https://www.techempower.com/benchmarks/

(4)Spring 應用程序優化:Optimizing Spring Framework for App Engine Applications



paulwong 2016-09-11 16:37 發表評論
]]>
主站蜘蛛池模板: 通河县| 阳山县| 肇东市| 河南省| 固原市| 墨脱县| 甘洛县| 微山县| 马山县| 天长市| 盈江县| 兴化市| 阜阳市| 岳阳市| 武隆县| 麟游县| 左权县| 翁源县| 四子王旗| 广丰县| 梓潼县| 明星| 航空| 施秉县| 铜川市| 蒲城县| 南华县| 武胜县| 平顶山市| 六安市| 马公市| 兴国县| 平泉县| 兰考县| 彰化县| 余江县| 天峨县| 涪陵区| 巧家县| 平度市| 大同县|