" />

欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot集成整合JWT與Shiro流程詳解

 更新時(shí)間:2022年12月06日 10:26:11   作者:leo825...  
安全管理是軟件系統(tǒng)必不可少的的功能。根據(jù)經(jīng)典的“墨菲定律”——凡是可能,總會(huì)發(fā)生。如果系統(tǒng)存在安全隱患,最終必然會(huì)出現(xiàn)問題,這篇文章主要介紹了SpringBoot集成JWT、Shiro框架的使用

前言

代碼庫:https://gitee.com/leo825/springboot-learning-parents.git

之前寫過《SpringBoot集成Shiro框架》,最近看了JWT,正好兩個(gè)整合一下,加深一下理解。

實(shí)戰(zhàn)演練

代碼結(jié)構(gòu)

SQL腳本

schema.sql

create table if not exists t_user (
	`id`            int  primary  key not null ,
	`username`      char (50) not null,
	`password`      char(50) not null,
	`role`          char(50) not null,
	`permission`    char(255) not null
) ;

data.sql

insert into t_user(id, username, password, role, permission) values (0,'smith','smith123','user','view');
insert into t_user(id, username, password, role, permission) values (1,'danny','danny123','admin','view,edit')

數(shù)據(jù)格式如下:

usernamepasswordrolepermission
smithsmith123userview
dannydanny123adminview,edit

pom依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.example</groupId>
		<artifactId>springboot-learning-parents</artifactId>
		<version>1.0-SNAPSHOT</version>
	</parent>
	<groupId>springboot-demo</groupId>
	<artifactId>springboot-jwt-shiro</artifactId>
	<version>1.0-SNAPSHOT</version>
	<name>springboot-jwt-shiro</name>
	<url>https://gitee.com/leo825/springboot-learning-parents.git</url>
	<description>springboot集成jwt和shiro測試</description>
	<properties>
		<start-class>com.demo.SpringbootJwtShiroApplication</start-class>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<mybatisplus.version>3.5.1</mybatisplus.version>
		<freemaker.version>2.3.31</freemaker.version>
		<mysql.version>8.0.28</mysql.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
		<!-- mybatis-plus 所需依賴  -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>${mybatisplus.version}</version>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-generator</artifactId>
			<version>${mybatisplus.version}</version>
		</dependency>
		<!-- 使用h2內(nèi)存數(shù)據(jù)庫 -->
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>
		<!-- jwt依賴 -->
		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>3.8.1</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.47</version>
		</dependency>
		<!--引入shrio 選擇stater方式-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring-boot-starter</artifactId>
			<version>1.9.0</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

User

package com.demo.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
 * 用戶
 */
@TableName("t_user")
@Data
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 用戶主鍵
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * 用戶名
     */
    private String username;
    /**
     * 密碼
     */
    private String password;
    /**
     * 角色
     */
    private String role;
    /**
     * 權(quán)限
     */
    private String permission;
}

JwtUtil

package com.demo.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
/**
 * jwt工具類
 */
public class JwtUtil {
    /**
     * 過期時(shí)間5分鐘
     */
    private static final long EXPIRE_TIME = 5 * 60 * 1000;
    /**
     * jwt 密鑰
     */
    public static final String SECRET = "jwt_secret";
    /**
     * 校驗(yàn)token是否正確
     *
     * @param token  密鑰
     * @param secret 用戶的密碼
     * @return 是否正確
     */
    public static boolean verify(String token, String username, String secret) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }
    /**
     * 獲得token中的信息無需secret解密也能獲得
     *
     * @return token中包含的用戶名
     */
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }
    /**
     * 生成簽名,5min后過期
     *
     * @param username 用戶名
     * @param pwd      用戶的密碼
     * @return 加密的token
     */
    public static String sign(String username, String pwd) {
        try {
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(pwd);
            // 附帶username信息
            return JWT.create()
                    .withClaim("username", username)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            return null;
        }
    }
}

UserMapper

package com.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.demo.model.User;
/**
 * 用戶表
 */
public interface UserMapper extends BaseMapper<User> {
}

UserService

public interface UserService extends IService<User> {
    User getUser(String username) throws AuthenticationException;
}

UserServiceImpl

package com.demo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.demo.mapper.UserMapper;
import com.demo.model.User;
import com.demo.service.UserService;
import org.springframework.stereotype.Service;
import javax.security.sasl.AuthenticationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Override
    public User getUser(String username) throws AuthenticationException {
        Map<String,Object> columnMap = new HashMap<>();
        columnMap.put("username", username);
        List<User> users = baseMapper.selectByMap(columnMap);
        if(users.isEmpty()){
            throw new AuthenticationException("User didn't existed!");
        }
        return users.get(0);
    }
}

JwtToken

package com.demo.token;
import org.apache.shiro.authc.AuthenticationToken;
public class JwtToken implements AuthenticationToken {
    // 密鑰
    private String token;
    public JwtToken(String token) {
        this.token = token;
    }
    @Override
    public Object getPrincipal() {
        return token;
    }
    @Override
    public Object getCredentials() {
        return token;
    }
}

MyRealm

package com.demo.realm;
import com.demo.model.User;
import com.demo.service.UserService;
import com.demo.token.JwtToken;
import com.demo.util.JwtUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Component
@Slf4j
public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;
    /**
     * 大坑!,必須重寫此方法,不然Shiro會(huì)報(bào)錯(cuò)
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }
    /**
     * 只有當(dāng)需要檢測用戶權(quán)限的時(shí)候才會(huì)調(diào)用此方法,例如checkRole,checkPermission之類的
     *
     * @param principals
     * @return
     */
    @SneakyThrows
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = JwtUtil.getUsername(principals.toString());
        User user = userService.getUser(username);
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRole(user.getRole());
        Set<String> permission = new HashSet<>(Arrays.asList(user.getPermission().split(",")));
        simpleAuthorizationInfo.addStringPermissions(permission);
        return simpleAuthorizationInfo;
    }
    /**
     * 默認(rèn)使用此方法認(rèn)證用戶名正確與否驗(yàn)證,錯(cuò)誤拋出異常即可。
     *
     * @param auth
     * @return
     * @throws AuthenticationException
     */
    @SneakyThrows
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) {
        String token = (String) auth.getCredentials();
        // 解密獲得username,用于和數(shù)據(jù)庫進(jìn)行對比
        String username = JwtUtil.getUsername(token);
        if (username == null) {
            throw new AuthenticationException("token invalid");
        }
        User user = userService.getUser(username);
        if (!JwtUtil.verify(token, username, user.getPassword())) {
            throw new AuthenticationException("Username or password error");
        }
        return new SimpleAuthenticationInfo(token, token, JwtUtil.SECRET);
    }
}

JWTFilter

package com.demo.filter;
import com.demo.token.JwtToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * 代碼的執(zhí)行流程 preHandle -> isAccessAllowed -> isLoginAttempt -> executeLogin
 */
@Slf4j
public class JWTFilter extends BasicHttpAuthenticationFilter {
    /**
     * 判斷用戶是否想要登入。
     * 檢測header里面是否包含Authorization字段即可
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader("Authorization");
        return authorization != null;
    }
    /**
     *
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String authorization = httpServletRequest.getHeader("Authorization");
        JwtToken token = new JwtToken(authorization);
        // 提交給realm進(jìn)行登入,如果錯(cuò)誤他會(huì)拋出異常并被捕獲
        getSubject(request, response).login(token);
        // 如果沒有拋出異常則代表登入成功,返回true
        return true;
    }
    /**
     * 這里我們詳細(xì)說明下為什么最終返回的都是true,即允許訪問
     * 例如我們提供一個(gè)地址 GET /article
     * 登入用戶和游客看到的內(nèi)容是不同的
     * 如果在這里返回了false,請求會(huì)被直接攔截,用戶看不到任何東西
     * 所以我們在這里返回true,Controller中可以通過 subject.isAuthenticated() 來判斷用戶是否登入
     * 如果有些資源只有登入用戶才能訪問,我們只需要在方法上面加上 @RequiresAuthentication 注解即可
     * 但是這樣做有一個(gè)缺點(diǎn),就是不能夠?qū)ET,POST等請求進(jìn)行分別過濾鑒權(quán)(因?yàn)槲覀冎貙懥斯俜降姆椒?,但實(shí)際上對應(yīng)用影響不大
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (isLoginAttempt(request, response)) {
            try {
                executeLogin(request, response);
            } catch (Exception e) {
                response401(request, response);
            }
        }
        return true;
    }
    /**
     * 對跨域提供支持
     */
    @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"));
        // 跨域時(shí)會(huì)首先發(fā)送一個(gè)option請求,這里我們給option請求直接返回正常狀態(tài)
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
    /**
     * 將非法請求跳轉(zhuǎn)到 /401
     */
    private void response401(ServletRequest req, ServletResponse resp) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
            httpServletResponse.sendRedirect("/401");
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }
}

ShiroConfig

package com.demo.config;
import com.demo.filter.JWTFilter;
import com.demo.realm.MyRealm;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
    @Bean("securityManager")
    public DefaultWebSecurityManager getManager(MyRealm realm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // 使用自己的realm
        manager.setRealm(realm);
        /*
         * 關(guān)閉shiro自帶的session,詳情見文檔
         * 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);
        manager.setSubjectDAO(subjectDAO);
        return manager;
    }
    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 添加自己的過濾器并且取名為jwt
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt", new JWTFilter());
        factoryBean.setFilters(filterMap);
        factoryBean.setSecurityManager(securityManager);
        factoryBean.setUnauthorizedUrl("/401");
        /*
         * 自定義url規(guī)則
         * http://shiro.apache.org/web.html#urls-
         */
        Map<String, String> filterRuleMap = new HashMap<>();
        // 所有請求通過我們自己的JWT Filter
        filterRuleMap.put("/**", "jwt");
        // 訪問401和404頁面不通過我們的Filter
        filterRuleMap.put("/401", "anon");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }
    /**
     * 下面的代碼是添加注解支持
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 強(qiáng)制使用cglib,防止重復(fù)代理和可能引起代理出錯(cuò)的問題
        // https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

TestController

URL作用
/login登入
/article所有人都可以訪問,但是用戶與游客看到的內(nèi)容不同
/require_auth登入的用戶才可以進(jìn)行訪問
/require_roleadmin的角色用戶才可以登入
/require_permission擁有view和edit權(quán)限的用戶才可以訪問

測試代碼如下:

package com.demo.controller;
import com.demo.common.Result;
import com.demo.model.User;
import com.demo.service.UserService;
import com.demo.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import javax.security.sasl.AuthenticationException;
@Slf4j
@RestController
@RequestMapping("/jwt")
public class TestController {
    @Autowired
    private UserService userService;
    @PostMapping("/login")
    public Result login(@RequestParam("username") String username, @RequestParam("password") String password) throws AuthenticationException {
        User user = userService.getUser(username);
        if (user.getPassword().equals(password)) {
            return new Result(200, "Login success", JwtUtil.sign(username, password));
        } else {
            throw new UnauthorizedException();
        }
    }
    @GetMapping("/article")
    public Result article() {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()) {
            return new Result(200, "You are already logged in", null);
        } else {
            return new Result(200, "You are guest", null);
        }
    }
    @GetMapping("/require_auth")
    @RequiresAuthentication
    public Result requireAuth() {
        return new Result(200, "You are authenticated", null);
    }
    @GetMapping("/require_role")
    @RequiresRoles("admin")
    public Result requireRole() {
        return new Result(200, "You are visiting require_role", null);
    }
    @GetMapping("/require_permission")
    @RequiresPermissions(logical = Logical.AND, value = {"view", "edit"})
    public Result requirePermission() {
        return new Result(200, "You are visiting permission require edit,view", null);
    }
    @RequestMapping(path = "/401")
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public Result unauthorized() {
        return new Result(401, "Unauthorized", null);
    }
}

測試

登錄成功

到此這篇關(guān)于SpringBoot集成整合JWT與Shiro流程詳解的文章就介紹到這了,更多相關(guān)SpringBoot JWT與Shiro內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 將Java程序的輸出結(jié)果寫入文件方法實(shí)例

    將Java程序的輸出結(jié)果寫入文件方法實(shí)例

    這篇文章主要給大家介紹了關(guān)于將Java程序的輸出結(jié)果寫入文件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • springboot配置嵌入式servlet容器的方法

    springboot配置嵌入式servlet容器的方法

    這篇文章主要介紹了springboot配置嵌入式servlet容器的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • Java中OAuth2.0第三方授權(quán)原理與實(shí)戰(zhàn)

    Java中OAuth2.0第三方授權(quán)原理與實(shí)戰(zhàn)

    本文主要介紹了Java中OAuth2.0第三方授權(quán)原理與實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • 解決@Around對靜態(tài)方法不生效的問題

    解決@Around對靜態(tài)方法不生效的問題

    這篇文章主要介紹了解決@Around對靜態(tài)方法不生效的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • SpringBoot @Autowired注入為空的情況解讀

    SpringBoot @Autowired注入為空的情況解讀

    這篇文章主要介紹了SpringBoot @Autowired注入為空的情況解讀,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • @NonNull導(dǎo)致無法序列化的問題及解決

    @NonNull導(dǎo)致無法序列化的問題及解決

    這篇文章主要介紹了@NonNull導(dǎo)致無法序列化的問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • Lombok 的@StandardException注解解析

    Lombok 的@StandardException注解解析

    @StandardException 是一個(gè)實(shí)驗(yàn)性的注解,添加到 Project Lombok 的 v__1.18.22 版本中,在本教程中,我們將使用 Lombok 的 @StandardException 注解自動(dòng)生成異常類型類的構(gòu)造函數(shù),需要的朋友可以參考下
    2023-05-05
  • guava中Multimap、HashMultimap用法小結(jié)

    guava中Multimap、HashMultimap用法小結(jié)

    這篇文章主要介紹了guava中Multimap、HashMultimap使用,Multimap它可以很簡單的實(shí)現(xiàn)一些功能,LinkedHashMultimap實(shí)現(xiàn)類與HashMultimap類的實(shí)現(xiàn)方法一樣,唯一的區(qū)別是LinkedHashMultimap保存了記錄的插入順序,本文就這些內(nèi)容講解的非常詳細(xì),需要的朋友參考下吧
    2022-05-05
  • myBatis的mapper映射文件之批量處理方式

    myBatis的mapper映射文件之批量處理方式

    這篇文章主要介紹了myBatis的mapper映射文件之批量處理方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • JVM知識總結(jié)之垃圾收集算法

    JVM知識總結(jié)之垃圾收集算法

    本博客為讀書筆記,讀的是《深入理解Java虛擬機(jī)》一書,在看這個(gè)書的時(shí)候,最大的一個(gè)感受便是“當(dāng)初怎么就沒有好好學(xué)習(xí)操作系統(tǒng)呢,不然也不會(huì)有這么多看的云里霧里的地方了”,不過那都是過去的事了,學(xué)習(xí)最好的時(shí)刻便是現(xiàn)在,需要的朋友可以參考下
    2021-06-06

最新評論