热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

input发送a.jax_JavaEE7/JAXRS2.0:具有自定义HTTP标头的简单RESTAPI身份验证和授权...

input发送a.jax在使用已可用的HTTP协议实施Web服务时,REST带来了很多便利。通过仅通过指定的URL触发GET,POST和其他HTTP方法

input发送a.jax

在使用已可用的HTTP协议实施Web服务时,REST带来了很多便利。 通过仅通过指定的URL触发GET,POST和其他HTTP方法,您将确保通过REST服务的响应来完成某些工作。 但是,无论REST给开发人员带来了什么便利,安全性和访问控制的主题都应始终得到解决。 本文将向您展示如何使用HTTP标头和JAX-RS 2.0拦截器来实现基于用户的简单身份验证。

认证者

让我们从一个Authenticator类开始。 该带有以下代码的DemoAuthenticator提供了必要的方法,用于对请求访问REST Web服务的所有用户进行身份验证。 请通读代码,并在此处提供注释以指导理解。

DemoAuthenticator的代码:

package com.developerscrappad.business;import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.security.GeneralSecurityException;
import javax.security.auth.login.LoginException;public final class DemoAuthenticator {private static DemoAuthenticator authenticator = null;// A user storage which stores private final Map usersStorage = new HashMap();// A service key storage which stores private final Map serviceKeysStorage = new HashMap();// An authentication token storage which stores .private final Map authorizationTokensStorage = new HashMap();private DemoAuthenticator() {// The usersStorage pretty much represents a user table in the databaseusersStorage.put( "username1", "passwordForUser1" );usersStorage.put( "username2", "passwordForUser2" );usersStorage.put( "username3", "passwordForUser3" );/*** Service keys are pre-generated by the system and is given to the* authorized client who wants to have access to the REST API. Here,* only username1 and username2 is given the REST service access with* their respective service keys.*/serviceKeysStorage.put( "f80ebc87-ad5c-4b29-9366-5359768df5a1", "username1" );serviceKeysStorage.put( "3b91cab8-926f-49b6-ba00-920bcf934c2a", "username2" );}public static DemoAuthenticator getInstance() {if ( authenticator == null ) {authenticator = new DemoAuthenticator();}return authenticator;}public String login( String serviceKey, String username, String password ) throws LoginException {if ( serviceKeysStorage.containsKey( serviceKey ) ) {String usernameMatch = serviceKeysStorage.get( serviceKey );if ( usernameMatch.equals( username ) && usersStorage.containsKey( username ) ) {String passwordMatch = usersStorage.get( username );if ( passwordMatch.equals( password ) ) {/*** Once all params are matched, the authToken will be* generated and will be stored in the* authorizationTokensStorage. The authToken will be needed* for every REST API invocation and is only valid within* the login session*/String authToken = UUID.randomUUID().toString();authorizationTokensStorage.put( authToken, username );return authToken;}}}throw new LoginException( "Don't Come Here Again!" );}/*** The method that pre-validates if the client which invokes the REST API is* from a authorized and authenticated source.** @param serviceKey The service key* @param authToken The authorization token generated after login* @return TRUE for acceptance and FALSE for denied.*/public boolean isAuthTokenValid( String serviceKey, String authToken ) {if ( isServiceKeyValid( serviceKey ) ) {String usernameMatch1 = serviceKeysStorage.get( serviceKey );if ( authorizationTokensStorage.containsKey( authToken ) ) {String usernameMatch2 = authorizationTokensStorage.get( authToken );if ( usernameMatch1.equals( usernameMatch2 ) ) {return true;}}}return false;}/*** This method checks is the service key is valid** @param serviceKey* @return TRUE if service key matches the pre-generated ones in service key* storage. FALSE for otherwise.*/public boolean isServiceKeyValid( String serviceKey ) {return serviceKeysStorage.containsKey( serviceKey );}public void logout( String serviceKey, String authToken ) throws GeneralSecurityException {if ( serviceKeysStorage.containsKey( serviceKey ) ) {String usernameMatch1 = serviceKeysStorage.get( serviceKey );if ( authorizationTokensStorage.containsKey( authToken ) ) {String usernameMatch2 = authorizationTokensStorage.get( authToken );if ( usernameMatch1.equals( usernameMatch2 ) ) {/*** When a client logs out, the authentication token will be* remove and will be made invalid.*/authorizationTokensStorage.remove( authToken );return;}}}throw new GeneralSecurityException( "Invalid service key and authorization token match." );}
}

通用代码说明:

通常,只有几个重要的项目组成了身份验证器,即: 服务密钥 , 授权令牌 , 用户名密码 。 用户名和密码通常成对使用。

服务密钥

服务密钥对于某些读者而言可能是新的。 在某些公共REST API服务中,系统会生成一个服务密钥(有时也称为API密钥),然后将其发送到允许访问REST服务的用户/客户端(通过电子邮件或其他方式)。 因此,除了仅使用用户名和密码登录REST服务外,系统还将检查服务密钥,是否允许用户/客户端访问REST API。 用户名,密码和服务密钥均已在上面的代码中预定义,仅用于演示目的。

授权令牌

进行身份验证后(通过login()方法),系统将为已身份验证的用户生成授权令牌。 该令牌通过HTTP响应传递回用户/客户端,以后将用于任何REST API调用。 用户/客户端将必须找到一种方法来在整个登录会话中进行存储和使用。 我们稍后再讨论。

必需的HTTP标头名称定义

向前发展,而不是将服务密钥和授权令牌作为HTTP参数(Form或Query)传递给服务器端应用程序,我们将其作为HTTP Header传递。 这是为了让请求在被目标REST方法处理之前先被过滤。 HTTP标头的名称如下:

HTTP标头名称 描述
service_key 使HTTP客户端能够访问REST Web服务的服务密钥。 这是认证和授权HTTP请求的第一层。
auth_token 在用户名/密码认证时生成的令牌,该令牌将用于任何REST Web Service调用(稍后显示的认证方法除外)。

REST API实施

为了方便和进一步减少代码错误,我们将HTTP标头名称作为静态最终变量放入接口中,以供其余类使用。

DemoHTTPHeaderNames.java的代码:

package com.developerscrappad.intf;public interface DemoHTTPHeaderNames {public static final String SERVICE_KEY = "service_key";public static final String AUTH_TOKEN = "auth_token";
}

为了实现身份验证过程和其他演示方法,在DemoBusinessRESTResourceProxy中定义了方法的签名,在适当的HTTP方法,参数中定义了业务签名,并在DemoBusinessRESTResource中定义了业务实现。

DemoBusinessRESTResourceProxy.java的代码:

package com.developerscrappad.intf;import java.io.Serializable;
import javax.ejb.Local;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;@Local
@Path( "demo-business-resource" )
public interface DemoBusinessRESTResourceProxy extends Serializable {@POST@Path( "login" )@Produces( MediaType.APPLICATION_JSON )public Response login(@Context HttpHeaders httpHeaders,@FormParam( "username" ) String username,@FormParam( "password" ) String password );@GET@Path( "demo-get-method" )@Produces( MediaType.APPLICATION_JSON )public Response demoGetMethod();@POST@Path( "demo-post-method" )@Produces( MediaType.APPLICATION_JSON )public Response demoPostMethod();@POST@Path( "logout" )public Response logout(@Context HttpHeaders httpHeaders);
}

DemoBusinessRESTResource.java的代码:

package com.developerscrappad.business;import com.developerscrappad.intf.DemoBusinessRESTResourceProxy;
import com.developerscrappad.intf.DemoHTTPHeaderNames;
import java.security.GeneralSecurityException;
import javax.ejb.Stateless;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.security.auth.login.LoginException;
import javax.ws.rs.FormParam;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;@Stateless( name = "DemoBusinessRESTResource", mappedName = "ejb/DemoBusinessRESTResource" )
public class DemoBusinessRESTResource implements DemoBusinessRESTResourceProxy {private static final long serialVersionUID = -6663599014192066936L;@Overridepublic Response login(@Context HttpHeaders httpHeaders,@FormParam( "username" ) String username,@FormParam( "password" ) String password ) {DemoAuthenticator demoAuthenticator = DemoAuthenticator.getInstance();String serviceKey = httpHeaders.getHeaderString( DemoHTTPHeaderNames.SERVICE_KEY );try {String authToken = demoAuthenticator.login( serviceKey, username, password );JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();jsonObjBuilder.add( "auth_token", authToken );JsonObject jsonObj = jsonObjBuilder.build();return getNoCacheResponseBuilder( Response.Status.OK ).entity( jsonObj.toString() ).build();} catch ( final LoginException ex ) {JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();jsonObjBuilder.add( "message", "Problem matching service key, username and password" );JsonObject jsonObj = jsonObjBuilder.build();return getNoCacheResponseBuilder( Response.Status.UNAUTHORIZED ).entity( jsonObj.toString() ).build();}}@Overridepublic Response demoGetMethod() {JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();jsonObjBuilder.add( "message", "Executed demoGetMethod" );JsonObject jsonObj = jsonObjBuilder.build();return getNoCacheResponseBuilder( Response.Status.OK ).entity( jsonObj.toString() ).build();}@Overridepublic Response demoPostMethod() {JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();jsonObjBuilder.add( "message", "Executed demoPostMethod" );JsonObject jsonObj = jsonObjBuilder.build();return getNoCacheResponseBuilder( Response.Status.ACCEPTED ).entity( jsonObj.toString() ).build();}@Overridepublic Response logout(@Context HttpHeaders httpHeaders ) {try {DemoAuthenticator demoAuthenticator = DemoAuthenticator.getInstance();String serviceKey = httpHeaders.getHeaderString( DemoHTTPHeaderNames.SERVICE_KEY );String authToken = httpHeaders.getHeaderString( DemoHTTPHeaderNames.AUTH_TOKEN );demoAuthenticator.logout( serviceKey, authToken );return getNoCacheResponseBuilder( Response.Status.NO_CONTENT ).build();} catch ( final GeneralSecurityException ex ) {return getNoCacheResponseBuilder( Response.Status.INTERNAL_SERVER_ERROR ).build();}}private Response.ResponseBuilder getNoCacheResponseBuilder( Response.Status status ) {CacheControl cc = new CacheControl();cc.setNoCache( true );cc.setMaxAge( -1 );cc.setMustRevalidate( true );return Response.status( status ).cacheControl( cc );}
}

login()方法用于验证用户名,密码以及正确的服务密钥。 在login()之后 ,将生成授权令牌并将其返回给客户端。 客户端稍后将不得不将其用于任何其他方法调用。 demoGetMethod()demoPostMethod()只是伪方法,它们出于演示目的返回JSON消息,但有一个特殊条件,即必须存在有效​​的授权令牌。 logout()方法用于使用户退出REST服务; 用户由“ auth_token ”标识。

服务密钥和授权令牌将通过以下方式提供给REST服务方法:

@Context HttpHeaders httpHeaders

httpHeaders是javax.ws.rs.core.HttpHeaders的实例,是一个对象,其中包含标题名称和值,供以后使用该应用程序使用。 但是,为了使REST服务接受HTTP标头,首先需要通过REST请求拦截器和响应拦截器来完成某些操作。

通过JAX-RS 2.0拦截器使用HTTP标头进行身份验证

由于某些安全限制,只是不要希望可以使用任何REST客户端传递任何HTTP标头,并希望REST服务接受它。 就是那样行不通。

为了使特定的标头在REST服务中被接受,我们必须非常明确地在响应过滤器拦截器中定义HTTP标头的接受。

DemoRESTResponseFilter.java的代码:

package com.developerscrappad.interceptors;import com.developerscrappad.intf.DemoHTTPHeaderNames;
import java.io.IOException;
import java.util.logging.Logger;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.ext.Provider;@Provider
@PreMatching
public class DemoRESTResponseFilter implements ContainerResponseFilter {private final static Logger log = Logger.getLogger( DemoRESTResponseFilter.class.getName() );@Overridepublic void filter( ContainerRequestContext requestCtx, ContainerResponseContext responseCtx ) throws IOException {log.info( "Filtering REST Response" );responseCtx.getHeaders().add( "Access-Control-Allow-Origin", "*" ); // You may further limit certain client IPs with Access-Control-Allow-Origin instead of '*'responseCtx.getHeaders().add( "Access-Control-Allow-Credentials", "true" );responseCtx.getHeaders().add( "Access-Control-Allow-Methods", "GET, POST, DELETE, PUT" );responseCtx.getHeaders().add( "Access-Control-Allow-Headers", DemoHTTPHeaderNames.SERVICE_KEY + ", " + DemoHTTPHeaderNames.AUTH_TOKEN );}
}

DemoRESTResponseFilter是一个实现ContainerResponseFilter的JAX-RS 2.0拦截器。 不要忘记同时使用@Provide和@PreMatching对其进行注释。 为了允许接受某些特定的自定义HTTP标头,标头名称“ Access-Control-Allow-Headers ”后跟带有“,”的标头值,因为分隔符必须作为自定义标头值的一部分添加。 这是通知浏览器或REST客户端允许的自定义标头的方法。 其余的标头用于CORS,您可以在我们的文章Java EE 7 / JAX-RS 2.0 – REST上的CORS(如何使REST API从其他域访问)中 。

接下来,要验证和验证服务密钥和授权令牌,我们需要从HTTP标头中将其提取出来,并使用请求过滤器拦截器对其进行预处理。

DemoRESTRequestFilter的代码:

package com.developerscrappad.interceptors;import com.developerscrappad.business.DemoAuthenticator;
import com.developerscrappad.intf.DemoHTTPHeaderNames;
import java.io.IOException;
import java.util.logging.Logger;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;@Provider
@PreMatching
public class DemoRESTRequestFilter implements ContainerRequestFilter {private final static Logger log = Logger.getLogger( DemoRESTRequestFilter.class.getName() );@Overridepublic void filter( ContainerRequestContext requestCtx ) throws IOException {String path = requestCtx.getUriInfo().getPath();log.info( "Filtering request path: " + path );// IMPORTANT!!! First, Acknowledge any pre-flight test from browsers for this case before validating the headers (CORS stuff)if ( requestCtx.getRequest().getMethod().equals( "OPTIONS" ) ) {requestCtx.abortWith( Response.status( Response.Status.OK ).build() );return;}// Then check is the service key exists and is valid.DemoAuthenticator demoAuthenticator = DemoAuthenticator.getInstance();String serviceKey = requestCtx.getHeaderString( DemoHTTPHeaderNames.SERVICE_KEY );if ( !demoAuthenticator.isServiceKeyValid( serviceKey ) ) {// Kick anyone without a valid service keyrequestCtx.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build() );return;}// For any pther methods besides login, the authToken must be verifiedif ( !path.startsWith( "/demo-business-resource/login/" ) ) {String authToken = requestCtx.getHeaderString( DemoHTTPHeaderNames.AUTH_TOKEN );// if it isn't valid, just kick them out.if ( !demoAuthenticator.isAuthTokenValid( serviceKey, authToken ) ) {requestCtx.abortWith( Response.status( Response.Status.UNAUTHORIZED ).build() );}}}
}

要获取标头值,我们调用ContainerRequestContext对象实例的getHeaderString()方法,例如:

String serviceKey = requestCtx.getHeaderString( "service_key" );

DemoRESTRequestFilter中的其余代码在验证和验证服务密钥和授权令牌方面非常简单。

REST服务部署

不要忘记定义用于启用REST服务的web.xml。

web.xml的代码:

javax.ws.rs.core.Application1javax.ws.rs.core.Application/rest-api/*

对于本演示,我将编译后的代码打包到一个名为RESTSecurityWithHTTPHeaderDemo.war的war文件中。 我选择在开发人员域crappad.com(此博客的域)上的Glassfish 4.0上进行部署。 如果您正在阅读本教程中的所有内容,则可以选择自己的其他域。 REST API URL将采用以下格式:

http://:/RESTSecurityWithHTTPHeaderDemo/rest-api/path/method-path/

无论如何,我正在使用的测试客户端的URL摘要是:

方法 REST URL HTTP方法
DemoBusinessRESTResourceProxy.login() http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/login/ 开机自检
DemoBusinessRESTResourceProxy。
demoGetMethod()
http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/demo-get-method/ 得到
DemoBusinessRESTResourceProxy。
demoPostMethod()
http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/demo-post-method/ 开机自检
DemoBusinessRESTResourceProxy.logout() http://developerscrappad.com:8080/RESTSecurityWithHTTPHeaderDemo/rest-api/demo-business-resource/logout/ 开机自检

REST客户端

总而言之,这是我编写的用于测试REST API的REST客户端。 REST客户端只是一个HTML文件(特别是HTML5,它支持Web存储),该文件利用jQuery进行REST API调用。 REST客户端的作用如下:

  1. 首先,REST客户端将在没有服务密钥和授权令牌的情况下进行REST API调用。 呼叫将被拒绝,HTTP状态为401(未授权)
  2. 接下来,它将使用“ username2”的特定服务密钥(目前在Authenticator.java中进行硬编码)执行登录。 收到授权令牌后,它将被存储在sessionStorage中以备将来使用。
  3. 然后,它将调用虚拟的get和post方法。
  4. 之后,它将执行注销
  5. 用户注销后,客户端将执行对虚拟get and post方法的调用,但是由于授权令牌的到期,访问将被HTTP状态401拒绝。

rest-auth-test.html的代码:


结果

rest-auth-test.html不需要与war文件打包在一起,这是为了将调用客户端脚本与服务器端应用程序分开以模拟跨域请求。 要运行rest-auth-test.html,您需要做的就是从Web浏览器执行它。 对我来说,我已经通过Firefox使用Firebug插件完成了此操作,结果如下:

rest-auth-test.html的结果

rest-auth-test.html的结果

效果很好。 第一个和最后一个请求将被拒绝为401(未经授权)HTTP状态,因为它是在身份验证之前和注销之后执行的(无效auth_token )。

最后的话

在JAX-RS 2.0应用程序中处理自定义HTTP标头时,只需记住将自定义HTTP标头名称作为响应过滤器中“ Access-Control-Allow-Header ”的一部分包含在内即可。

Access-Control-Allow-Headers: custom_header_name1, custom_header_name2

之后,可以通过REST上下文在javax.ws.rs.core.HttpHeaders的帮助下,在REST Web服务方法中轻松获得HTTP标头。 不要忘记对CORS的限制和影响,在REST请求和响应拦截器中都应注意这一点。

感谢您的阅读,希望本文对您有所帮助。

相关文章:

  • Java EE 7 / JAX-RS 2.0 – REST上的CORS(如何使REST API从其他域访问)
  • http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
  • http://www.html5rocks.com/zh-CN/tutorials/cors/
  • http://www.w3.org/TR/cors/
  • https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS

翻译自: https://www.javacodegeeks.com/2014/10/java-ee-7-jax-rs-2-0-simple-rest-api-authentication-authorization-with-custom-http-header.html

input发送a.jax



推荐阅读
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
author-avatar
浪漫的白狼族
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有