写在前面:本文乃标题党,不是月经贴,侧重于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指明要装配的类型名称来解决这个问题。