@EnableResourceServer资源服务注解源码分析

学习参考

Spring Security框架配置运行流程完整分析 - 【必看】

Security OAuth2 授权 & JWT

OAuth2授权流程和源码解析 - 自己总结 - 有道笔记~

DefaultTokenServices源码

@EnableAuthorizationServer授权服务注解源码分析

@EnableResourceServer资源服务注解源码分析

Spring Security整合Gitee第三方登录

第三方登录源码梳理 - 自己总结 - 有道笔记~

Security OAuth2 SSO单点登录(一)

Security OAuth2 SSO单点登录源码剖析 - 自己总结 - 有道笔记~

(八)SpringCloud+Security+Oauth2–token增强个性化和格式化输出

十七.SpringCloud+Security+Oauth2实现微服务授权 -非对称加密生成JWT令牌

十四.SpringCloud+Security+Oauth2实现微服务授权 - 网关统一鉴权

十三.SpringCloud+Security+Oauth2实现微服务授权 - 服务之间授权

@EnableResourceServer

概要

@EnableResourceServer用于开启资源服务器,它使用@Import注解引入了ResourceServerConfiguration

  • ResourceServerConfiguration
    • 资源服务器配置类,继承自继承自WebSecurityConfigurerAdapter,order为3,大于AuthorizationServerSecurityConfiguration的order,其中WebSecurityConfigurerAdapter的order默认为100;由此可见它们的顺序:AuthorizationServerSecurityConfiguration、ResourceServerConfiguration、WebSecurityConfigurerAdapter
    • 自动注入容器中定义的TokenStore令牌存储组件、所有的tokenServices资源服务器令牌服务、AuthorizationServerEndpointsConfiguration授权服务器端点配置类、所有的ResourceServerConfigurer资源服务器配置器
    • 在配置HttpSecurity时,会创建1个ResourceServerSecurityConfigurer资源服务器安全配置器,它用于添加到HttpSecurity中继续配置HttpSecurity,会往HttpSecurity中添加OAuth2AuthenticationProcessingFilter这一重要的过滤器,该OAuth2AuthenticationProcessingFilter用于使用令牌加载OAuth2Authenticaiton认证对象,绑定到当前线程中。
    • 在配置HttpSecurity时,也会对把自动注入的TokenStore、tokenServices设置到ResourceServerSecurityConfigurer资源服务器安全配置器中,并且会使用ResourceServerConfigurer资源服务器配置器对该ResourceServerSecurityConfigurer资源服务器安全配置器进行配置
    • 可定义ResourceServerConfigurer类型的bean来配置ResourceServerSecurityConfigurer和HttpSecurity,它们的配置过程都在ResourceServerConfiguration的configure(HttpSecurity)方法中

ResourceServerConfiguration

资源服务器配置类,继承自继承自WebSecurityConfigurerAdapter,order为3

属性定义

/* 尝试注入容器中的 TokenStore 令牌存储器 */
@Autowired(required = false)
private TokenStore tokenStore;

/* 尝试注入容器中的 AuthenticationEventPublisher 认证事件发布器 */
@Autowired(required = false)
private AuthenticationEventPublisher eventPublisher;

/* 尝试注入容器中的 ResourceServerTokenServices 资源服务器令牌服务 */
@Autowired(required = false)
private Map<String, ResourceServerTokenServices> tokenServices;

/* 注入 ApplicationContext 应用上下文 */
@Autowired
private ApplicationContext context;

/* 注入容器中的所有定义的 ResourceServerConfigurer 资源服务器配置器 */
private List<ResourceServerConfigurer> configurers = Collections.emptyList();

/* 尝试注入容器中的 AuthorizationServerEndpointsConfiguration 授权服务器都单点配置类
   因为: 授权服务器经常也会开启资源服务器的功能, 如果未开启, 这里就会是null	 */
@Autowired(required = false)
private AuthorizationServerEndpointsConfiguration endpoints;

configure(HttpSecurity)

其实就是在配置ResourceServerSecurityConfigurer 资源服务器安全配置器,而 资源服务器安全配置器 则是为了给HttpSecurity添加1个OAuth2AuthenticationProcessingFilter过滤器

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    // 创建1个【ResourceServerSecurityConfigurer 资源服务器安全配置器】, 用来继续配置HttpSecurity
    ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
    
    // 解析出1个 资源服务器令牌服务, 
    // 从自动注入的tokenServices中获取, 如果没有, 则返回null; 如果只有1个, 则选择这个; 如果有多个, 须使用@Primary来标注使用哪个
    ResourceServerTokenServices services = resolveTokenServices();
    
    // 如果解析出来了 ResourceServerTokenServices 资源服务器令牌服务, 则设置给 资源服务器安全配置器 的 resourceTokenServices属性
    if (services != null) {
        
        resources.tokenServices(services);
        
    }
    else {
        
        // 此时, 容器中未定义任何 ResourceServerTokenServices 资源服务器令牌服务
        
        if (tokenStore != null) {
            // 如果令牌存储器有定义 TokenStore令牌存储器, 
            // 则将此TokenStore设置给 【ResourceServerSecurityConfigurer 资源服务器安全配置器】的tokenStore属性
            resources.tokenStore(tokenStore);
        }
        else if (endpoints != null) {
            // 此时容器中未定义任何  TokenStore令牌存储器, 则使用AuthorizationServerEndpointsConfiguration的endpoints的tokenStore属性
            // 设置给【ResourceServerSecurityConfigurer 资源服务器安全配置器】的tokenStore属性
            resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore());
        }
    }
    
    // 如果容器中有定义 AuthenticationEventPublisher 认证事件发布器, 则将它设置给【ResourceServerSecurityConfigurer 资源服务器安全配置器】
    if (eventPublisher != null) {
        resources.eventPublisher(eventPublisher);
    }
    
    // 使用容器中所有定义的 *ResourceServerConfigurer* 资源服务器配置器 来配置【ResourceServerSecurityConfigurer 资源服务器安全配置器】
    // 这个扩展点非常重要, 注意它的执行时机哦~
    for (ResourceServerConfigurer configurer : configurers) {
        configurer.configure(resources);
    }
    
    // 给HttpSecurity添加一些配置器
    http
        
        // 1. 给HttpSecurity的sharedObjects属性中AuthenticationManagerBuilder类型的公共属性添加AnonymousAuthenticationProvider
        // 2. 这个AuthenticationManagerBuilder的公共属性, 在 WebSecurityConfigurerAdapter 的 getHttp()方法中, 在创建HttpSecurity时, 
        //    就会把WebSecurityConfigurerAdapter的authenticationBuilder属性作为AuthenticationManagerBuilder传进去作为HttpSecurity的公共属性
        .authenticationProvider(new AnonymousAuthenticationProvider("default"))
        
        // 添加异常处理配置器, 将resources的accessDeniedHandler设置给异常处理配置器
        .exceptionHandling().accessDeniedHandler(resources.getAccessDeniedHandler())
        .and()
        
        // 添加会话管理配置器, 不使用HttpSession
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        
        // 移除掉csrf配置器
        .csrf().disable();
    
    // 将【ResourceServerSecurityConfigurer 资源服务器安全配置器】添加到HttpSecurity, 
    // 目的是使用刚刚配置好的 资源服务器安全配置器 来配置HttpSecurity
    http.apply(resources);
    
    // 1. 如果 AuthorizationServerEndpointsConfiguration 不为空, 那说明开启了授权服务器
    //    那就把@FrameworkEndpoint标注的处理器方法的请求路径排除在 资源服务过滤器链 之外
    // 2. 其实根本就不用吧?!当同时开启授权服务和资源服务的话, 授权服务过滤器链肯定排在资源服务器链前面, 那么肯定先匹配上授权服务过滤器链阿, 
    //    这在AuthorizationServerSecurityConfiguration的configure(HttpSecurity)方法中就设置了资源过滤器链的匹配的路径,
    //    难道这里只是想说 授权服务的请求不归资源服务器链管?
    if (endpoints != null) {
        http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping()));
    }
    
    // 使用容器中所有定义的 *ResourceServerConfigurer* 资源服务器配置器 来配置HttpSecurity过滤器链
    // 这个扩展点非常重要, 注意它的执行时机哦~
    for (ResourceServerConfigurer configurer : configurers) {
        configurer.configure(http);
    }
    
    // 如果没有定义 ResourceServerConfigurer 资源服务器配置器, 那就对资源服务过滤器链匹配到的请求 都需要认证后,才能访问
    if (configurers.isEmpty()) {
        http.authorizeRequests().anyRequest().authenticated();
    }
}
ResourceServerSecurityConfigurer

资源服务器安全配置器,用于给HttpSecurity添加OAuth2AuthenticationProcessingFilter过滤器,该过滤器会通过读取令牌加载OAuth2Authentication绑定到当前线程,以确认客户端身份和用户授权信息。

// 异常处理过滤器的认证入口点
AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();

// 异常处理过滤器的访问拒绝处理器
AccessDeniedHandler accessDeniedHandler = new OAuth2AccessDeniedHandler();

// 【OAuth2AuthenticationProcessingFilter 资源服务器过滤器】
// 它会使用 TokenExtractor 令牌提取器 从当前请求中获取 令牌, 然后把令牌交给 OAuth2AuthenticationManager 作认证, OAuth2AuthenticationManager 又会交给 ResourceServerTokenServices 来加载 OAuth2Authentication 认证对象
OAuth2AuthenticationProcessingFilter resourcesServerFilter;

// 认证管理器, 实现一般为: OAuth2AuthenticationManager, 它会持有ResourceServerTokenServices, 用于读取令牌
AuthenticationManager authenticationManager;

// 认证事件发布器
AuthenticationEventPublisher eventPublisher = null;

// 资源服务器令牌服务, 用于读取令牌, 以获得OAuth2Authentication认证对象
ResourceServerTokenServices resourceTokenServices;

// 令牌存储器, 用户存储令牌和查询令牌
TokenStore tokenStore = new InMemoryTokenStore();

// 资源服务标识, 用于标识此资源服务。同时, ClientDetails客户端必须拥有该资源服务, 才能访问该资源
String resourceId = "oauth2-resource";

// 用于支持权限表达式, 可参考: Security方法注解权限控制过程及自定义权限表达式 https://blog.csdn.net/qq_16992475/article/details/130462486
SecurityExpressionHandler<FilterInvocation> expressionHandler = new OAuth2WebSecurityExpressionHandler();

// 令牌提取器, 用于在 OAuth2AuthenticationProcessingFilter 过滤器中, 从请求中获取令牌
TokenExtractor tokenExtractor;

// 获取认证详情信息
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;

// 是否无状态。
// 如果是false, 则不仅仅是OAuth2客户端可以访问, 非OAuth2客户端也可以访问;
// 如果是true, 则如果是非OAuth2客户端已被认证了, 那么在OAuth2AuthenticationProcessingFilter中会清除掉这个认证对象
//(注意OAuth2AuthenticationProcessingFilter过滤器排在UsernamePasswordAuthenticationFilter前面, 这在FilterComparator和ResourceServerSecurityConfigurer的configure(HttpSecurity)方法中可以看到)
boolean stateless = true;
init(HttpSecurit)
@Override
public void init(HttpSecurity http) {
    
    // 1. 其实就是给HttpSecurity过滤器链上的异常过滤器设置入口点属性,
    // 2. 将当前ResourceServerSecurityConfigurer资源服务器安全配置器的authenticationEntryPoint属性设置到ExceptionHandlingConfigurer的defaultEntryPointMappings中, 并以MediaTypeRequestMatcher实例作为key
    registerDefaultAuthenticationEntryPoint(http);
}
configure(HttpSecurity)
@Override
public void configure(HttpSecurity http) {

    // 创建1个 OAuth2AuthenticationManager OAuth2认证管理器, 并完成 OAuth2认证管理器 的配置
    AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http);

    // 创建1个 【OAuth2AuthenticationProcessingFilter 过滤器】
    resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
    
    // 给 【OAuth2AuthenticationProcessingFilter 过滤器】 设置 OAuth2认证管理器 和 认证入口点
    resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
    resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
    
    // 【OAuth2AuthenticationProcessingFilter 过滤器】 设置 认证事件发布器
    if (eventPublisher != null) {
        resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher);
    }
    
    // 给 【OAuth2AuthenticationProcessingFilter 过滤器】 设置 令牌提取器
    if (tokenExtractor != null) {
        resourcesServerFilter.setTokenExtractor(tokenExtractor);
    }
    
    // 给 【OAuth2AuthenticationProcessingFilter 过滤器】 设置 认证详情源
    if (authenticationDetailsSource != null) {
        resourcesServerFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
    }
    
    // 使用ObjectPostProcessor处理 【OAuth2AuthenticationProcessingFilter 过滤器】
    resourcesServerFilter = postProcess(resourcesServerFilter);
    
    // 给 【OAuth2AuthenticationProcessingFilter 过滤器】 设置 stateless
    resourcesServerFilter.setStateless(stateless);

    http
        
        // 添加FilterSecurityInterceptor过滤器, 并添加支持权限表达式处理器
        .authorizeRequests().expressionHandler(expressionHandler)
        
        .and()
        
        // 添加【OAuth2AuthenticationProcessingFilter 过滤器】, 放置在AbstractPreAuthenticatedProcessingFilter前面
        // 在我们熟悉的 UsernamePasswordAuthenticationFilter, 可在FilterComparator中看到
        .addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)
        
        // 添加异常处理配置器, 并指定 访问拒绝处理器 和 认证入口点
        .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
        					.authenticationEntryPoint(authenticationEntryPoint);
}

private AuthenticationManager oauthAuthenticationManager(HttpSecurity http) {
    
    // 创建1个 OAuth2AuthenticationManager OAuth2认证管理器
    OAuth2AuthenticationManager oauthAuthenticationManager = new OAuth2AuthenticationManager();
    
    // 如果已经设置认证管理器, 并且 如果认证管理器是 OAuth2AuthenticationManager  那么给该oauthAuthenticationManager继续设置属性, 
    //                          如果认证管理器不是 OAuth2AuthenticationManager 类型, 那么直接返回
    if (authenticationManager != null) {
        if (authenticationManager instanceof OAuth2AuthenticationManager) {
            oauthAuthenticationManager = (OAuth2AuthenticationManager) authenticationManager;
        }
        else {
            return authenticationManager;
        }
    }
    
    // 设置 资源服务标识, 若客户端无该资源服务标识, 将不能访问该资源
    oauthAuthenticationManager.setResourceId(resourceId);
    
    // 获取 资源服务器令牌服务, 并设置给 OAuth2认证管理器
    oauthAuthenticationManager.setTokenServices(resourceTokenServices(http));
    
    // 获取 客户端服务, 并设置给 OAuth2认证管理器
    oauthAuthenticationManager.setClientDetailsService(clientDetails());
    
    // 返回配置好的 OAuth2认证管理器
    return oauthAuthenticationManager;
}

// 获取资源服务器令牌服务
private ResourceServerTokenServices resourceTokenServices(HttpSecurity http) {
    
    // 获取 ResourceServerSecurityConfigurer 设置的 resourceTokenServices 或 创建1个DefaultTokenServices 作为 资源服务器令牌服务
    tokenServices(http);
    
    return this.resourceTokenServices;
}

// 获取 资源服务器令牌服务
private ResourceServerTokenServices tokenServices(HttpSecurity http) {
    
    // 如果设置 给 ResourceServerSecurityConfigurer 设置了 resourceTokenServices, 就直接返回指定的资源服务器令牌服务
    if (resourceTokenServices != null) {
        return resourceTokenServices;
    }
    
    // 未给 ResourceServerSecurityConfigurer 设置 令牌服务 属性时, 
    // 就会创建默认的令牌服务, 并使用 ResourceServerSecurityConfigurer 设置 的令牌存储器
    
    // 创建1个默认的令牌服务
    DefaultTokenServices tokenServices = new DefaultTokenServices();
    
    // 获取tokenStore 设置到 令牌服务 中()
    tokenServices.setTokenStore(tokenStore());
    
    // 设置支持刷新令牌
    tokenServices.setSupportRefreshToken(true);
    
    // 设置客户端详情服务
    tokenServices.setClientDetailsService(clientDetails());
    
    // DefaultTokenServices可作为资源服务器令牌服务
    this.resourceTokenServices = tokenServices;
    
    return tokenServices;
}

// 获取 令牌存储器
// 直接返回 ResourceServerSecurityConfigurer 的 tokenStore 属性, 显然它一定要被设置, 否则一调用就会抛异常
private TokenStore tokenStore() {
    Assert.state(tokenStore != null, "TokenStore cannot be null");
    return this.tokenStore;
}

// 获取访问拒绝处理器, 直接返回 ResourceServerSecurityConfigurer 的 accessDeniedHandler 属性
public AccessDeniedHandler getAccessDeniedHandler() {
    return this.accessDeniedHandler;
}
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值