代理模式介绍
- 代表:被選中或當選為他人投票或代表他人的人– Merriam-Webster 。
- 委托模式:在軟件工程中,委托模式是面向對象編程中的一種設計模式,其中,一個對象而不是執行其聲明的任務之一,而是將該任務委托給一個關聯的輔助對象Wikipedia 。
- 讓事情盡可能簡單,但不要簡單- 愛因斯坦 ( Albert Einstein)釋義 。
Spring Batch是Enterprise Java工具箱中的重要工具。 它提供了開箱即用的強大功能,尤其是從不同來源讀取和寫入數據時。 我們在此博客中提供了幾篇介紹Spring Batch的文章。 如果您不熟悉Spring Batch和Reader,Processor,Writer Tasklet,請花點時間回顧一下。
我上面使用的措辭對我來說很重要。 我嘗試做的一件事就是保持我提供的代碼盡可能可維護。 我希望它能正常工作,但是我今天簽入的代碼將在以后某個日期由某些人維護。 保持代碼盡可能簡單是確保代碼易于維護的一種方法。
那么,當您必須處理復雜的數據源時會發生什么呢?
我們發現,經常要處理的輸入文件并不像每行一個記錄那么簡單。 通常,文件中有多行僅描述一條記錄。
例如:
HKaren Traviss LAB00KW3VG2G LI0345478274 LI0345511131 F00000003 HJim Butcher LI0451457811 F00000001 HDave Duncan LI0380791277 LI0345352912 F00000002 HRik Scarborough LI9999999999 F00000001在這里,我們有一個文件,其中包含十五行中的四個記錄。 每條記錄均以頁眉行開頭,包含一個或多個正文行,并以頁腳結尾。 標頭包含線型(標頭為H)和名稱。 該行還包含線型(L),查找類型(在此示例中為ISBN或Amazon代碼)以及查找書本的鍵。 頁腳再次包含線型和此塊中的記錄數。
使用標準的讀取器,將讀取每一行,然后傳遞給處理器,然后處理器必須確定處理的是哪種類型的行。 然后,處理程序在處理每條正文行時,處理程序將必須保留來自每個標頭的信息,直到處理了頁腳。 然后,編寫者將必須知道處理器發送的每一行,以及是否應將其寫入。 這很復雜,部分是因為多個對象必須知道如何讀取文件,而不是處理器僅關心單個對象,而編寫器僅關心編寫給出的內容。
相反,讓我們將Delegate模式引入Reader并讓其處理創建整個記錄的過程。 由于我們具有來自多行的信息以及用于創建每個記錄的頁眉和頁腳,因此我們必須將記錄列表傳遞給處理程序。 你們當中的觀察者會注意到,每個記錄都包含一個ISBN或Amazon圖書符號,并且可以用來查找作者(也包含在標題中)。 在現實生活中,這種冗余可能也不會發生。
讓我們將輸出包裝在另一個對象中,以使其更易于使用。
public class OrderReaderStep implements ItemReader<OrderList> {private static final Logger logger = LoggerFactory.getLogger(OrderReaderStep.class);private FlatFileItemReader <FieldSet> delegate;private static final String FOOTER = "F*";private static final String BODY = "L*";private static final String HEADER = "H*";@BeforeSteppublic void beforeStep(StepExecution stepExecution) {delegate = new FlatFileItemReader<>();delegate.setResource(new ClassPathResource("orders.txt"));final DefaultLineMapper <FieldSet> defaultLineMapper = new DefaultLineMapper<>();final PatternMatchingCompositeLineTokenizer orderFileTokenizer = new PatternMatchingCompositeLineTokenizer();final Map<String, LineTokenizer> tokenizers = new HashMap<>();tokenizers.put(HEADER, buildHeaderTokenizer());tokenizers.put(BODY, buildBodyTokenizer());tokenizers.put(FOOTER, buildFooterTokenizer());orderFileTokenizer.setTokenizers(tokenizers);defaultLineMapper.setLineTokenizer(orderFileTokenizer);defaultLineMapper.setFieldSetMapper(new PassThroughFieldSetMapper());delegate.setLineMapper(defaultLineMapper);delegate.open(stepExecution.getExecutionContext());}@AfterSteppublic void afterStep(StepExecution stepExecution) {delegate.close();}@Overridepublic OrderList read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {logger.info("start read");OrderList record = null;FieldSet line;List<Order> bodyList = new ArrayList<>();while ((line = delegate.read()) != null) {String prefix = line.readString("lineType");if (prefix.equals("H")) {record = new OrderList();record.setName(line.readString("name"));} else if (prefix.equals("L")) {Order order = new Order();order.setLookup(line.readString("lookupKey"));order.setLookupType(line.readString("keyType"));bodyList.add(order);} else if (prefix.equals("F")) {if (record != null) {if (line.readLong("count") != bodyList.size()) {throw new ValidationException("Size does not match file count");}record.setOrders(bodyList);}break;}}logger.info("end read");return record;}private LineTokenizer buildBodyTokenizer() {FixedLengthTokenizer tokenizer = new FixedLengthTokenizer();tokenizer.setColumns(new Range[]{ //new Range(1, 1), // lineTypenew Range(2, 2), // keyTypenew Range(3, 12) // lookup key});tokenizer.setNames(new String[]{ //"lineType","keyType","lookupKey"}); //tokenizer.setStrict(false);return tokenizer;}private LineTokenizer buildFooterTokenizer() {FixedLengthTokenizer tokenizer = new FixedLengthTokenizer();tokenizer.setColumns(new Range[]{ //new Range(1, 1), // lineTypenew Range(2, 9) // count});tokenizer.setNames(new String[]{ //"lineType","count"}); //tokenizer.setStrict(false);return tokenizer;}private LineTokenizer buildHeaderTokenizer() {FixedLengthTokenizer tokenizer = new FixedLengthTokenizer();tokenizer.setColumns(new Range[]{ //new Range(1, 1), // lineTypenew Range(2, 20), // name});tokenizer.setNames(new String[]{ //"lineType","name"}); //tokenizer.setStrict(false);return tokenizer;}}此Reader實現ItemReader接口。 這為我們提供了一個由作業調用的read方法,直到它返回null或發生錯誤時引發異常。 在我們的Reader中,我們聲明另一個Reader,這是一個FlatFileItemReader。 這是我們的代表,即為我們執行功能所選擇的對象。 我們的read方法將以委托的讀取為循環,直到讀取Footer。 然后它將整個記錄捆綁到其包裝器中,并將其傳遞給處理器。
必須先打開委托閱讀器,然后才完成使用。 我必須在此處將它初始化并在此處進行設置,因此在BeforeStep中在此處打開它。 我也可以將包含的閱讀器實現為ItemStreamReader,并使用Interface給我們的open,close以及update方法。
將簡化的對象返回給Processor可以使我們大大簡化Processor:
@Override public List<BookList> process(OrderList orderList) throws Exception {logger.info("process");List<BookList> books = new ArrayList<>();for (Order order : orderList.getOrders()) {BookList bl = doProcessing(orderList.getName(), order);books.add(bl);}return books; }doProcessing方法可以包含此Job的業務邏輯,并且需要創建一個有效的BookList對象。 由于我們正在處理多個記錄,因此該過程將創建多個可返回的BookList,并將其傳遞給Writer。 我將留給您填寫該對象的其余部分,但這只是一個標準的ItemProcessor。 處理器不必在調用之間保留記錄信息,因此程序員可以專注于業務邏輯。
我們的編寫器實現ItemStreamWriter。 這為我們提供了比ItemWriter更多的方法,但是,如果您喜歡使用ItemWriter(類似于我們閱讀器的方法),請確保在BeforeStep中打開Delegate,在AfterStep中將其關閉。
在Writer中使用委托使我們能夠遍歷Writer從Reader和Process收到的List。
public class ListWriter implements ItemStreamWriter<List<BookList>> {private static final Logger logger = LoggerFactory.getLogger(ListWriter.class);private FlatFileItemWriter<BookList> delegate;@BeforeSteppublic void beforeStep(StepExecution stepExecution) {delegate = new FlatFileItemWriter<>();delegate.setResource(new FileSystemResource("booklist.csv"));delegate.setShouldDeleteIfEmpty(true);delegate.setAppendAllowed(true);DelimitedLineAggregator<BookList> dla = new DelimitedLineAggregator<>();dla.setDelimiter(",");BeanWrapperFieldExtractor<BookList> fieldExtractor = new BeanWrapperFieldExtractor<>();fieldExtractor.setNames(new String[]{"bookName", "author"});dla.setFieldExtractor(fieldExtractor);delegate.setLineAggregator(dla);}@Overridepublic void close() throws ItemStreamException {delegate.close();}@Overridepublic void open(ExecutionContext ec) throws ItemStreamException {delegate.open(ec);}@Overridepublic void update(ExecutionContext ec) throws ItemStreamException {delegate.update(ec);}@Overridepublic void write(List<? extends List<BookList>> list) throws Exception {logger.info("write");for (List<BookList> bookList : list) {delegate.write(bookList);}}}這為我們提供了以下輸出:
Going Grey,Karen Traviss Hard Contact,Karen Traviss 501st,Karen Traviss Storm Front,Jim Butcher Lord of the Fire Lands,Dave Duncan The Reluctant Swordsman,Dave Duncan Wolfbrander Series Unpublished,Rik Scarborough那么,如果稍微復雜一些并且輸入文件不包含頁腳,會發生什么呢?
邏輯記錄仍然從標題行開始,但在下一個標題之前的行結束。 在我們之前的示例中,系統必須先讀取下一行,然后才能知道已完成,然后具有一些復雜的邏輯來保留該信息以進行下一個遍歷。
HKaren Traviss LAB00KW3VG2G LI0345478274 LI0345511131 HJim Butcher LI0451457811 HDave Duncan LI0380791277 LI0345352912 HRik Scarborough LI9999999999要求我們當前的作者在下一次電話會議之前先閱讀并保留該記錄是不必要的復雜操作,這會導致維護麻煩。 但是,我們可以使用PeekableItemReader簡化此過程:
class OrderReaderStep2 implements ItemStreamReader<OrderList> {private static final String BODY = "L*";private static final String HEADER = "H*";private static final Logger logger = LoggerFactory.getLogger(OrderReaderStep2.class);private SingleItemPeekableItemReader <FieldSet> delegate;@BeforeSteppublic void beforeStep(StepExecution stepExecution) {FlatFileItemReader fileReader = new FlatFileItemReader<>();fileReader.setResource(new ClassPathResource("orders2.txt"));final DefaultLineMapper <FieldSet> defaultLineMapper = new DefaultLineMapper<>();final PatternMatchingCompositeLineTokenizer orderFileTokenizer = new PatternMatchingCompositeLineTokenizer();final Map<String, LineTokenizer> tokenizers = new HashMap<>();tokenizers.put(HEADER, buildHeaderTokenizer());tokenizers.put(BODY, buildBodyTokenizer());orderFileTokenizer.setTokenizers(tokenizers);defaultLineMapper.setLineTokenizer(orderFileTokenizer);defaultLineMapper.setFieldSetMapper(new PassThroughFieldSetMapper());fileReader.setLineMapper(defaultLineMapper);delegate = new SingleItemPeekableItemReader<>();delegate.setDelegate(fileReader);}@Overridepublic void close() throws ItemStreamException {delegate.close();}@Overridepublic void open(ExecutionContext ec) throws ItemStreamException {delegate.open(ec);}@Overridepublic OrderList read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {logger.info("start read");OrderList record = null;FieldSet line;List<Order> bodyList = new ArrayList<>();while ((line = delegate.read()) != null) {String prefix = line.readString("lineType");if (prefix.equals("H")) {record = new OrderList();record.setName(line.readString("name"));} else if (prefix.equals("L")) {Order order = new Order();order.setLookup(line.readString("lookupKey"));order.setLookupType(line.readString("keyType"));bodyList.add(order);}FieldSet nextLine = delegate.peek();if (nextLine == null || nextLine.readString("lineType").equals("H")) {record.setOrders(bodyList);break;}}logger.info("end read");return record;}@Overridepublic void update(ExecutionContext ec) throws ItemStreamException {delegate.update(ec);}private LineTokenizer buildBodyTokenizer() {FixedLengthTokenizer tokenizer = new FixedLengthTokenizer();tokenizer.setColumns(new Range[]{ //new Range(1, 1), // lineTypenew Range(2, 2), // keyTypenew Range(3, 12) // lookup key});tokenizer.setNames(new String[]{ //"lineType","keyType","lookupKey"}); //tokenizer.setStrict(false);return tokenizer;}private LineTokenizer buildHeaderTokenizer() {FixedLengthTokenizer tokenizer = new FixedLengthTokenizer();tokenizer.setColumns(new Range[]{ //new Range(1, 1), // lineTypenew Range(2, 20), // name});tokenizer.setNames(new String[]{ //"lineType","name"}); //tokenizer.setStrict(false);return tokenizer;}}這次,我確實將包含的Reader實現為ItemStreamReader,以向您展示它們之間的區別。 可以像上一個一樣將其實現為ItemReader。
PeekableItemReader允許我們向前查看下一條記錄,以查看是否到達記錄的末尾或文件的末尾。 然后可以使用相同的處理器和寫入器來產生與以前相同的輸出。
最后的想法
乍一看,委托模式似乎不像使用單個讀取器或寫入器那么簡單。 這兩個對象都有更多的配置。 但是我最喜歡的釋義是說要盡可能簡單,而且再簡單不過。 稍微復雜一點的Reader和Writer將使您的Processor更加簡單,并有助于后續維護。
代碼很好,我的朋友。
翻譯自: https://www.javacodegeeks.com/2016/03/introducing-delegate-pattern.html
總結
- 上一篇: mockito环境配置_Mockito
- 下一篇: 线程并发库和线程池的作用_并发–顺序线程