最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Springboot+Shiro+Jwt實(shí)現(xiàn)權(quán)限控制的項(xiàng)目實(shí)踐

 更新時(shí)間:2023年09月14日 15:59:54   作者:搬山道猿  
如今的互聯(lián)網(wǎng)已經(jīng)成為前后端分離的時(shí)代,所以本文在使用SpringBoot整合Shiro框架的時(shí)候會(huì)聯(lián)合JWT一起搭配使用,具有一定的參考價(jià)值,感興趣的可以了解一下

前置背景

為什么寫(xiě)下這篇文章?

因?yàn)樾枰獙?shí)現(xiàn)一個(gè)設(shè)備管理系統(tǒng)的權(quán)限管理模塊,在查閱很多博客以及其他網(wǎng)上資料之后,發(fā)現(xiàn)重復(fù)、無(wú)用的博客很多,因此寫(xiě)一篇文章來(lái)記錄,以便后面復(fù)習(xí)。

涉及的知識(shí)點(diǎn)主要有下列知識(shí)點(diǎn):

  • JWT
  • shiro

書(shū)寫(xiě)順序

  • 首先使用springboot 結(jié)合 jwt完成前后端分離的token認(rèn)證。
  • 其次結(jié)合shiro完成shiro+jwt的前后端分離的權(quán)限認(rèn)證管理。

權(quán)限管理的表結(jié)構(gòu)設(shè)計(jì)

一個(gè)user可以擁有多個(gè)role,一個(gè)role也可以被多個(gè)user擁有, 一個(gè) 角色擁有多個(gè)權(quán)限即功能,一個(gè)權(quán)限可以被多個(gè)role擁有。

用戶(hù)、角色、權(quán)限類(lèi)

 表結(jié)構(gòu)圖

Part1: spring boot + jwt

這一部分就可以完成前后端分離項(xiàng)目的登錄功能。在不需要添加權(quán)限管理的情況下,就可以滿(mǎn)足需求。

Spring boot集成JWT

<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>3.8.2</version>
</dependency>

思路整理

  • 為什么需要使用到j(luò)wt?
  • jwt是什么
  • Java如何使用jwt

在前后端分離的項(xiàng)目中,由服務(wù)器使用的會(huì)話(huà)管理 session 無(wú)法滿(mǎn)足需求。需要一種技術(shù)做會(huì)話(huà)管理。因此選擇 JWT 。  Json web token (JWT)  : 是目前流行的跨域認(rèn)證解決方案,是一種基于 Token 的認(rèn)證授權(quán)機(jī)制。  JWT 的數(shù)據(jù)結(jié)構(gòu)分為三部分 header payload signature。 這三部分通過(guò) . 連接,如下

Token示例:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJwYXNzd29yZCI6InN1cGVyIiwiZXhwIjoxNjYzMTIzNDgzLCJ1c2VybmFtZSI6InN1cGVyIn0.
5xVg6IuOLe_uVwwOaeyRDbTHRjfmIbNsnb-DP9-Ic20

如何使用:當(dāng)用戶(hù)登錄系統(tǒng)后,服務(wù)端給前端發(fā)送一個(gè)基于用戶(hù)信息創(chuàng)建的token,然后在此后的每一次前端請(qǐng)求都會(huì)攜帶token。服務(wù)端通過(guò)攔截器攔截請(qǐng)求,同時(shí)驗(yàn)證攜帶的token是否正確。如果正確則放行請(qǐng)求,不正確則拒絕通過(guò)。 思路流程圖:

token的創(chuàng)建和驗(yàn)證

JWTUtil.java 負(fù)責(zé)創(chuàng)建和驗(yàn)證jwt格式的token

    public class JWTUtil {
        private static final long EXPIRE_TIME = 3 * 60 * 1000;//默認(rèn)3分鐘
        //私鑰
        private static final String TOKEN_SECRET = "privateKey";
    ?
        public static String createToken(UserEntity userModel) {
            try {
            // 設(shè)置過(guò)期時(shí)間
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            log.info(String.valueOf(date));
            // 私鑰和加密算法
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            // 設(shè)置頭部信息
            Map<String, Object> header = new HashMap<>(2);
            header.put("Type", "Jwt");
            header.put("alg", "HSA256");
            // 返回token字符串
            return JWT.create()
                .withHeader(header)
                .withClaim("username", userModel.getUsername())
                .withExpiresAt(date)
                .sign(algorithm);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        /**
        * 檢驗(yàn)token是否正確
        *
        * @param **token**
        * @return
        */
        public static boolean verifyToken(String token, String username) {
            log.info("驗(yàn)證token..");
            try {
                Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
                JWTVerifier verifier = JWT.require(algorithm)
                                          .withClaim("username",username).build();
                // 驗(yàn)證不通過(guò)會(huì)拋出異常。
                verifier.verify(token);
                return true;
            } catch (Exception e) {
                log.info("verifyToken = {}",e.getMessage());
                return false;
            }
        }
    ?
        // 通過(guò)withClaim添加在token里面的數(shù)據(jù)都可以通過(guò)這種方式獲取
        public static String getUsername(String token){
            DecodedJWT jwt = JWT.decode(token);
            String username = String.valueOf(jwt.getClaim("username"));
            if (StringUtils.hasLength(username)){
                return username;
            }
            return  null;
        }
    }

攔截器的創(chuàng)建和配置

創(chuàng)建攔截器,攔截請(qǐng)求

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor  {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 當(dāng)前端是通過(guò)在請(qǐng)求里面以 token="xxxx.xxx.zzz"的方式傳遞時(shí),通過(guò)getHeader("token")
        // 的方式獲取。
        String token = request.getHeader("token");
        log.info("token = {}",token);
        if (token == null){
            setReturnInfo((HttpServletResponse) response,401,"請(qǐng)攜帶token");
            return false;
        }
        // 解析token中的數(shù)據(jù),JWTUtil.getUsername();
        // 在這里可以通過(guò)findUserByUsername的方式從數(shù)據(jù)源中獲取數(shù)據(jù)
        // 假定登錄用戶(hù)是super, 并傳遞給此方法傳遞參數(shù) 
        if ( !JWTUtil.verifyToken(token,"super")){
            setReturnInfo((HttpServletResponse) response,401,"token已過(guò)期");
            return false;
        }
        return true;
    }
    private static void setReturnInfo(HttpServletResponse httpResponse,int status,String msg) throws IOException {
        log.info("token = null");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpResponse.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        Map<String,String> result  =new HashMap<>();
        result.put("status",String.valueOf(status));
        result.put("msg",msg);
        httpResponse.getWriter().print(JSONUtils.toJSONString(result));
        // 前端可根據(jù)返回的status判斷
    }
}

2. 配置攔截器

InterceptorConfig.java 負(fù)責(zé)將使用了JwtUtil的攔截器配置進(jìn)入Spring boot。

    @Configuration
    public class InterceptorConfig  implements WebMvcConfigurer {
        @Resource
        private LoginInterceptor interceptor;
    ?
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            List<String> patterns = new ArrayList<>();
    ?
            // 添加過(guò)濾路由 默認(rèn)攔截所有請(qǐng)求
            patterns.add("/**");
            registry.addInterceptor(interceptor)
                    .addPathPatterns(patterns)
                    .excludePathPatterns("/user/login"); // 用戶(hù)登錄請(qǐng)求不攔截
        }
    ?
    }

Part2:shiro+jwt

  • 明確shiro是什么
  • shiro的工作流程
  • spring boot如何配置shiro框架
  • shiro和jwt的整合

shiro是一個(gè)權(quán)限管理框架,相對(duì)于Spring Security而言,代碼簡(jiǎn)單。適用場(chǎng)景多,不只局限于Java。 shiro的架構(gòu)原理圖:

  • Subject:當(dāng)前和系統(tǒng)交互的"用戶(hù)",可以是人,系統(tǒng),第三方插件等。統(tǒng)稱(chēng)為Subject。 SecurityManager:類(lèi)似于Spring容器的一個(gè)容器,是Shiro的核心。管理眾多的組件。
  • Authenticator:認(rèn)證組件,用戶(hù)需要先通過(guò)系統(tǒng)認(rèn)證,在進(jìn)行用戶(hù)授權(quán),需要先判斷系統(tǒng)中是否有這個(gè)用戶(hù),在進(jìn)行后續(xù)操作,因此在這里就是進(jìn)行系統(tǒng)認(rèn)證的地方。
  • Authorizer:授權(quán),當(dāng)一個(gè)用戶(hù)是屬于當(dāng)前系統(tǒng)時(shí),這個(gè)用戶(hù)的一些操作就需要判斷是否有權(quán)力去做這件事情。在這里就需要進(jìn)行授權(quán)相關(guān)的
  • Realm 可以理解為數(shù)據(jù)源,就是在realm記錄了那些屬于本系統(tǒng)的用戶(hù),他們具有什么樣的角色

就相當(dāng)于在一個(gè)擁有多個(gè)公司的工業(yè)園區(qū),人們需要有這個(gè)園區(qū)的卡片,才允許進(jìn)入園區(qū),而進(jìn)入園區(qū)之后需要由本公司的門(mén)禁你才能進(jìn)入公司,否則就不能進(jìn)入公司一樣。A公司的員工不能進(jìn)入B公司。  Subject  就是員工,或者快遞小哥, SecurityManager  就是園區(qū)門(mén)禁系統(tǒng), Authenticator  就是門(mén)禁的閘機(jī)  Authorizer  的作用就是公司的門(mén)禁一樣, realm  就是園區(qū)系統(tǒng)的數(shù)據(jù)庫(kù),記錄了系統(tǒng)的用戶(hù)和權(quán)限信息 。

工作流程:一個(gè)subject通過(guò)login()方法,將subject的信息提交給SecurityManager,SecurityManager調(diào)用自己的組件去判斷,認(rèn)證,授權(quán)等。shiro是通過(guò)filter來(lái)進(jìn)行攔截請(qǐng)求的,因此在結(jié)合jwt時(shí),就不需要interceptor就能達(dá)到預(yù)期的效果。

思路流程圖:

第一步改造JwtToken

    // AuthenticationToken 是shiro框架的。
    public class JWTToken implements AuthenticationToken {
        private String token;
    ?
        public JWTToken(String token) {
            this.token = token;
        }
    ?
        public String getToken() {
            return token;
        }
    ?
        @Override
        public Object getPrincipal() {
            return token;
        }
    ?
        @Override
        public Object getCredentials() {
            return token;
        }
    ?
    }

JWTUtils代碼不變,創(chuàng)建和驗(yàn)證邏輯如上。 第二步編寫(xiě)自己的數(shù)據(jù)源Realm

    public class MyRealm extends AuthorizingRealm {
    ?
        // 指定憑證匹配器。匹配器工作在認(rèn)證后,授權(quán)前。
        public MyRealm() {
            this.setCredentialsMatcher(new JWTCredentialsMatcher());
        }
        @Resource
        UserServiceInt userServiceInt;
        // 判斷token是否為JWTToken 必須重寫(xiě)
        @Override
        public boolean supports(AuthenticationToken token) {
            return token instanceof JWTToken;
        }
        // 認(rèn)證
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    ?
            log.info("AuthenticationInfo 開(kāi)始認(rèn)證");
            String token = ((JWTToken) authenticationToken).getToken();
            String username = JWT.decode(token).getClaim("username").asString();
            // 從系統(tǒng)的數(shù)據(jù)庫(kù)查找是否擁有這個(gè)用戶(hù),也可以提前把數(shù)據(jù)加載到Redis中,從redis中查找即可。
            UserModel user = userServiceInt.getUserByUsername(username);
            if (user == null) {
                log.info("user為空"); 
                // 認(rèn)證不通過(guò)
                return null;
            }
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                    user,
                    token,
                    "myRealm"
            );
            return simpleAuthenticationInfo;
        }
        // 授權(quán)
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            log.info("開(kāi)始授權(quán)...");
            // 從PrincipalCollection獲取user
            UserModel userModel = (UserModel) principalCollection.getPrimaryPrincipal();
    ?
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            // 模擬數(shù)據(jù)庫(kù)操作。實(shí)際上可以利用mybatis的級(jí)聯(lián)查詢(xún),查詢(xún)出用戶(hù)的角色和權(quán)限信息。
            if (userModel.getUsername().equals("super")){
                // 添加用戶(hù)角色
                simpleAuthorizationInfo.addRole("admin");
                // 添加用戶(hù)權(quán)限
                simpleAuthorizationInfo.addStringPermission("user:list");
            }
            return simpleAuthorizationInfo;
        }
    }
public class JWTCredentialsMatcher implements CredentialsMatcher {
   @Override
   public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
?
       String token = ((JWTToken)authenticationToken).getToken();
       log.info("JWTCredentialsMatcher token = {}",token);
       UserModel userModel = (UserModel) authenticationInfo.getPrincipals().getPrimaryPrincipal();
       log.info("JWTCredentialsMatcher token = {}",userModel.toString());
       // 調(diào)用JwtUtils驗(yàn)證token即可
       return JWTUtil.verifyToken(token, userModel.getUsername(), userModel.getPassword());
   }
}

第三:編寫(xiě)filter攔截前端請(qǐng)求

    public class JwtFilter extends BasicHttpAuthenticationFilter {
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            log.info("isAccessAllowed : 驗(yàn)證是否擁有token");
            String token = ((HttpServletRequest) request).getHeader("token");
            HttpServletResponse servletResponse = (HttpServletResponse) response;
            if (!StringUtils.hasLength(token)) {
                try {
                    setReturnInfo(servletResponse, 401, "token為空");
                } catch (IOException e) {
                    e.printStackTrace();
                    return false;
                }
            }
            return executeLogin(request,response);
        }
        @Override
        protected boolean executeLogin(ServletRequest request, ServletResponse response)   {
            log.info("executeLogin : 執(zhí)行登錄");
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            String token = httpServletRequest.getHeader("token");
            JWTToken jwtToken = new JWTToken(token);
            // 提交給realm進(jìn)行登入,如果錯(cuò)誤他會(huì)拋出異常并被捕獲
            try {
                getSubject(request, response).login(jwtToken);
            } catch (Exception e) {
                log.info("認(rèn)證出現(xiàn)異常:{}", e.getMessage());
                try {
                    setReturnInfo(httpServletResponse,401,"token錯(cuò)誤");
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                return false;
            }
            // 如果沒(méi)有拋出異常則代表登入成功,返回true
            return true;
        }
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            log.info("登錄失敗");
            return super.onAccessDenied(request, response);
        }
        /**
         * 對(duì)跨域提供支持
         */
        @Override
        protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
            httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
            httpServletResponse.setCharacterEncoding("UTF-8");
            // 跨域時(shí)會(huì)首先發(fā)送一個(gè)option請(qǐng)求,這里我們給option請(qǐng)求直接返回正常狀態(tài)
            if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
                httpServletResponse.setStatus(HttpStatus.OK.value());
                return true;
            }
            return super.preHandle(request, response);
        }
        private static void setReturnInfo(HttpServletResponse response, int status, String msg) throws IOException {
            response.setContentType("application/json;charset=utf-8");
            Map<String, String> result = new HashMap<>();
            result.put("status", String.valueOf(status));
            result.put("msg", msg);
            response.getWriter().write(JSONUtils.toJSONString(result));
        }

第四:配置shiro

    @Configuration
    public class ShiroConfig {
        @Bean("shiroFilter")
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            Map<String, Filter> filterMap = new HashMap<>();
            filterMap.put("jwt",new JwtFilter());
            shiroFilterFactoryBean.setFilters(filterMap);
            Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
            filterChainDefinitionMap.put("/user/login","anon");
            filterChainDefinitionMap.put("/**", "jwt");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
        // 禁用session
        @Bean
        protected SessionStorageEvaluator sessionStorageEvaluator(){
            DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
            sessionStorageEvaluator.setSessionStorageEnabled(false);
            return sessionStorageEvaluator;
        }
        @Bean("securityManager")
        public SecurityManager securityManager(MyRealm userRealm) {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            // 指定SecurityManager的域
            securityManager.setRealm(userRealm);
            /*
             * 關(guān)閉shiro自帶的session,詳情見(jiàn)文檔
             * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
             */
            DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
            DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
            defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
            subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
            securityManager.setSubjectDAO(subjectDAO);
            return securityManager;
        }
        @Bean("myRealm")
        public MyRealm shiroRealm() {
            MyRealm shiroRealm = new MyRealm();
            return shiroRealm;
        }
        @Bean
        public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
            autoProxyCreator.setProxyTargetClass(true);
            return autoProxyCreator;
        }
        /**
         * 開(kāi)啟shiro aop注解支持.
         * 使用代理方式;所以需要開(kāi)啟代碼支持;
         * @param
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    }

第五步在controller的方法上適用shiro權(quán)限控制的注解即可

    @GetMapping("list")
    // @RequiresAuthentication
    @RequiresRoles(value = {"admin"})
    // @RequiresPermissions(value = {"user:list"})
    public List<UserModel> listUsers(){
        return  userServiceInt.listUser();
    }

到此這篇關(guān)于Springboot+Shiro+Jwt實(shí)現(xiàn)權(quán)限控制的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)Springboot Shiro Jwt實(shí)現(xiàn)權(quán)限控制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • spring aop底層源碼執(zhí)行邏輯剖析(源碼解析)

    spring aop底層源碼執(zhí)行邏輯剖析(源碼解析)

    這篇文章主要介紹了spring aop底層源碼執(zhí)行邏輯剖析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-08-08
  • Spring?@Cacheable注解類(lèi)內(nèi)部調(diào)用失效的解決方案

    Spring?@Cacheable注解類(lèi)內(nèi)部調(diào)用失效的解決方案

    這篇文章主要介紹了Spring?@Cacheable注解類(lèi)內(nèi)部調(diào)用失效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • SpringBoot如何實(shí)現(xiàn)一個(gè)實(shí)時(shí)更新的進(jìn)度條的示例代碼

    SpringBoot如何實(shí)現(xiàn)一個(gè)實(shí)時(shí)更新的進(jìn)度條的示例代碼

    本文詳細(xì)的介紹了SpringBoot如何實(shí)現(xiàn)一個(gè)實(shí)時(shí)更新的進(jìn)度條,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • SpringBoot使用RESTful接口詳解

    SpringBoot使用RESTful接口詳解

    RESTful是一種web軟件風(fēng)格,它不是標(biāo)準(zhǔn)也不是協(xié)議,它不一定要采用,只是一種風(fēng)格,它倡導(dǎo)的是一個(gè)資源定位(url)及資源操作的風(fēng)格,這篇文章主要介紹了SpringBoot使用RESTful接口
    2022-10-10
  • Springboot使用slf4j記錄日志的方法步驟

    Springboot使用slf4j記錄日志的方法步驟

    本文主要介紹了Springboot使用slf4j記錄日志的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • JAVA中跳出當(dāng)前多重嵌套循環(huán)的方法詳解

    JAVA中跳出當(dāng)前多重嵌套循環(huán)的方法詳解

    今天在看面試題時(shí),發(fā)現(xiàn)了這個(gè)問(wèn)題,因?yàn)樵赑HP中跳出多次循環(huán)可以使用break數(shù)字來(lái)跳出多層循環(huán),但這在java中并不好使,這篇文章主要給大家介紹了關(guān)于JAVA中跳出當(dāng)前多重嵌套循環(huán)的相關(guān)資料,需要的朋友可以參考下
    2022-01-01
  • java 全角半角字符轉(zhuǎn)換如何實(shí)現(xiàn)

    java 全角半角字符轉(zhuǎn)換如何實(shí)現(xiàn)

    在java中可能會(huì)用到過(guò)全角半角字符轉(zhuǎn)換問(wèn)題,于是網(wǎng)上搜索整理了一下,曬出來(lái)和大家分享,希望可以幫助你們
    2012-12-12
  • java使用JDBC連接數(shù)據(jù)庫(kù)的五種方式(IDEA版)

    java使用JDBC連接數(shù)據(jù)庫(kù)的五種方式(IDEA版)

    這篇文章主要介紹了java使用JDBC連接數(shù)據(jù)庫(kù)的五種方式(IDEA版),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • java與C 代碼運(yùn)行效率的對(duì)比(整理)

    java與C 代碼運(yùn)行效率的對(duì)比(整理)

    最近和朋友無(wú)意間討論起了 有關(guān)java 和C 的 效率問(wèn)題, (我是java 推介者, 他是 c 語(yǔ)言推介者, 他做的是嵌入式)故,想通過(guò)網(wǎng)絡(luò)查詢(xún)一下, 總結(jié)一下,兩者到底效率如何,其有何差異,原因又是啥?各種優(yōu)勢(shì)有在何處?
    2021-04-04
  • 基于Ok+Rxjava+retrofit實(shí)現(xiàn)斷點(diǎn)續(xù)傳下載

    基于Ok+Rxjava+retrofit實(shí)現(xiàn)斷點(diǎn)續(xù)傳下載

    這篇文章主要為大家詳細(xì)介紹了基于Ok+Rxjava+retrofit實(shí)現(xiàn)斷點(diǎn)續(xù)傳下載,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-06-06

最新評(píng)論

九龙坡区| 灵山县| 西宁市| 霍林郭勒市| 奉新县| 墨竹工卡县| 东莞市| 绍兴县| 汉寿县| 元谋县| 塔河县| 大安市| 濉溪县| 哈尔滨市| 大邑县| 巧家县| 盐津县| 拉孜县| 历史| 济宁市| 马尔康县| 静宁县| 凌云县| 景德镇市| 阳东县| 葫芦岛市| 安图县| 新宾| 鹿邑县| 株洲县| 开远市| 宁乡县| 沭阳县| 温宿县| 海淀区| 云阳县| 扎兰屯市| 普洱| 辉县市| 驻马店市| 涞水县|