寫在前面:本文乃標題黨,不是月經貼,側重於Web開發差別,或細節或概述,如有不對之處,還請各位讀者本着友好互助的心態批評指正。因爲博客園中.Neter較多(我的感受),所以本文也能夠做爲.Neter到Java開發的快速入門。html
總述前端
在.Net開發中,微軟官方框架類能夠很好的解決的大部分問題,開發人員能夠問心無愧的在一畝三分地騰挪躲閃出花來;偶有一些優(zhao)秀(chao)的開源庫,各庫的關注點也基本不會重樣;因此.Neter只要循序漸進便可。而Java喜歡定義各類規範,各路大神各自實現,所以一個概念經常會有不少的第三方庫,雖然有Spring這種殺手級框架,不過基於IOC和AOP的設定,Spring家族也變得異常龐大,在編碼時須要引入大量的annotation來織入邏輯;雖然貌似最大程度的解耦了各組件,但致使代碼的可讀性和可調試性很是很差,碎片化很是嚴重。不過也由於如此,Java社區成爲設計思想的孕育地,並經常出現一些讓人擊節的設計模式。其中的概念傳播到隔壁.Net圈,圈內小白每每一臉懵逼,而少數大佬無論不顧拿來套用,每每是用錯了,或者讓人不知因此。java
籠統來講,.Net框架隱藏細節,簡便清晰,套路單一,但常陷入知其然不知其因此然的懵逼境地;Java&Spring註解隱藏細節,概念繁多,沒有方向感或有被繞暈的風險,但一旦破位而出,則縱橫捭闔天地之大可任意施展至其它平臺。不過二者差別隨着.Net的開源以肉眼不可見的速度緩慢消失,特別是最近幾年,.Net在語法層面已經超越了Java良多,Java雖然一時半會抹不開面子,但也一直在改進。到的本文撰寫時分,借用不知名網友語:「C#語法已經達到Java20,用戶量撐死Java7,生態Java1.4」。react
二者競爭主要集中在Web開發領域。目前在該領域,Spring Boot已基本成爲事實上Java平臺的「官方框架」,我想大部分開發人員並不會在乎背後的實現細節,從這個方面來說,兩個平臺的開發模式有必定程度的類似。c++
數據持久層web
爲啥這節標題不是ORM呢?畢竟ORM如今是業界標準,很難想象這個時代還須要手寫SQL,還須要手動操做JDBC/ADO;若是你打算這麼幹,必定會被年輕一輩打心眼裏鄙視:)spring
Java數據庫
ORM:十多年前,Hibernate就開始興起,它提供了半對象化的HQL和徹底的面向對象QBC。以後也出現了其它一些ORM好比TopLink。編程
JPA:JDK5引入,是SUN公司爲了統一目前衆多ORM而提出的ORM規範(又犯了定義規範的癮)。這個規範出來後,不少ORM表示支持,但之前的還得維護啊,因此像Hibernate就另外建了一個分支叫Hibernate JPA。網友benjaminlee1所言:「JPA的出現只是用於規範現有的ORM技術,它不能取代現有的Hibernate等ORM框架,相反,採用JPA開發時,咱們仍將使用這些ORM框架,只是此時開發出來的應用不在依賴於某個持久化提供商。應用能夠在不修改代碼的狀況下載任何JPA環境下運行,真正作到低耦合,可擴展的程序設計。相似於JDBC,在JDBC出現之前,咱們的程序針對特性的數據庫API進行編程,可是如今咱們只須要針對JDBC API編程,這樣可以在不改變代碼的狀況下就能換成其餘的數據庫。」後端
Spring Data JPA:有了JPA,咱們就能夠不在乎使用哪一個ORM了,可是Spring Data JPA更進一步(爲Spring家族添磚加瓦),按約定的方式自動給咱們生成持久化代碼,固然它底層仍是要依賴各路ORM的。相關資料:使用 Spring Data JPA 簡化 JPA 開發
Mybatis:隨着時間的流逝,Hibernate曾經帶來的榮耀已經被臃腫醜陋的配置文件,沒法優化的查詢語句淹沒。不少人開始懷念可一手掌控數據操做的時代,因而Mybatis出現了。Mybatis不是一個完整的ORM,它只完成了數據庫返回結果到對象的映射,而存取邏輯仍爲SQL,寫在Mapper文件中,它提供的語法在必定程度上簡化了SQL的編寫,最後Mybatis將SQL邏輯映射到接口方法上(在Mapper文件中指定<mapper namespace="xxx">,其中xxx爲映射的DAO接口)。針對每一個表寫通用增刪改查的Mapper SQL既枯燥又易出錯,因此出現了Mybatis-Generator之類的代碼生成工具,它能基於數據表生成實體類、基本CRUD的Mapper文件、對應的DAOInterface。
Mybatis-Plus:在Mybatis的基礎上,提供了諸如分頁、複雜條件查詢等功能,基礎CRUD操做不須要額外寫SQL Mapper了,只要DAO接口繼承BaseMapper接口便可。固然爲了方便,它也提供了本身的代碼生成器。
.NET
ORM:主流Entity Framework,除開ORM功能外,它還提供了Code first、DB first、T4代碼生成等特性。性能上與Hibernate一個等級,但使用便捷性和功能全面性較好,更別說還有linq的加持。
認證&受權&鑑權
認證是檢測用戶/請求是否合法,受權是賦予合法用戶相應權限,鑑權是鑑別用戶是否有請求某項資源的權限(認證和受權通常是同時完成)。咱們以web爲例。
C#/Asp.net mvc
提供了兩個Filter:IAuthenticationFilter 和 AuthorizeAttribute,前者用於認證受權,後者用於鑑權。
認證成功後,將user賦給filterContext.Principal(第17行),filterContext.Principal接收一個IPrincipal接口對象,該接口有個 bool IsInRole(string role) 方法,用於後續的鑑權過程。
注意第27行,咱們將擁有該資源的全部權限賦給Roles,以後AuthorizeAttribute會循環Roles,依次調用當前用戶(上述的filterContext.Principal)的IsInRole方法,若其中一個返回true則代表用戶有訪問當前資源的權限。
Java/Spring Security
也提供了兩個類,一個Filter和一個Interceptor:AuthenticationProcessingFilter用於用戶認證受權,AbstractSecurityInterceptor用於鑑權。Spring Security基於它們又封裝了幾個類,主要幾個:WebSecurityConfigurerAdapter、FilterInvocationSecurityMetadataSource、AccessDecisionManager、UserDetailsService。另外還有各種註解如@EnableGlobalMethodSecurity等。(如下代碼含有一點jwt邏輯)
WebSecurityConfigurerAdapter:
主要關注兩個方法configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder)和configure(HttpSecurity httpSecurity)。configureAuthentication主要用於設置UserDetailsService,加載用戶數據須要用到;configure用於設置資源的安全級別以及全局安全策略等。第41行withObjectPostProcessor,用於設置FilterInvocationSecurityMetadataSource和AccessDecisionManager,它們兩個用於鑑權,下面會講到。
UserDetailService(此處從數據庫獲取):
注意loadUserByUsername須要的參數名username是約定好的,在UsernamePasswordAuthenticationFilter中定義,value是從HttpServletRequest中獲取。
FilterInvocationSecurityMetadataSource(用於獲取當前請求資源所需的權限):
AccessDecisionManager:
上述第19行和第22行分別爲UserDetailService處取到的用戶擁有的權限和FilterInvocationSecurityMetadataSource取到的訪問資源須要的權限,二者對比後即得出用戶是否有訪問該資源的權限。具體來講,鑑權的整個流程是:訪問資源時,會經過AbstractSecurityInterceptor攔截器攔截,其中會調用FilterInvocationSecurityMetadataSource的方法來獲取被攔截url所需的所有權限,再調用受權管理器AccessDecisionManager,這個受權管理器會經過spring的全局緩存SecurityContextHolder獲取用戶的權限信息,還會獲取被攔截的url和被攔截url所需的所有權限,而後根據所配的策略(有:一票決定,一票否認,少數服從多數等),若是權限足夠,則返回,權限不夠則報錯並調用權限不足頁面。
題外話,登陸認證能夠認爲並不是認證受權的一部分,而是將身份令牌頒發給客戶端的過程,以後客戶端拿着身份令牌過來請求資源的時候才進入上面的認證受權環節。不過Spring Secuity中涉及到的認證方法能夠簡化登陸認證的代碼編寫:
1 final Authentication authentication = authenticationManager.authenticate( 2 new UsernamePasswordAuthenticationToken(username, password) 3 ); 4 5 SecurityContextHolder.getContext().setAuthentication(authentication);
其中authenticationManager由框架提供,框架會根據上面說到的configureAuthentication提供合適的AuthenticationManager實例,認證失敗時拋出異常,不然返回Authenticatio令牌併爲用戶相關的SecurityContext設置令牌。須要注意的是,SecurityContext是存放在ThreadLocal中的,並且在每次權限鑑定的時候都是從ThreadLocal中獲取SecurityContext中對應的Authentication所擁有的權限,而且不一樣的request是不一樣的線程,爲何每次均可以從ThreadLocal中獲取到當前用戶對應的SecurityContext呢?在Web應用中這是經過SecurityContextPersistentFilter實現的,默認狀況下其會在每次請求開始的時候從session中獲取SecurityContext,而後把它設置給SecurityContextHolder,在請求結束後又會將該SecurityContext保存在session中,而且在SecurityContextHolder中清除。當用戶第一次訪問系統的時候,該用戶沒有SecurityContext,待登陸成功後,以後的每次請求就能夠從session中獲取到該SecurityContext並把它賦予給SecurityContextHolder了,因爲SecurityContextHolder已經持有認證過的Authentication對象了,因此下次訪問的時候也就再也不須要進行登陸認證了。
而上文說到的jwt,倒是cookie/session一輩子黑。它的機制是http請求頭部的令牌認證。咱們能夠藉助它在session過時後也能正常的認證受權,而不須要用戶從新登陸。
1 public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { 2 3 private final Log logger = LogFactory.getLog(this.getClass()); 4 5 @Autowired 6 private UserDetailsService userDetailsService; 7 8 @Autowired 9 private JwtTokenUtil jwtTokenUtil; 10 11 @Value("${jwt.header}") 12 private String tokenHeader; 13 14 @Override 15 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { 16 final String requestHeader = request.getHeader(this.tokenHeader); 17 18 String username = null; 19 String authToken = null; 20 if (requestHeader != null && requestHeader.startsWith("Bearer ")) { 21 authToken = requestHeader.substring(7); 22 try { 23 username = jwtTokenUtil.getUsernameFromToken(authToken); 24 } catch (IllegalArgumentException e) { 25 logger.error("an error occured during getting username from token", e); 26 } catch (Exception e1) { 27 logger.error(e1.getMessage()); 28 } 29 } 30 31 if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { 32 33 // It is not compelling necessary to load the use details from the database. You could also store the information 34 // in the token and read it from it. It's up to you ;) 35 UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); 36 37 // For simple validation it is completely sufficient to just check the token integrity. You don't have to call 38 // the database compellingly. Again it's up to you ;) 39 if (jwtTokenUtil.validateToken(authToken, userDetails)) { 40 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); 41 authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 42 logger.info("authenticated user " + username + ", setting security context"); 43 SecurityContextHolder.getContext().setAuthentication(authentication); 44 } 45 } 46 47 chain.doFilter(request, response); 48 } 49 }
固然也能夠不借助Spring Security,單純的實現jwt,那樣就須要本身實現認證和受權過程了。
在Spring Boot 1.5中,咱們能夠依靠重寫WebMvcConfigurerAdapter的方法來添加自定義攔截器,消息轉換器等;Spring Boot 2.0 後,該類被標記爲@Deprecated。方式改成實現WebMvcConfigurer接口。在Java中,攔截器(Interceptor)和Filter有所不一樣,前者更貼近AOP概念,然後者只有前置執行。
對比:Asp.net mvc相對清晰,可控性高;Spring Security隱藏了邏輯順序,涉及類較多,關鍵步驟散落各處,層級不清,容易讓新手困惑。還有其它的Java認證框架如Shiro,也很流行,此處按過不表。
非阻塞編程
在web開發領域,傳統的實現異步的方式都比較複雜,好比 Java 中的 NIO,須要瞭解 channel,selector,buffer 這些概念,或者使用 netty 這樣的網絡框架。c/c++ 進行異步/非阻塞編程,則須要理解 select,poll,epoll 等概念,開發與維護門檻較高。並且這部分的開發與業務無關,那麼封裝底層機制,推出一套開發框架的必要性就顯而易見了。概念上,.Net習慣稱爲異步編程(Asynchronous programming),Java稱之爲響應式編程(Reactive Programming)。
.Net/Asynchronous programming
.Net4.5(C#5.0,2012年)開始,引入async/await關鍵字,在語法層面上將異步編程變得如同同步處理般清晰流暢,並在短時內即推出了支持主流數據庫的異步組件。從接收請求到數據操做,開發人員能很方便的將傳統的同步代碼遷移爲異步模式。以後幾年,如Python(3.5)、Nodejs(7.6)等紛紛效仿,成爲事實上的語法標準。
Java/Reactive Programming
咱們得先從Stream提及,Stream自己和響應式編程不要緊,但以後的Reactive Streams在某種程度上繼承了它的某些概念。Java 8 引入了Stream,方便集合的聚合操做,它也支持lambda表達式做爲操做參數,能夠將其看作Iterator。相似的語法在C#中也有,只是C#提供的是無侵入方式,集合自己就支持,更不用說Stream這個概念多麼讓人混淆。相關資料:Java 8 中的 Streams API 詳解
Stream的映射操做有map和flatmap,相似C#中Select和SelectMany的區別。
Reactive Streams
歷程
響應式流從2013年開始,做爲提供非阻塞背壓的異步流處理標準的倡議。
在2015年,出版了一個用於處理響應式流的規範和Java API。 Java API 中的響應式流由四個接口組成:Publisher<T>,Subscriber<T>,Subscription和Processor<T,R>。
JDK 9在java.util.concurrent包中提供了與響應式流兼容的API,它在java.base模塊中。 API由兩個類組成:Flow和SubmissionPublisher<T>。Flow類封裝了響應式流Java API。 由響應式流Java API指定的四個接口做爲嵌套靜態接口包含在Flow類中:Flow.Processor<T,R>,Flow.Publisher<T>,Flow.Subscriber<T>和Flow.Subscription。
Reactor是Reactive Streams的一個實現庫。鄙人認爲,Reactive Streams針對的場景是無邊界數據的enumerate處理,無邊界即數據/需求會被不停的生產出來,沒法在事前確立循環規則(如循環次數);另外一方面,它又提供了單次處理的處理規則(如每次處理多少條數據/需求)。相關資料:聊聊reactive streams的backpressure。
Spring5.0開始提供響應式 Web 編程支持,框架爲Spring WebFlux,區別於傳統的Spring MVC同步模式。Spring WebFlux基於Reactor,其語法相似JS的Promise,並有一些靈活有用的特性如延時處理返回。具體用法可參看:(5)Spring WebFlux快速上手——響應式Spring的道法術器 。就文中所說,目前(本文書寫時間)Spring Data對MongoDB、Redis、Apache Cassandra和CouchDB數據庫提供了響應式數據訪問支持,意即便用其它數據庫的項目尚沒法真正作到異步響應(最關鍵的IO環節仍爲線程同步)。
在Java 7推出異步I/O庫,以及Servlet3.1增長了對異步I/O的支持以後,Tomcat等Servlet容器也隨後開始支持異步I/O,而後Spring WebMVC也增長了對Reactor庫的支持,在Spring MVC3.2版本已經支持異步模式。至於Spring爲什麼又推出一套WebFlux就不得而知了。
對比:非阻塞編程方面,Java推動速度慢,目前的程度尚不能與幾年前的.Net相比,語法上,.Net的async/await相比類Promise語法更簡潔,Spring WebFlux在請求響應處理上有一些亮點。
其它
幾個月前(美國當地時間9月25日),Oracle官方宣佈 Java 11 (18.9 LTS) 正式發佈。Java目前的版本發佈策略是半年一版,每三年發佈一個長期支持版本,Java 11 是自 Java 8 後的首個長期支持版本。目測Java 8 開始的不少特性都參考了C#,好比異步編程、Lambda、Stream、var等等,這是一個好的現象,相互學習才能進步嘛。
.Net的MVC模板引擎爲默認爲razor,它是專注且多情的,依賴於後端代碼。而Java平臺經常使用的有不少,如FreeMarker,它獨立於任何框架,能夠將它看做複雜版的string.format,用在mvc中就是string.format(v,m),輸出就是v模板綁定m數據後的html;還有Spring Boot自帶的thymeleaf,它因爲使用了標籤屬性作爲語法,模版頁面直接用瀏覽器渲染,使得前端和後端能夠並行開發,竊覺得這是兼顧便捷與運行效率的最佳先後端分離開發利器。
Java8開始,能夠在Interface中定義靜態方法和默認方法。在接口中,增長default方法, 是爲了既有的成千上萬的Java類庫的類增長新的功能, 且沒必要對這些類從新進行設計(相似於C#的擴展方法,但靈活度低,耦合度高)。
Java8的Optional有點相似於.NET的xxxx?,都是簡化是否爲空判斷。
Java ThreadLocal相似於.NET ThreadStaticAttribute,都是提供線程內的局部變量[副本],這種變量在線程的生命週期內起做用。
Java
Fork/Join:Java 7 引入,方便咱們將任務拆成子任務並行執行[並彙總結果後返回]。
靜態引入:import static。導入靜態方法。
使用匿名內部類方式初始化對象:
ArrayList<Student> stuList = new ArrayList<Student>() { { for (int i = 0; i < 100; i++) { add(new Student("student" + i, random.nextInt(50) + 50)); } } };
Java 9 開始支持Http/2,關於Http/2的特色以及它相較於1.0、1.1版本的改進可自行百度,總之效率上提高很大。
Spring3.0引入了@Configuration。Instead of using the XML files, we can use plain Java classes to annotate the configurations by using the @Configuration annotation. If you annotate a class with @Configuration annotation, it indicates that the class is used for defining the beans using the @Bean annotation. This is very much similar to the <bean/> element in the spring XML configurations.固然,xml配置和註解配置能夠混用。咱們若要複用它處定義的配置類,可以使用@Import註解,它的做用相似於將多個XML配置文件導入到單個文件。
Spring中的後置處理器BeanPostProcessor,用於在Spring容器中完成bean實例化、配置以及其餘初始化方法先後要添加一些本身邏輯處理。Spring Security中還有個ObjectPostProcessor,能夠用來修改或者替代經過Java方式配置建立的對象實例,可用在沒法預先設置值如須要根據不一樣條件設置不一樣值的場景。
@Value("#{}")與@Value("${}"):前者用於賦予bean字段的值,後者用於賦予屬性文件中定義的屬性值。
Servlet3.0開始,@WebServlet, @WebFilter, and @WebListener can be enabled by using @ServletComponentScan,不用在web.xml裏面配置了。這無關Spring,而是Servlet容器特性。
@Autowired是根據類型進行自動裝配的。若是當Spring上下文中存在不止一個UserDao類型的bean時,就會拋出BeanCreationException異常。咱們可使用@Qualifier指明要裝配的類型名稱來解決這個問題。