文档章节

基于角色的权限控制在springMVC框架中的实现

菜蚜
 菜蚜
发布于 01/11 16:32
字数 1823
阅读 1069
收藏 50
点赞 0
评论 6

前言:常规来说,我们在做权限的时候,基本就是这么几个要素:用户、角色、资源(权限点)。角色本质上是给资源分组,这样不同的group具有不同的权限来控制用户更方便一些。

一般情况下,web应用的权限控制都会设计成把请求路径(也就是url,实质是uri)作为权限点来赋予角色不同的权限,在拦截器获取用户信息后,根据用户的角色找到对应的权限点,并与当前的请求路径匹配,最终返回是否具有权限。

那么,今天我想说的是,在一般的web项目中,在spring(MVC)框架下,我们是怎么灵活使用spring框架本身完成权限校验的。

对于一个web请求来说,我们都能得到一个HttpServletRequest对象,那么这个request对象有很多信息决定了这个请求的唯一性:请求路径uri、请求方法(常用rest风格的GET/POST/PUT/DELETE...)、请求参数params、请求头header(主要包括Content-Type、Referer、User-Agent、Cookie)等,可惜传统的权限控制实现方式是比较局限的,而且严重限制了制定rest风格的url。

所以,springmvc是怎么将当前request对象和所有controller的请求进行匹配的呢?我们可以利用这个机制实现权限控制。

OK,源码分析正式开始:

part I

springmvc继承了servlet的核心处理类:

org.springframework.web.servlet.DispatcherServlet extends javax.servlet.http.HttpServlet

而方法核心处理方法

org.springframework.web.servlet.DispatcherServlet#doService

调用了

org.springframework.web.servlet.DispatcherServlet#doDispatch

继续往下,又调用了

org.springframework.web.servlet.DispatcherServlet#getHandler

OK,这个方法里面我们会看到一个接口org.springframework.web.servlet.HandlerMapping,这是处理映射的最基础的接口。

来看看它的实现:

org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler

再看调用:

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
// Handler method lookup

	/**
	 * Look up a handler method for the given request.
	 */
	@Override
	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);// 查找当前的uri
		if (logger.isDebugEnabled()) {
			logger.debug("Looking up handler method for path " + lookupPath);
		}
		this.mappingRegistry.acquireReadLock();
		try {
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);// 查找处理方法
			if (logger.isDebugEnabled()) {
				if (handlerMethod != null) {
					logger.debug("Returning handler method [" + handlerMethod + "]");
				}
				else {
					logger.debug("Did not find handler method for [" + lookupPath + "]");
				}
			}
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			this.mappingRegistry.releaseReadLock();
		}
	}

来看org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod

/**
	 * Look up the best-matching handler method for the current request.
	 * If multiple matches are found, the best match is selected.
	 * @param lookupPath mapping lookup path within the current servlet mapping
	 * @param request the current request
	 * @return the best-matching handler method, or {@code null} if no match
	 * @see #handleMatch(Object, String, HttpServletRequest)
	 * @see #handleNoMatch(Set, String, HttpServletRequest)
	 */
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<Match>();
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);// 根据uri获取所有的请求匹配,这里是一个列表,因为有些请求可能uri相同,method、参数等不同
        // 其中这个类有两个变量很重要,1、org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#mappingLookup是一个RequestMappingInfo为key,HanderMethod为value的Map;2、org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#urlLookup是一个uri为key,RequestMappingInfo为value的Map
		if (directPathMatches != null) {
			addMatchingMappings(directPathMatches, matches, request);// 这里把直接匹配的进行二次筛选,具体看下面代码分析
		}
		if (matches.isEmpty()) {
			// No choice but to go through all mappings...
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}

		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));// 给匹配到的列表根据优先级排序,以选择最佳匹配
			Collections.sort(matches, comparator);
			if (logger.isTraceEnabled()) {
				logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
						lookupPath + "] : " + matches);
			}
			Match bestMatch = matches.get(0);
			if (matches.size() > 1) {
				if (CorsUtils.isPreFlightRequest(request)) {
					return PREFLIGHT_AMBIGUOUS_MATCH;
				}
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
							request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
				}
			}
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}
		else {
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}

继续看怎么进行二次筛选的:

/**
	 * Checks if all conditions in this request mapping info match the provided request and returns
	 * a potentially new request mapping info with conditions tailored to the current request.
	 * <p>For example the returned instance may contain the subset of URL patterns that match to
	 * the current request, sorted with best matching patterns on top.
	 * @return a new instance in case all conditions match; or {@code null} otherwise
	 */
	@Override
	public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
        // 这里很关键了,这个方法是进一步把request对象和当前RequestMappingInfo的各个条件做比对进行匹配。所以这里匹配分为两步:第一步,匹配uri,第二步匹配其他condition。而这里陈列的conditions也是区分request对象是否唯一的所有条件。
		RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
		ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
		HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
		ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
		ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);

		if (methods == null || params == null || headers == null || consumes == null || produces == null) {
			return null;
		}

		PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
		if (patterns == null) {
			return null;
		}

		RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
		if (custom == null) {
			return null;
		}

		return new RequestMappingInfo(this.name, patterns,
				methods, params, headers, consumes, produces, custom.getCondition());
	}

part II

接下来,我们看看spring怎么初始化所有的controller请求到内存的:

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet (实现了接口InitializingBean,bean实例化完成时执行)

这个方法会调用

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods

然后调用

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods

继而调用

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod

紧接着调用

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register

public void register(T mapping, Object handler, Method method) {
            // 这个方法就是将controller的请求和所在的类、方法一起注册到对应的变量中,放在内存供后续使用
			this.readWriteLock.writeLock().lock();
			try {
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				assertUniqueMethodMapping(handlerMethod, mapping);

				if (logger.isInfoEnabled()) {
					logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
				}
				this.mappingLookup.put(mapping, handlerMethod);// 赋值为map1,结构上文有说

				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
					this.urlLookup.add(url, mapping);// 赋值为map2,结构上文有说
				}

				String name = null;
				if (getNamingStrategy() != null) {
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}

				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
					this.corsLookup.put(handlerMethod, corsConfig);
				}

				this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
			}
			finally {
				this.readWriteLock.writeLock().unlock();
			}
		}

注:核心的RequestMappingInfo这个类就是@RequestMapping注解的映射;

有些注释在贴的代码中夹杂着。。

part III

那么分析基本告一段落,我们只需要把

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerMapping
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod

稍微改造一下就行了。

附上改造后的入口代码:

package com.xxx.cms.web.interceptor;

import com.google.common.collect.Maps;
import com.xxx.cms.ucenter.domain.resource.PlatResource;
import com.xxx.cms.ucenter.domain.user.User;
import com.xxx.cms.ucenter.service.role.AccessPermissionService;
import com.xxx.cms.web.access.AbstractHandlerMethodMapping;
import com.xxx.cms.web.access.RequestMappingHandlerMapping;
import com.xxx.cms.web.base.Constant;
import com.xxx.cms.web.component.UserInfoService;
import com.xxx.session.SessionException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;

/**
 * 用户权限校验拦截器
 *
 * @author caiya
 * @since 1.0
 */
public class UserAccessInterceptor extends HandlerInterceptorAdapter {

    private static final Logger logger = LoggerFactory.getLogger(UserAccessInterceptor.class);

    public static Map<String, AbstractHandlerMethodMapping<RequestMappingInfo>> MAPPING_CACHE_MAP = Maps.newConcurrentMap();

    private final UserInfoService userInfoService;

    private final AccessPermissionService accessPermissionService;

    public UserAccessInterceptor(UserInfoService userInfoService, AccessPermissionService accessPermissionService) {
        this.userInfoService = userInfoService;
        this.accessPermissionService = accessPermissionService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取用户信息
        User user = userInfoService.getUserInfo(Constant.getSessionId(request));
        if (user == null) {
            throw new SessionException("用户会话失效!");
        }

        // 权限校验
        if (!match(user.getRoleId(), request)) {
            throw new IllegalAccessException("用户没有权限做此操作!");
        }

        return super.preHandle(request, response, handler);
    }

    private boolean match(Long currentRoleId, HttpServletRequest request) throws Exception {
        // 先查询缓存,这里以role为key进行缓存
        String currentAccessCacheKey = "access:roleId-" + currentRoleId;
        AbstractHandlerMethodMapping<RequestMappingInfo> currentMapping = MAPPING_CACHE_MAP.get(currentAccessCacheKey);// 从本地缓存获取数据
        if (currentMapping == null) {
            // 查询数据库(这里会有另外的缓存)
            Map<Long, List<PlatResource>> accessMap = accessPermissionService.getRoleResources();
            for (Map.Entry<Long, List<PlatResource>> entry : accessMap.entrySet()) {
                AbstractHandlerMethodMapping<RequestMappingInfo> mapping = new RequestMappingHandlerMapping();
                List<PlatResource> resources = entry.getValue();
                for (PlatResource resource : resources) {
                    if (StringUtils.isBlank(resource.getUrlPath()) && StringUtils.isBlank(resource.getMethod())) {
                        continue;
                    }
                    PatternsRequestCondition patternsCondition = new PatternsRequestCondition(resource.getUrlPath());
                    RequestMethodsRequestCondition methodsCondition = new RequestMethodsRequestCondition(RequestMethod.valueOf(resource.getMethod()));
                    // reserve other conditions..
                    RequestMappingInfo requestMappingInfo = new RequestMappingInfo(null, patternsCondition, methodsCondition, null, null, null, null, null);
                    mapping.registerMapping(requestMappingInfo, this.getClass(), this.getClass().getMethods()[0]);// ignore these params
                }
                if (entry.getKey().equals(currentRoleId)) {
                    currentMapping = mapping;
                }
                try {
                    // TODO 设置本地缓存,注意缓存更新策略
                    String accessCacheKey = "access:roleId-" + entry.getKey();
//                    MAPPING_CACHE_MAP.put(accessCacheKey, mapping);
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }

        if (currentMapping == null) {
            return false;
        }

        AbstractHandlerMethodMapping.Match match = currentMapping.getBestMatch(request);
        return match != null && match.getMapping() != null;
    }
}

其中改造了三个spring的类:

import com.xxx.cms.ucenter.service.role.AccessPermissionService;
import com.xxx.cms.web.access.AbstractHandlerMethodMapping;
import com.xxx.cms.web.access.RequestMappingHandlerMapping;

我想,这里就不必贴了吧。

文章编写急促,还请谅解。另欢迎交流~

----------补充说明----------

由于本文直接使用springmvc框架,依赖的框架本身的很多类,所以当spring版本变化的时候,需要注意是否会影响到本文实现的内容。当然,如果有精力的话,可以把请求匹配机制从spring中抽离出来,独立成自己的权限校验框架。

© 著作权归作者所有

共有 人打赏支持
菜蚜
粉丝 21
博文 73
码字总数 32859
作品 0
杭州
程序员
加载中

评论(6)

菜蚜
菜蚜

引用来自“键盘敲得贼溜的猴子”的评论

就是借用了spring mvc的match流程。既学习了源码也模拟运用,活学活用嘛

引用来自“菜蚜”的评论

我们为了兼容springMVC提供的rest风格的url,并且想知道spring是怎么映射一个request对象到每一个controller的方法,所以需要分析源码以及做适配。如果不用springmvc框架,或许需要借鉴这套匹配机制重写一套request映射框架了(其实尝试过,只是依赖太深不好办,也不方便序列化把mapping存进redis)。

引用来自“键盘敲得贼溜的猴子”的评论

突然想起个问题,如果我有 /app/module/resource/{id}的权限,没有/app/module/resource/list的权限,当让两者都是GET请求,我能正常访问后者吗?

引用来自“菜蚜”的评论

可以的,org.springframework.web.servlet.mvc.condition.PatternsRequestCondition#getMatchingPattern先通过equals完全匹配,再通过org.springframework.util.AntPathMatcher#doMatch占位符匹配。

引用来自“键盘敲得贼溜的猴子”的评论

可是这样就不符合权限验证的逻辑了,我没有后者的权限应该不能访问才对。当然实际项目中这样的权限分配是有点变态了。。。
1、有 /app/module/resource/{id}的权限,没有/app/module/resource/list的权限;
2、没有 /app/module/resource/{id}的权限,有/app/module/resource/list的权限;
可以改造一下成为:先匹配equals完全/精准匹配的请求(含include的权限和exclude的权限),再匹配占位符的权限;如果真有这种需要可以这样改,但是没必要搞得太复杂了。
键盘敲得贼溜的猴子

引用来自“键盘敲得贼溜的猴子”的评论

就是借用了spring mvc的match流程。既学习了源码也模拟运用,活学活用嘛

引用来自“菜蚜”的评论

我们为了兼容springMVC提供的rest风格的url,并且想知道spring是怎么映射一个request对象到每一个controller的方法,所以需要分析源码以及做适配。如果不用springmvc框架,或许需要借鉴这套匹配机制重写一套request映射框架了(其实尝试过,只是依赖太深不好办,也不方便序列化把mapping存进redis)。

引用来自“键盘敲得贼溜的猴子”的评论

突然想起个问题,如果我有 /app/module/resource/{id}的权限,没有/app/module/resource/list的权限,当让两者都是GET请求,我能正常访问后者吗?

引用来自“菜蚜”的评论

可以的,org.springframework.web.servlet.mvc.condition.PatternsRequestCondition#getMatchingPattern先通过equals完全匹配,再通过org.springframework.util.AntPathMatcher#doMatch占位符匹配。
可是这样就不符合权限验证的逻辑了,我没有后者的权限应该不能访问才对。当然实际项目中这样的权限分配是有点变态了。。。
菜蚜
菜蚜

引用来自“键盘敲得贼溜的猴子”的评论

就是借用了spring mvc的match流程。既学习了源码也模拟运用,活学活用嘛

引用来自“菜蚜”的评论

我们为了兼容springMVC提供的rest风格的url,并且想知道spring是怎么映射一个request对象到每一个controller的方法,所以需要分析源码以及做适配。如果不用springmvc框架,或许需要借鉴这套匹配机制重写一套request映射框架了(其实尝试过,只是依赖太深不好办,也不方便序列化把mapping存进redis)。

引用来自“键盘敲得贼溜的猴子”的评论

突然想起个问题,如果我有 /app/module/resource/{id}的权限,没有/app/module/resource/list的权限,当让两者都是GET请求,我能正常访问后者吗?
可以的,org.springframework.web.servlet.mvc.condition.PatternsRequestCondition#getMatchingPattern先通过equals完全匹配,再通过org.springframework.util.AntPathMatcher#doMatch占位符匹配。
键盘敲得贼溜的猴子

引用来自“键盘敲得贼溜的猴子”的评论

就是借用了spring mvc的match流程。既学习了源码也模拟运用,活学活用嘛

引用来自“菜蚜”的评论

我们为了兼容springMVC提供的rest风格的url,并且想知道spring是怎么映射一个request对象到每一个controller的方法,所以需要分析源码以及做适配。如果不用springmvc框架,或许需要借鉴这套匹配机制重写一套request映射框架了(其实尝试过,只是依赖太深不好办,也不方便序列化把mapping存进redis)。
突然想起个问题,如果我有 /app/module/resource/{id}的权限,没有/app/module/resource/list的权限,当让两者都是GET请求,我能正常访问后者吗?
菜蚜
菜蚜

引用来自“键盘敲得贼溜的猴子”的评论

就是借用了spring mvc的match流程。既学习了源码也模拟运用,活学活用嘛
我们为了兼容springMVC提供的rest风格的url,并且想知道spring是怎么映射一个request对象到每一个controller的方法,所以需要分析源码以及做适配。如果不用springmvc框架,或许需要借鉴这套匹配机制重写一套request映射框架了(其实尝试过,只是依赖太深不好办,也不方便序列化把mapping存进redis)。
键盘敲得贼溜的猴子
就是借用了spring mvc的match流程。既学习了源码也模拟运用,活学活用嘛
Shiro和Spring Security对比

Shiro简介 Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不...

有余力则学文
04/27
0
0
maven 构建 springmvc + spring security 权限控制示例

maven 构建 springmvc + spring security 权限控制示例。 介绍 :Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(A...

空云万里晴
2013/01/02
0
3
Spring Security实现RBAC权限管理

Spring Security实现RBAC权限管理 一简介 在企业应用中,认证和授权是非常重要的一部分内容,业界最出名的两个框架就是大名鼎鼎的 Shiro和Spring Security。由于Spring Boot非常的流行,选择...

小忽悠
06/21
0
0
权限设计(下) - 细说权限设计

什么是权限管理 一般来说,只要有用户参与的系统,那么都要有权限管理,尤其是一些后台的管理系统, 权限管理可以实现对用户访问系统的控制,按照安全规则或者相关策略的控制,可以使用户访问...

风间影月
2016/06/07
0
0
使用 Spring Security 保护 Web 应用的安全 转载

在 Web 应用开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境...

小样
2012/08/24
0
0
SpringMvc + Shiro[数据库存权限] 配置 ;[附git.oschina的项目地址]

一 shiro简介 apache shiro 是功能强大并且容易集成的开源权限框架,它能够完成认证、授权、加密、会话管理等功能。认证和授权为权限控制的核心,简单来说,“认证”就是证明“你是谁?” We...

王庭
2015/10/28
0
3
spring security

Spring Security 为基于 J2EE 企业应用软件提供了全面安全服务。安全主要包括两个操作“认证”与“验证”(有时候也会叫做权限控制)。“认证”是为用户建立一个其声明的角色的过程,这个角色...

peiquan
07/04
0
0
Pig 1.0-BETA 发布,完善的 Spring Cloud 开发脚手框架

Hi 大家好! 我是pig开发团队的冷冷,经过4月迭代,400次提交,关闭issue 35 ,N个内测版本,我们将这个完善的微服务开发脚手架框架正式开放公测。 PIG是一个后端基于Spring Cloud、oAuth2....

冷冷gg
04/24
0
0
springboot系列(二):Apache Shiro安全框架的简单使用

上一篇文章中实践了原始方式实现的登录验证的过程,了解了关于登录验证的一些简单原理。然而在实际项目的开发中,这种方式并不能满足实际安全的需求,而是借助一些框架来实现。目前,Apache ...

ijustdarren
2017/11/15
0
0
3.osframe框架权限开发说明

开源项目名称: osframe管理系统 托管地址: http://git.oschina.net/haizicq/osframe 主要技术: spring mvc、spring、hibernate、angular js、seajs、bootstrap、shiro 框架介绍: 本框架是...

王春-海子
2016/06/30
102
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

微服务架构下的安全认证与鉴权

微服务下常见认证解决方案; OAuth认证与授权; JWT认证介绍; Spring Cloud的OAuth2实现; 单体应用转变为分布式应用 单体应用转变为分布式应用在架构方式上存在较大区别,单体应用下的简单...

Java大蜗牛
23分钟前
0
0
前端面试题汇总

最近在复习,准备找工作了,特此总结一下前端的相关知识。 1.获取浏览器URL中查询字符的参数: function getQuery(name){    var reg = new RegExp("(^|&)"+name+"=([^&]*)"(&|$));...

凛冬来袭
24分钟前
0
0
可持续发展的学习道路

与其要求别人,不如提升自己 内心渴望进步 经常做出改变现有模式,不断学习 寻找资源,整合资源,不断熟练这种模式 渠道很重要 先打开新世界的航路

狮子狗
28分钟前
0
0
apollox-lua开源项目 示例codepen2

今天在示例上增加了几个功能, 首先添加js array的标准库。 所有js array的方法目前都支持了。 添加查看code模式。 点击查看code可以看到生成的lua代码。默认web模式需要把标准库连接进来, ...

钟元OSS
44分钟前
0
0
javascript性能优化之避免重复工作

javascript最重要也最根本的性能优化标准之一是避免工作,避免工作又包括两点,第一,不做不必要的工作,第二,不做重复的已经完成的工作。第一部分可以通过代码重构完成,第二部分不做重复的...

老韭菜
55分钟前
0
0
缓存穿透、并发和雪崩那些事

0 题记 缓存穿透、缓存并发和缓存雪崩是常见的由于并发量大而导致的缓存问题,本文讲解其产生原因和解决方案。 缓存穿透通常是由恶意攻击或者无意造成的;缓存并发是由设计不足造成的;缓存雪...

Java填坑之路
58分钟前
1
0
项目jar包管理构建工具---Maven

一、what is Maven? 我们来寻找一下官网,里面介绍了maven到底是什么?下面一句话就有讲解到:Apache Maven is a software project management and comprehension tool. Based on the conc...

一看就喷亏的小猿
今天
0
0
JVM学习手册(一):查看堆内存使用情况以及排错

平时出现内存溢出以及死锁,一般处理方式都是查看日志,找到抛出异常的代码行,然后本地分析代码,但是这样对于线上排查十分糟糕,这段时间在研究JVM发现了几个比较好的工具和指令. 1.针对频繁GC和...

勤奋的蚂蚁
今天
1
0
17.TCP:传输控制协议

介绍 TCP和UDP使用同一网络层(IP),但TCP提供了面向连接、可靠的传输层服务 TCP传输给IP层的信息单位称为报文段或段 TCP通过如下方式保证可靠性: 应用数据被分割成TCP认为最合适发送的数据...

loda0128
今天
0
0
重装Oracle时出现environment variable "PATH"错误的解决办法

在win7 64位下重新安装oracle 11g,一直报environment variable "PATH"的错误,按说明将path里多余的路径删除,但没办法解决。选择忽略错误继续安装,装一半会报CRC错误,还是安装失败。最好...

良言
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部