做者:小傅哥
博客:https://bugstack.cnhtml
沉澱、分享、成長,讓本身和他人都能有所收穫!😄
你感覺到的容易,必定有人爲你承擔不容易
前端
這句話更像是描述生活的,許許多多的磕磕絆絆總有人爲你提供躲雨的屋檐和避風的港灣。其實編程開發的團隊中也同樣有人只負責CRUD中的簡單調用,去使用團隊中高級程序員開發出來的核心服務和接口。這樣的編程開發對於初期剛進入程序員行業的小夥伴來講鍛鍊鍛鍊仍是不錯的,但隨着開發的日子愈來愈久一直作這樣的事情就很可貴到成長,也想努力的去作一些更有難度的承擔,以此來加強我的的技術能力。java
沒有最好的編程語言,語言只是工具
程序員
刀槍棍棒、斧鉞鉤叉、包子油條、盒子麻花,是語言。五郎八卦棍、十二路彈腿、洪家鐵線拳,是設計。記得葉問裏有一句臺詞是:金山找:今天我北方拳術,輸給你南方拳術了。葉問:你錯了,不是南北拳的問題,是你的問題。
因此當你編程開發寫的久了,就不會再特別在乎用的語言,而是爲目標服務,用最好的設計能力也就是編程的智慧作出作最完美的服務。這也就是編程人員的價值所在!web
設計與反設計以及過渡設計
spring
設計模式是解決程序中不合理、不易於擴展、不易於維護的問題,也是幹掉大部分ifelse
的利器,在咱們經常使用的框架中基本都會用到大量的設計模式來構建組件,這樣也能方便框架的升級和功能的擴展。但!若是不能合理的設計以及亂用設計模式,會致使整個編程變得更加複雜難維護,也就是咱們常說的;反設計
、過渡設計
。而這部分設計能力也是從實踐的項目中獲取的經驗,不斷的改造優化摸索出的最合理的方式,應對當前的服務體量。編程
bugstack蟲洞棧
,回覆源碼下載
獲取(打開獲取的連接,找到序號18)工程 | 描述 |
---|---|
itstack-demo-design-10-00 | 場景模擬工程;模擬一個提供接口服務的SpringBoot工程 |
itstack-demo-design-10-01 | 使用一坨代碼實現業務需求 |
itstack-demo-design-10-02 | 經過設計模式開發爲中間件,包裝通用型核心邏輯 |
外觀模式也叫門面模式,主要解決的是下降調用方的使用接口的複雜邏輯組合。這樣調用方與實際的接口提供方提供方提供了一箇中間層,用於包裝邏輯提供API接口。有些時候外觀模式也被用在中間件層,對服務中的通用性複雜邏輯進行中間件層包裝,讓使用方能夠只關心業務開發。設計模式
那麼這樣的模式在咱們的所見產品功能中也常常遇到,就像幾年前咱們註冊一個網站時候每每要添加不少信息,包括;姓名、暱稱、手機號、QQ、郵箱、住址、單身等等,但如今註冊成爲一個網站的用戶只須要一步便可,不管是手機號仍是微信也都提供了這樣的登陸服務。而對於服務端應用開發來講之前是提供了一個整套的接口,如今註冊的時候並無這些信息,那麼服務端就須要進行接口包裝,在前端調用註冊的時候服務端獲取相應的用戶信息(從各個渠道),若是獲取不到會讓用戶後續進行補全(營銷補全信息給獎勵),以此來拉動用戶的註冊量和活躍度。api
在本案例中咱們模擬一個將全部服務接口添加白名單的場景tomcat
在項目不斷壯大發展的路上,每一次發版上線都須要進行測試,而這部分測試驗證通常會進行白名單開量或者切量的方式進行驗證。那麼若是在每個接口中都添加這樣的邏輯,就會很是麻煩且不易維護。另外這是一類具有通用邏輯的共性需求,很是適合開發成組件,以此來治理服務,讓研發人員更多的關心業務功能開發。
通常狀況下對於外觀模式的使用一般是用在複雜或多個接口進行包裝統一對外提供服務上,此種使用方式也相對簡單在咱們日常的業務開發中也是最經常使用的。你可能常常聽到把這兩個接口包裝一下,但在本例子中咱們把這種設計思路放到中間件層,讓服務變得能夠統一控制。
itstack-demo-design-10-00 └── src ├── main │ ├── java │ │ └── org.itstack.demo.design │ │ ├── domain │ │ │ └── UserInfo.java │ │ ├── web │ │ │ └── HelloWorldController.java │ │ └── HelloWorldApplication.java │ └── resources │ └── application.yml └── test └── java └── org.itstack.demo.test └── ApiTest.java
SpringBoot
的HelloWorld
工程,在工程中提供了查詢用戶信息的接口HelloWorldController.queryUserInfo
,爲後續擴展此接口的白名單過濾作準備。@RestController public class HelloWorldController { @Value("${server.port}") private int port; /** * key:須要從入參取值的屬性字段,若是是對象則從對象中取值,若是是單個值則直接使用 * returnJson:預設攔截時返回值,是返回對象的Json * * http://localhost:8080/api/queryUserInfo?userId=1001 * http://localhost:8080/api/queryUserInfo?userId=小團團 */ @RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET) public UserInfo queryUserInfo(@RequestParam String userId) { return new UserInfo("蟲蟲:" + userId, 19, "天津市南開區旮旯衚衕100號"); } }
userId
,查詢用戶信息。後續就須要在這裏擴展白名單,只有指定用戶才能夠查詢,其餘用戶不能查詢。@SpringBootApplication @Configuration public class HelloWorldApplication { public static void main(String[] args) { SpringApplication.run(HelloWorldApplication.class, args); } }
SpringBoot
啓動類。須要添加的是一個配置註解@Configuration
,爲了後續能夠讀取白名單配置。通常對於此種場景最簡單的作法就是直接修改代碼
累加if
塊幾乎是實現需求最快也是最慢的方式,快是修改當前內容很快,慢是若是同類的內容幾百個也都須要如此修改擴展和維護會愈來愈慢。
itstack-demo-design-10-01 └── src └── main └── java └── org.itstack.demo.design └── HelloWorldController.java
public class HelloWorldController { public UserInfo queryUserInfo(@RequestParam String userId) { // 作白名單攔截 List<String> userList = new ArrayList<String>(); userList.add("1001"); userList.add("aaaa"); userList.add("ccc"); if (!userList.contains(userId)) { return new UserInfo("1111", "非白名單可訪問用戶攔截!"); } return new UserInfo("蟲蟲:" + userId, 19, "天津市南開區旮旯衚衕100號"); } }
接下來使用外觀器模式來進行代碼優化,也算是一次很小的重構。
此次重構的核心是使用外觀模式也能夠說門面模式,結合SpringBoot
中的自定義starter
中間件開發的方式,統一處理全部須要白名單的地方。
後續接下來的實現中,會涉及的知識;
itstack-demo-design-10-02 └── src ├── main │ ├── java │ │ └── org.itstack.demo.design.door │ │ ├── annotation │ │ │ └── DoDoor.java │ │ ├── config │ │ │ ├── StarterAutoConfigure.java │ │ │ ├── StarterService.java │ │ │ └── StarterServiceProperties.java │ │ └── DoJoinPoint.java │ └── resources │ └── META_INF │ └── spring.factories └── test └── java └── org.itstack.demo.test └── ApiTest.java
門面模式模型結構
public class StarterService { private String userStr; public StarterService(String userStr) { this.userStr = userStr; } public String[] split(String separatorChar) { return StringUtils.split(this.userStr, separatorChar); } }
@ConfigurationProperties("itstack.door") public class StarterServiceProperties { private String userStr; public String getUserStr() { return userStr; } public void setUserStr(String userStr) { this.userStr = userStr; } }
application.yml
中添加 itstack.door
的配置信息。@Configuration @ConditionalOnClass(StarterService.class) @EnableConfigurationProperties(StarterServiceProperties.class) public class StarterAutoConfigure { @Autowired private StarterServiceProperties properties; @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "itstack.door", value = "enabled", havingValue = "true") StarterService starterService() { return new StarterService(properties.getUserStr()); } }
@Configuration
、@ConditionalOnClass
、@EnableConfigurationProperties
,這一部分主要是與SpringBoot的結合使用。@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DoDoor { String key() default ""; String returnJson() default ""; }
@Aspect @Component public class DoJoinPoint { private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class); @Autowired private StarterService starterService; @Pointcut("@annotation(org.itstack.demo.design.door.annotation.DoDoor)") public void aopPoint() { } @Around("aopPoint()") public Object doRouter(ProceedingJoinPoint jp) throws Throwable { //獲取內容 Method method = getMethod(jp); DoDoor door = method.getAnnotation(DoDoor.class); //獲取字段值 String keyValue = getFiledValue(door.key(), jp.getArgs()); logger.info("itstack door handler method:{} value:{}", method.getName(), keyValue); if (null == keyValue || "".equals(keyValue)) return jp.proceed(); //配置內容 String[] split = starterService.split(","); //白名單過濾 for (String str : split) { if (keyValue.equals(str)) { return jp.proceed(); } } //攔截 return returnObject(door, method); } private Method getMethod(JoinPoint jp) throws NoSuchMethodException { Signature sig = jp.getSignature(); MethodSignature methodSignature = (MethodSignature) sig; return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes()); } private Class<? extends Object> getClass(JoinPoint jp) throws NoSuchMethodException { return jp.getTarget().getClass(); } //返回對象 private Object returnObject(DoDoor doGate, Method method) throws IllegalAccessException, InstantiationException { Class<?> returnType = method.getReturnType(); String returnJson = doGate.returnJson(); if ("".equals(returnJson)) { return returnType.newInstance(); } return JSON.parseObject(returnJson, returnType); } //獲取屬性值 private String getFiledValue(String filed, Object[] args) { String filedValue = null; for (Object arg : args) { try { if (null == filedValue || "".equals(filedValue)) { filedValue = BeanUtils.getProperty(arg, filed); } else { break; } } catch (Exception e) { if (args.length == 1) { return args[0].toString(); } } } return filedValue; } }
Object doRouter(ProceedingJoinPoint jp)
,接下來咱們分別介紹。@Pointcut("@annotation(org.itstack.demo.design.door.annotation.DoDoor)")
定義切面,這裏採用的是註解路徑,也就是全部的加入這個註解的方法都會被切面進行管理。
getFiledValue
獲取指定key也就是獲取入參中的某個屬性,這裏主要是獲取用戶ID,經過ID進行攔截校驗。
returnObject
返回攔截後的轉換對象,也就是說當非白名單用戶訪問時則返回一些提示信息。
doRouter
切面核心邏輯,這一部分主要是判斷當前訪問的用戶ID是否白名單用戶,若是是則放行jp.proceed();
,不然返回自定義的攔截提示信息。
這裏的測試咱們會在工程:itstack-demo-design-10-00
中進行操做,經過引入jar包,配置註解的方式進行驗證。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>itstack-demo-design-10-02</artifactId> </dependency>
# 自定義中間件配置 itstack: door: enabled: true userStr: 1001,aaaa,ccc #白名單用戶ID,多個逗號隔開
/** * http://localhost:8080/api/queryUserInfo?userId=1001 * http://localhost:8080/api/queryUserInfo?userId=小團團 */ @DoDoor(key = "userId", returnJson = "{\"code\":\"1111\",\"info\":\"非白名單可訪問用戶攔截!\"}") @RequestMapping(path = "/api/queryUserInfo", method = RequestMethod.GET) public UserInfo queryUserInfo(@RequestParam String userId) { return new UserInfo("蟲蟲:" + userId, 19, "天津市南開區旮旯衚衕100號"); }
@DoDoor
,也就是咱們的外觀模式中間件化實現。. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.1.2.RELEASE) 2020-06-11 23:56:55.451 WARN 65228 --- [ main] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration) 2020-06-11 23:56:55.531 INFO 65228 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2020-06-11 23:56:55.533 INFO 65228 --- [ main] o.i.demo.design.HelloWorldApplication : Started HelloWorldApplication in 1.688 seconds (JVM running for 2.934)
白名單用戶訪問
http://localhost:8080/api/queryUserInfo?userId=1001
{"code":"0000","info":"success","name":"蟲蟲:1001","age":19,"address":"天津市南開區旮旯衚衕100號"}
非白名單用戶訪問
http://localhost:8080/api/queryUserInfo?userId=小團團
{"code":"1111","info":"非白名單可訪問用戶攔截!","name":null,"age":null,"address":null}
userId
換成小團團
,此時返回的信息已是被攔截的信息。而這個攔截信息正式咱們自定義註解中的信息:@DoDoor(key = "userId", returnJson = "{\"code\":\"1111\",\"info\":\"非白名單可訪問用戶攔截!\"}")