摘要:不同與其它中間件框架,中有大量的業(yè)務(wù)代碼,它向我們展示了大神是如何寫業(yè)務(wù)代碼的依賴的層次結(jié)構(gòu),如何進(jìn)行基礎(chǔ)包配置,以及工具類編寫,可以稱之為之最佳實(shí)踐。代碼參考視圖解析器,這里的配置指的是不檢查頭,而且默認(rèn)請(qǐng)求為格式。
不同與其它中間件框架,Apollo中有大量的業(yè)務(wù)代碼,它向我們展示了大神是如何寫業(yè)務(wù)代碼的:maven依賴的層次結(jié)構(gòu),如何進(jìn)行基礎(chǔ)包配置,以及工具類編寫,可以稱之為springboot之最佳實(shí)踐。
一 apollo項(xiàng)目依賴apollo中有7個(gè)子項(xiàng)目
最重要的有四個(gè)
apollo-portal:后臺(tái)管理服務(wù)
apollo-admin:后臺(tái)配置管理服務(wù),用戶發(fā)布了的配置項(xiàng)會(huì)經(jīng)過(guò)portal->admin更新到數(shù)據(jù)庫(kù)
apollo-configservice: 配置管理服務(wù),客戶端通過(guò)該服務(wù)拉取配置項(xiàng)
apollo-client:客戶端,集成該客戶端拉取配置項(xiàng)
此外還有apollo-biz,apollo-common,apollo-core提供基礎(chǔ)服務(wù)
其依賴關(guān)系如下
utils中集成了了一些通用方法,比如判斷非空,對(duì)象拷貝,字符串拼接等
BeanUtils 拷貝對(duì)象實(shí)現(xiàn)不同類對(duì)象中屬性的拷貝,服務(wù)之間傳遞的都是dto對(duì)象,而在使用時(shí)必須轉(zhuǎn)換為用法:
//在網(wǎng)絡(luò)中傳輸?shù)臑镈TO對(duì)象,而程序中處理的是實(shí)體類對(duì)象 @RequestMapping(path = "/apps", method = RequestMethod.POST) public AppDTO create(@RequestBody AppDTO dto) { ... //DTO拷貝成實(shí)體類 App entity = BeanUtils.transfrom(App.class, dto); ... //實(shí)體類再拷貝成DTO dto = BeanUtils.transfrom(AppDTO.class, entity);
源碼:
// 封裝{@link org.springframework.beans.BeanUtils#copyProperties},慣用與直接將轉(zhuǎn)換結(jié)果返回 public staticExceptionUtils 將exception轉(zhuǎn)為StringT transfrom(Class clazz, Object src) { if (src == null) { return null; } T instance = null; try { instance = clazz.newInstance(); } catch (Exception e) { throw new BeanUtilsException(e); } org.springframework.beans.BeanUtils.copyProperties(src, instance, getNullPropertyNames(src)); return instance; }
//將exception轉(zhuǎn)為String } catch (IllegalAccessException ex) { if (logger.isErrorEnabled()) { logger.error(ExceptionUtils.exceptionToString(ex));
ExceptionUtils源碼
public static String toString(HttpStatusCodeException e) { MaperrorAttributes = gson.fromJson(e.getResponseBodyAsString(), mapType); if (errorAttributes != null) { return MoreObjects.toStringHelper(HttpStatusCodeException.class).omitNullValues() .add("status", errorAttributes.get("status")) .add("message", errorAttributes.get("message"))
當(dāng)中用到了Guava的MoreObjects的鏈?zhǔn)秸{(diào)用來(lái)優(yōu)雅的拼接字符串,參考Guava Object的使用
InputValidator驗(yàn)證ClusterName和AppName是否正確
RequestPrecondition做非空、正數(shù)判斷等,抽象出了一個(gè)類,而不用硬編碼了
RequestPrecondition.checkArguments(!StringUtils.isContainEmpty(model.getReleasedBy(), model .getReleaseTitle()), "Params(releaseTitle and releasedBy) can not be empty");UniqueKeyGenerator
key值生成器
2 exception 異常包封裝常用的異常處理類,對(duì)常見(jiàn)的異常做了分類,比如業(yè)務(wù)異常,服務(wù)異常,not found異常等,大家做異常時(shí)不妨參考下其對(duì)異常的分類。
AbstractApolloHttpExceptionapollo異常基類,設(shè)置了httpstatus,便于返回準(zhǔn)確的http的報(bào)錯(cuò)信息,其繼承了RuntimeException,并加入了一個(gè)httpStatus
public abstract class AbstractApolloHttpException extends RuntimeException{ protected HttpStatus httpStatus; ... }BadRequestException
業(yè)務(wù)異常類,下圖可以看出其對(duì)業(yè)務(wù)異常的分類描述
某個(gè)值找不到了
當(dāng)對(duì)應(yīng)的服務(wù)不可達(dá),比如這段
ServiceException e = new ServiceException(String.format("No available admin server." + " Maybe because of meta server down or all admin server down. " + "Meta server address: %s", MetaDomainConsts.getDomain(env)));BeanUtilsException
BeanUtils中進(jìn)行對(duì)象轉(zhuǎn)換時(shí)發(fā)生異常類
3 Controller包封裝了異常處理中心,報(bào)文轉(zhuǎn)換,http序列換等工具
3.1 WebMvcConfig實(shí)現(xiàn)了WebMvcConfigurer和WebServerFactoryCustomizer
public class WebMvcConfig implements WebMvcConfigurer, WebServerFactoryCustomizer{
而我們的WebMvcConfigurer是個(gè)接口,類實(shí)現(xiàn)這個(gè)接口來(lái)具備一定的能力,以下就列出了這些能力
挑重點(diǎn)介紹下
@Override public void addArgumentResolvers(ListargumentResolvers) { PageableHandlerMethodArgumentResolver pageResolver = new PageableHandlerMethodArgumentResolver(); pageResolver.setFallbackPageable(PageRequest.of(0, 10)); argumentResolvers.add(pageResolver); }
重載HandlerMethodArgumentResolver是做啥用的呢?簡(jiǎn)單來(lái)說(shuō)就是用來(lái)處理spring mvc中各類參數(shù),比如@RequestParam、@RequestHeader、@RequestBody、@PathVariable、@ModelAttribute
而是使用了addArgumentResolvers后就加入了新的參數(shù)處理能力。HandlerMethodArgumentResolver中有兩個(gè)最重要的參數(shù)
supportsParameter:用于判定是否需要處理該參數(shù)分解,返回true為需要,并會(huì)去調(diào)用下面的方法resolveArgument。 resolveArgument:真正用于處理參數(shù)分解的方法,返回的Object就是controller方法上的形參對(duì)象。
比如apollo就加入的是對(duì)分頁(yè)的處理: PageableHandlerMethodArgumentResolver
這里我們可以看個(gè)例子,有這樣一個(gè)業(yè)務(wù)場(chǎng)景,用戶傳的報(bào)文在網(wǎng)絡(luò)中做了加密處理,需要對(duì)用戶報(bào)文做解密,相當(dāng)一個(gè)公共處理邏輯,寫到業(yè)務(wù)代碼中不方便維護(hù),此時(shí)就可以增加一個(gè)HandlerMethodArgumentResolver用于解密。代碼參考github:xxx
@Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer.favorPathExtension(false); configurer.ignoreAcceptHeader(true).defaultContentType(MediaType.APPLICATION_JSON); }
視圖解析器,這里的配置指的是不檢查accept頭,而且默認(rèn)請(qǐng)求為json格式。
靜態(tài)資源控制器
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 10 days addCacheControl(registry, "img", 864000); addCacheControl(registry, "vendor", 864000); // 1 day addCacheControl(registry, "scripts", 86400); addCacheControl(registry, "styles", 86400); addCacheControl(registry, "views", 86400); }
靜態(tài)資源的訪問(wèn)時(shí)間。
定制tomcat,spring boot集成了tomcat,在2.0以上版本中,通過(guò)實(shí)現(xiàn)WebServerFactoryCustomizer類來(lái)自定義tomcat,比如在這里設(shè)置字符集
@Override public void customize(TomcatServletWebServerFactory factory) { MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT); mappings.add("html", "text/html;charset=utf-8"); factory.setMimeMappings(mappings ); }3.2 GlobalDefaultExceptionHandler
統(tǒng)一異常處理類,用于抓取controller層的所有異常,從此再也不用寫超級(jí)多的try...catch了。只要加了@ControllerAdvice就能抓取所有異常了。
@ControllerAdvice public class GlobalDefaultExceptionHandler {
而后使用@ExcepionHandler來(lái)抓取異常,比如這樣
//處理系統(tǒng)內(nèi)置的Exception @ExceptionHandler(Throwable.class) public ResponseEntity
在apollo中定義了這幾個(gè)異常:
內(nèi)置異常: Throwable,HttpRequestMethodNotSupportedException,HttpStatusCodeException,AccessDeniedException
以及apollo自定義的異常AbstractApolloHttpException
將異常進(jìn)行分類能方便直觀的展示所遇到的異常
根據(jù)用戶請(qǐng)求頭來(lái)格式化不同對(duì)象。請(qǐng)求傳給服務(wù)器的都是一個(gè)字符串流,而服務(wù)器根據(jù)用戶請(qǐng)求頭判斷不同媒體類型,然后在已注冊(cè)的轉(zhuǎn)換器中查找對(duì)應(yīng)的轉(zhuǎn)換器,比如在content-type中發(fā)現(xiàn)json后,就能轉(zhuǎn)換成json對(duì)象了
@Configuration public class HttpMessageConverterConfiguration { @Bean public HttpMessageConverters messageConverters() { GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter(); gsonHttpMessageConverter.setGson( new GsonBuilder().setDateFormat("yyyy-MM-dd"T"HH:mm:ss.SSSZ").create()); final List> converters = Lists.newArrayList( new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(), new AllEncompassingFormHttpMessageConverter(), gsonHttpMessageConverter); return new HttpMessageConverters() { @Override public List > getConverters() { return converters; } }; } }
apollo中自定了GsonHttpMessageConverter,重寫了默認(rèn)的json轉(zhuǎn)換器,這種轉(zhuǎn)換當(dāng)然更快樂(lè),Gson是google的一個(gè)json轉(zhuǎn)換器,當(dāng)然,傳說(shuō)ali 的fastjson會(huì)更快,但是貌似fastjson問(wèn)題會(huì)很多。json處理中對(duì)于日期格式的處理也是一個(gè)大問(wèn)題,所以這里也定義了日期格式轉(zhuǎn)換器。
3.4 CharacterEncodingFilterConfiguration 過(guò)濾器@Configuration public class CharacterEncodingFilterConfiguration { @Bean public FilterRegistrationBean encodingFilter() { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new CharacterEncodingFilter()); bean.addInitParameter("encoding", "UTF-8"); bean.setName("encodingFilter"); bean.addUrlPatterns("/*"); bean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD); return bean; } }
加入了一個(gè)CharacterEncodingFilter將所有的字符集全部轉(zhuǎn)換成UTF-8.
四 aop包里面只定義了一個(gè)類,用于給所有的數(shù)據(jù)庫(kù)操作都加上cat鏈路跟蹤,簡(jiǎn)單看下它的用法
@Aspect //定義一個(gè)切面 @Component public class RepositoryAspect { /** ** 所有Repository下的類都必須都添加切面 */ @Pointcut("execution(public * org.springframework.data.repository.Repository+.*(..))") public void anyRepositoryMethod() { } /** ** 切面的具體方法 */ @Around("anyRepositoryMethod()") public Object invokeWithCatTransaction(ProceedingJoinPoint joinPoint) throws Throwable { ... }五 condition 條件注解
cloud條件注解
@ConditionalOnBean:當(dāng)SpringIoc容器內(nèi)存在指定Bean的條件 @ConditionalOnClass:當(dāng)SpringIoc容器內(nèi)存在指定Class的條件 @ConditionalOnExpression:基于SpEL表達(dá)式作為判斷條件 @ConditionalOnJava:基于JVM版本作為判斷條件 @ConditionalOnJndi:在JNDI存在時(shí)查找指定的位置 @ConditionalOnMissingBean:當(dāng)SpringIoc容器內(nèi)不存在指定Bean的條件 @ConditionalOnMissingClass:當(dāng)SpringIoc容器內(nèi)不存在指定Class的條件 @ConditionalOnNotWebApplication:當(dāng)前項(xiàng)目不是Web項(xiàng)目的條件 @ConditionalOnProperty:指定的屬性是否有指定的值 @ConditionalOnResource:類路徑是否有指定的值 @ConditionalOnSingleCandidate:當(dāng)指定Bean在SpringIoc容器內(nèi)只有一個(gè),或者雖然有多個(gè)但是指定首選的Bean @ConditionalOnWebApplication:當(dāng)前項(xiàng)目是Web項(xiàng)目的條件
ConditionalOnBean
@Configuration public class Configuration1 { @Bean @ConditionalOnBean(Bean2.class) public Bean1 bean1() { return new Bean1(); } } @Configuration public class Configuration2 { @Bean public Bean2 bean2(){ return new Bean2(); }
在spring ioc的過(guò)程中,優(yōu)先解析@Component,@Service,@Controller注解的類。其次解析配置類,也就是@Configuration標(biāo)注的類。最后開始解析配置類中定義的bean。
在apollo中使用自定義condition:
用注解實(shí)現(xiàn)spi
@Configuration @Profile("ctrip") public static class CtripEmailConfiguration { @Bean public EmailService ctripEmailService() { return new CtripEmailService(); } @Bean public CtripEmailRequestBuilder emailRequestBuilder() { return new CtripEmailRequestBuilder(); } }
spi的定義: SPI 全稱為 Service Provider Interface,是一種服務(wù)發(fā)現(xiàn)機(jī)制。SPI 的本質(zhì)是將接口實(shí)現(xiàn)類的全限定名配置在文件中,并由服務(wù)加載器讀取配置文件,加載實(shí)現(xiàn)類。這樣可以在運(yùn)行時(shí),動(dòng)態(tài)為接口替換實(shí)現(xiàn)類。正因此特性,我們可以很容易的通過(guò) SPI 機(jī)制為我們的程序提供拓展功能。
而 @Profile("ctrip")是特指在系統(tǒng)環(huán)境變量中存在ctrip時(shí)才會(huì)生效,限定了方法的生效環(huán)境。
還有一種常見(jiàn)的方式是做數(shù)據(jù)庫(kù)配置,比如在不同的dev,stg,prd環(huán)境中配置不同的地址,或者使用不同的數(shù)據(jù)庫(kù):
@Profile("dev") @Profile("stg") @Profile("prd")
但這樣存在一個(gè)問(wèn)題是無(wú)法滿足devops的一次編譯,多處運(yùn)行的原則,因此最好是將配置放置與外部,通過(guò)不同環(huán)境特征來(lái)獲取不同的配置文件。
看下自定義的condition是如何實(shí)現(xiàn)的:
定義注解
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnProfileCondition.class) //具體的實(shí)現(xiàn)類 public @interface ConditionalOnMissingProfile { //注解的名稱 /** * The profiles that should be inactive * @return */ String[] value() default {}; }
而后再實(shí)現(xiàn)類中實(shí)現(xiàn)了對(duì)環(huán)境變量的判斷
//實(shí)現(xiàn)condition接口 public class OnProfileCondition implements Condition { //如果match則返回true @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //獲取環(huán)境變量中所有的active值, spring.profile.active=xxx SetactiveProfiles = Sets.newHashSet(context.getEnvironment().getActiveProfiles()); //獲取profile中的所有制 Set requiredActiveProfiles = retrieveAnnotatedProfiles(metadata, ConditionalOnProfile.class.getName()); Set requiredInactiveProfiles = retrieveAnnotatedProfiles(metadata, ConditionalOnMissingProfile.class .getName()); return Sets.difference(requiredActiveProfiles, activeProfiles).isEmpty() && Sets.intersection(requiredInactiveProfiles, activeProfiles).isEmpty(); } private Set retrieveAnnotatedProfiles(AnnotatedTypeMetadata metadata, String annotationType) { if (!metadata.isAnnotated(annotationType)) { return Collections.emptySet(); } MultiValueMap attributes = metadata.getAllAnnotationAttributes(annotationType); if (attributes == null) { return Collections.emptySet(); } Set profiles = Sets.newHashSet(); List> values = attributes.get("value"); if (values != null) { for (Object value : values) { if (value instanceof String[]) { Collections.addAll(profiles, (String[]) value); } else { profiles.add((String) value); } } } return profiles; } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/77852.html
摘要:零為何要學(xué)源碼簡(jiǎn)單,是我現(xiàn)在看起來(lái)最簡(jiǎn)單的源碼不會(huì)像封裝了一層又一層,把人繞暈,而沒(méi)有那么多封裝,上手快,我們學(xué)習(xí)就應(yīng)該從簡(jiǎn)單的開始憑什么非要去學(xué)封的像粽子一樣的源碼,我們就是要去學(xué)簡(jiǎn)簡(jiǎn)單單,平時(shí)樸素,接地氣的源碼最接近業(yè)務(wù)代碼的源碼。 零 為何要學(xué)apollo源碼 1 簡(jiǎn)單,Apollo是我現(xiàn)在看起來(lái)最簡(jiǎn)單的源碼不會(huì)像spring封裝了一層又一層,把人繞暈,而apollo沒(méi)有那么多封...
摘要:分鐘學(xué)是一個(gè)系列,簡(jiǎn)單暴力,包學(xué)包會(huì)。接管了請(qǐng)求和狀態(tài)管理。一般在生產(chǎn)環(huán)境中,我們通常還希望做權(quán)限驗(yàn)證請(qǐng)求攔截等事務(wù)處理。 21 分鐘學(xué) apollo-client 是一個(gè)系列,簡(jiǎn)單暴力,包學(xué)包會(huì)。 搭建 Apollo client 端,集成 redux使用 apollo-client 來(lái)獲取數(shù)據(jù)修改本地的 apollo store 數(shù)據(jù)提供定制方案 請(qǐng)求攔截 封裝修改 clie...
摘要:改造背景前面我們講解了如何對(duì)接來(lái)持久化限流的規(guī)則,對(duì)接后可以直接通過(guò)的后臺(tái)進(jìn)行規(guī)則的修改,推送到各個(gè)客戶端實(shí)時(shí)生效。因此推送規(guī)則正確做法應(yīng)該是配置中心控制臺(tái)控制臺(tái)配置中心數(shù)據(jù)源,而不是經(jīng)數(shù)據(jù)源推送至配置中心。 改造背景 前面我們講解了如何對(duì)接Apollo來(lái)持久化限流的規(guī)則,對(duì)接后可以直接通過(guò)Apollo的后臺(tái)進(jìn)行規(guī)則的修改,推送到各個(gè)客戶端實(shí)時(shí)生效。 但還有一個(gè)問(wèn)題就是Sentinel...
摘要:分鐘學(xué)是一個(gè)系列,簡(jiǎn)單暴力,包學(xué)包會(huì)。一旦你丟失了,可能會(huì)導(dǎo)致寫入失敗,或者盡管寫入了,但本該攜帶的那一層的數(shù)據(jù)沒(méi)有寫入。 21 分鐘學(xué) apollo-client 是一個(gè)系列,簡(jiǎn)單暴力,包學(xué)包會(huì)。 搭建 Apollo client 端,集成 redux使用 apollo-client 來(lái)獲取數(shù)據(jù)修改本地的 apollo store 數(shù)據(jù)提供定制方案 請(qǐng)求攔截 封裝修改 clie...
摘要:分鐘學(xué)是一個(gè)系列,簡(jiǎn)單暴力,包學(xué)包會(huì)。那怎么辦呢本章就教你非常簡(jiǎn)單地實(shí)現(xiàn)擴(kuò)展的。我們可以借鑒的的寫法,為的實(shí)例添加一些自己的方法。更重要的是,也會(huì)有的效果,上一個(gè)的輸出會(huì)成為下一個(gè)的輸入,便于組合。 21 分鐘學(xué) apollo-client 是一個(gè)系列,簡(jiǎn)單暴力,包學(xué)包會(huì)。 搭建 Apollo client 端,集成 redux使用 apollo-client 來(lái)獲取數(shù)據(jù)修改本地的 ...
閱讀 1072·2023-04-26 02:26
閱讀 2192·2021-09-26 10:16
閱讀 1576·2019-08-30 12:57
閱讀 3491·2019-08-29 16:10
閱讀 3245·2019-08-29 13:47
閱讀 1231·2019-08-29 13:12
閱讀 2160·2019-08-29 11:11
閱讀 1357·2019-08-26 13:28