关于Spring Cache 缓存拦截器( CacheInterceptor)

目录
  • Spring Cache 缓存拦截器( CacheInterceptor)
    • spring cache常用的三种缓存操作
    • 具体整个流程是这样的
  • CacheInterceptor.java
    • 定义Cacheable注解
    • 定义Rediskey.java
    • Cache.java
    • RedisCache.java
    • CacheManager.java
    • AbstractCacheManager.java
    • RedisCacheManager.java
    • 实现CacheInterceptor.java
    • 配置Spring.xml
    • 测试使用

Spring Cache 缓存拦截器( CacheInterceptor)

打开Spring Cache的核心缓存拦截器CacheInterceptor,可以看到具体实现:

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
	@Override
	public Object invoke(final MethodInvocation invocation) throws Throwable {
		Method method = invocation.getMethod();
		CacheOperationInvoker aopAllianceInvoker = new CacheOperationInvoker() {
			@Override
			public Object invoke() {
				try {
					return invocation.proceed();
				}
				catch (Throwable ex) {
					throw new ThrowableWrapper(ex);
				}
			}
		};
		try {
			return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
		}
		catch (CacheOperationInvoker.ThrowableWrapper th) {
			throw th.getOriginal();
		}
	}
}

CacheInterceptor默认实现了Spring aop的MethodInterceptor接口,MethodInterceptor的功能是做方法拦截。拦截的方法都会调用invoke方法,在invoke方法里面主要缓存逻辑是在execute方法里面,该方法是继承了父类CacheAspectSupport。

protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
		// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
		if (this.initialized) {
			Class<?> targetClass = getTargetClass(target);
			//获取执行方法上所有的缓存操作集合。如果有缓存操作则执行到execute(...),如果没有就执行invoker.invoke()直接调用执行方法了
			Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass);
			if (!CollectionUtils.isEmpty(operations)) {
				return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
			}
		}
		return invoker.invoke();
	}

集合Collection operations中存放了所有的缓存操作CachePutOperation、CacheableOperation、CacheEvictOperation

spring cache常用的三种缓存操作

  • @CachePut:执行方法后,将方法返回结果存放到缓存中。不管有没有缓存过,执行方法都会执行,并缓存返回结果(unless可以否决进行缓存)。(当然,这里说的缓存都要满足condition条件)
  • @Cacheable:如果没有缓存过,获取执行方法的返回结果;如果缓存过,则直接从缓存中获取,不再执行方法。
  • @CacheEvict:如果设置了beforeIntercepte则在方法执行前进行缓存删除操作,如果没有,则在执行方法调用完后进行缓存删除操作。
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
		// Special handling of synchronized invocation
		if (contexts.isSynchronized()) {
			CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
			if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
				Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
				Cache cache = context.getCaches().iterator().next();
				try {
					return wrapCacheValue(method, cache.get(key, new Callable<Object>() {
						@Override
						public Object call() throws Exception {
							return unwrapReturnValue(invokeOperation(invoker));
						}
					}));
				}
				catch (Cache.ValueRetrievalException ex) {
					// The invoker wraps any Throwable in a ThrowableWrapper instance so we
					// can just make sure that one bubbles up the stack.
					throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
				}
			}
			else {
				// No caching required, only call the underlying method
				return invokeOperation(invoker);
			}
		}
		// 处理beforeIntercepte=true的缓存删除操作
		processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
				CacheOperationExpressionEvaluator.NO_RESULT);
		// 从缓存中查找,是否有匹配@Cacheable的缓存数据
		Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
		// 如果@Cacheable没有被缓存,那么就需要将数据缓存起来,这里将@Cacheable操作收集成CachePutRequest集合,以便后续做@CachePut缓存数据存放。
		List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
		if (cacheHit == null) {
			collectPutRequests(contexts.get(CacheableOperation.class),
					CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
		}
		Object cacheValue;
		Object returnValue;
		//如果没有@CachePut操作,就使用@Cacheable获取的结果(可能也没有@Cableable,所以result可能为空)。
		if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
			//如果没有@CachePut操作,并且cacheHit不为空,说明命中缓存了,直接返回缓存结果
			cacheValue = cacheHit.get();
			returnValue = wrapCacheValue(method, cacheValue);
		}
		else {
			// 否则执行具体方法内容,返回缓存的结果
			returnValue = invokeOperation(invoker);
			cacheValue = unwrapReturnValue(returnValue);
		}
		// Collect any explicit @CachePuts
		collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
		// Process any collected put requests, either from @CachePut or a @Cacheable miss
		for (CachePutRequest cachePutRequest : cachePutRequests) {
			cachePutRequest.apply(cacheValue);
		}
		// Process any late evictions
		processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
		return returnValue;
	}
	//根据key从缓存中查找,返回的结果是ValueWrapper,它是返回结果的包装器
	private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
		Object result = CacheOperationExpressionEvaluator.NO_RESULT;
		for (CacheOperationContext context : contexts) {
			if (isConditionPassing(context, result)) {
				Object key = generateKey(context, result);
				Cache.ValueWrapper cached = findInCaches(context, key);
				if (cached != null) {
					return cached;
				}
				else {
					if (logger.isTraceEnabled()) {
						logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
					}
				}
			}
		}
		return null;
	}
	private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
		for (Cache cache : context.getCaches()) {
			Cache.ValueWrapper wrapper = doGet(cache, key);
			if (wrapper != null) {
				if (logger.isTraceEnabled()) {
					logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
				}
				return wrapper;
			}
		}
		return null;
	}

具体整个流程是这样的

CacheInterceptor.java

项目中基本上都需要使用到Cache的功能, 但是Spring提供的Cacheable并不能很好的满足我们的需求, 所以这里自己借助Spring思想完成自己的业务逻辑.

定义Cacheable注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
    RedisKey value();
    String key();
}

定义Rediskey.java

public enum RedisKeyEnum {
    TEST_CACHE("test:", 24, TimeUnit.HOURS, "Test");

    /**
     * 缓存Key的前缀
     */
    private String keyPrefix;

    /**
     * 过期时间
     */
    private long timeout;

    /**
     * 过期时间单位
     */
    private TimeUnit timeUnit;

    /**
     * 描述
     */
    private String desc;
    private static final String REDIS_KEY_DEFUALT_SEPARATOR = ":";
    RedisKey(String keyPrefix, long timeout, TimeUnit timeUnit, String desc){
        this.keyPrefix = keyPrefix;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.desc = desc;
    }

    public long getTimeout() {
        return timeout;
    }

    public TimeUnit getTimeUnit() {
        return timeUnit;
    }

    public String getDesc() {
        return desc;
    }

    /**
     * 获取完整的缓存Key
     * @param keys
     * @return
     */
    public String getKey(String... keys) {
        if(keys == null || keys.length <= 0){
            return this.keyPrefix;
        }
        String redisKey = keyPrefix;
        for (int i = 0, length = keys.length; i < length; i++) {
            String key = keys[i];
            redisKey += key;
            if (i < length - 1) {
                redisKey += REDIS_KEY_DEFUALT_SEPARATOR;
            }
        }
        return redisKey;
    }
}

Cache.java

public interface Cache<K, V> {
    /**
     * 返回缓存名称
     * @return
     */
    String getName();

    /**
     * 添加一个缓存实例
     *
     * @param key
     * @param value
     */
    V put(K key, V value);

    /**
     * 添加一个可过期的缓存实例
     * @param key
     * @param value
     * @param expire
     * @param timeUnit
     * @return
     */
    V put(K key, V value, long expire, TimeUnit timeUnit);

    /**
     * 返回缓存数据
     *
     * @param key
     * @return
     */
    V get(K key);

    /**
     * 删除一个缓存实例, 并返回缓存数据
     *
     * @param key
     * @return
     */
    void remove(K key);

    /**
     * 获取所有的缓存key
     * @return
     */
    Set<K> keys();

    /**
     * 获取所有的缓存key
     * @return
     */
    Set<K> keys(K pattern);

    /**
     * 获取所有的缓存数据
     * @return
     */
    Collection<V> values();

    /**
     * 清空所有缓存
     */
    void clear();
}

RedisCache.java

public class RedisCache<K, V> implements Cache<K, V> {
    public static final String DEFAULT_CACHE_NAME =  RedisCache.class.getName() + "_CACHE_NAME";
    private RedisTemplate<K, V> redisTemplate;
    private ValueOperations<K, V> valueOperations;
    public RedisCache(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.valueOperations = redisTemplate.opsForValue();
        DataType dataType = redisTemplate.type("a");
    }

    @Override
    public String getName() {
        return DEFAULT_CACHE_NAME;
    }

    @Override
    public V put(K key, V value) {
        valueOperations.set(key, value);
        return value;
    }

    @Override
    public V put(K key, V value, long expire, TimeUnit timeUnit) {
        valueOperations.set(key, value, expire, timeUnit);
        return value;
    }

    @Override
    public V get(K key) {
        return valueOperations.get(key);
    }

    @Override
    public void remove(K key) {
//        V value = valueOperations.get(key);
        redisTemplate.delete(key);
    }

    @Override
    public Set<K> keys() {
        return null;
    }

    @Override
    public Set<K> keys(K pattern) {
        return redisTemplate.keys(pattern);
    }

    @Override
    public Collection<V> values() {
        return null;
    }

    @Override
    public void clear() {
    }
}

CacheManager.java

public interface CacheManager {
    /**
     * 获取缓存
     * @return
     */
    Cache getCache(String name);

    /**
     * 获取所有的缓存名称
     */
    Collection<String> getCacheNames();
}

AbstractCacheManager.java

public abstract class AbstractCacheManager implements CacheManager, InitializingBean, DisposableBean {
    private static final Logger logger = LoggerFactory.getLogger(AbstractCacheManager.class);
    private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
    private volatile Set<String> cacheNames = Collections.emptySet();
    private static final String DEFAULT_CACHE_NAME_SUFFIX = "_CACHE_NAME";

    @Override
    public void afterPropertiesSet() throws Exception {
        initlalizingCache();
    }

    private void initlalizingCache(){
        Collection<? extends Cache> caches = loadCaches();
        synchronized (this.cacheMap) {
            this.cacheNames = Collections.emptySet();
            this.cacheMap.clear();
            Set<String> cacheNames = new LinkedHashSet<String>(caches.size());
            for (Cache cache : caches) {
                String name = cache.getName();
                if(StringUtils.isEmpty(name)){
                    name = cache.getClass().getName() + DEFAULT_CACHE_NAME_SUFFIX;
                }
                this.cacheMap.put(name, cache);
                cacheNames.add(name);
            }
            this.cacheNames = Collections.unmodifiableSet(cacheNames);
        }
    }

    @Override
    public Cache getCache(String name) {
        Cache cache = cacheMap.get(name);
        if(cache != null){
            return cache;
        }
        return null;
    }

    protected abstract Collection<? extends Cache> loadCaches();
    @Override
    public Collection<String> getCacheNames() {
        return this.cacheNames;
    }

    @Override
    public void destroy() throws Exception {
        cacheMap.clear();
    }
}

RedisCacheManager.java

public class RedisCacheManager extends AbstractCacheManager {
    private RedisTemplate redisTemplate;
    public RedisCacheManager(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    } 

    @Override
    protected Collection<? extends Cache> loadCaches() {
        Collection<Cache<String, Object>> caches = new ArrayList<>();
        RedisCache<String, Object> redisCache = new RedisCache<>(redisTemplate);
        caches.add(redisCache);
        return caches;
    }
}

实现CacheInterceptor.java

/**
 * 缓存数据过滤器, 缓存到redis数据中的数据是ServiceResult.getDateMap()数据
 * 使用: 在service方法上添加com.chinaredstar.urms.annotations.Cacheable注解, 并指定RedisKeyEunm和cache key, cache key支持Spel表达式
 * 以下情况不缓存数据:
 *  1: 返回状态为fasle时, 不缓存数据
 *  2: 返回dataMap为空时, 不缓存数据
 *  3: 返回数据结构不是ServiceReslut实例时, 不缓存数据
 *
 * 当缓存问题时, 不影响正常业务, 但所有的请求都会打到DB上, 对DB有很大的冲击
 */
public class CacheInterceptor implements MethodInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(CacheInterceptor.class);
    private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    private CacheManager cacheManager;
    public void setCacheManager(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Method method = methodInvocation.getMethod();
        Object[] args = methodInvocation.getArguments();
        Cacheable cacheable = method.getAnnotation(Cacheable.class);
        if (cacheable == null) {
            return methodInvocation.proceed();
        }
        String key = parseCacheKey(method, args, cacheable.key());
        logger.info(">>>>>>>> -- 获取缓存key : {}", key);
        if(StringUtils.isEmpty(key)){
            return methodInvocation.proceed();
        }
        RedisKey redisKey = cacheable.value();
        Cache cache = cacheManager.getCache(RedisCache.DEFAULT_CACHE_NAME);
        Object value = null;
        try{
            value = cache.get(redisKey.getKey(key));
        } catch (Exception e){
            logger.info(">>>>>>>> -- 从缓存中获取数据异常 : {}", ExceptionUtil.exceptionStackTrace(e));
        }
        if (value != null) {
            logger.info(">>>>>>>> -- 从缓存中获取数据 : {}", JsonUtil.toJson(value));
            return ServiceResult.newInstance(true, value);
        }
        value = methodInvocation.proceed();
        logger.info(">>>>>>>> -- 从接口中获取数据 : {}", JsonUtil.toJson(value));
        if ( value != null && value instanceof ServiceResult ) {
            ServiceResult result = (ServiceResult) value;
            if(!result.isSuccess() || result.getDataMap() == null){
                return value;
            }
            try{
                cache.put(redisKey.getKey(key), result.getDataMap(), redisKey.getTimeout(), redisKey.getTimeUnit());
            } catch (Exception e){
                logger.info(">>>>>>>> -- 将数据放入缓存异常 : {}", ExceptionUtil.exceptionStackTrace(e));
            }
        }
        return value;
    }

    /**
     * 使用SpeL解析缓存key
     * @param method
     * @param args
     * @param expressionString
     * @return
     */
    private String parseCacheKey(Method method, Object[] args, String expressionString) {
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();
        if (parameterNames != null && parameterNames.length > 0
                && args != null && args.length > 0
                && args.length == parameterNames.length ) {
            for (int i = 0, length = parameterNames.length; i < length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
        }
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(expressionString);
        return (String) expression.getValue(context);
    }
}

配置Spring.xml

    <bean id="redisCacheManager" class="com.package.cache.RedisCacheManager">
        <constructor-arg ref="cacheRedisTemplate" />
    </bean>
    <bean id="cacheInterceptor" class="com.package.interceptor.CacheInterceptor" p:cacheManager-ref="redisCacheManager"/>

    <!-- 方法拦截器 MethodInterceptor -->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="cacheInterceptorPointcut" expression="execution(* com.package..*(..))
                                                                and @annotation(com.package.annotations.Cacheable)"/>
        <aop:advisor advice-ref="cacheInterceptor" pointcut-ref="cacheInterceptorPointcut" order="2" />
    </aop:config>

测试使用

@Cacheable(value = RedisKey.TEST_CACHE, key = "#code + ':' + #user.id")
public ServiceResult<String> test(String code, User user){
    return new ServiceResult("success");
}

说明

Cacheable其中的参数key拼接的规则支持Spring SpeL表达式。其规则和Spring Cacheable使用方法一致。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring interceptor拦截器配置及用法解析

    fifter.servlet.interceptor fifter用来处理请求头.请求参数.编码的一些设置,然后转交给servlet,处理业务,返回 servlet现在常用的spring,servlet拦截/到DispatcherServlet,交由spring管理 interceptor,servlet请求之后可以实现HandlerInterceptor做到preHandle.postHandle.afterCompletion在controller之前.之后.渲染之后 登陆 业务中常用的登陆

  • springboot拦截器Interceptor的使用,你都了解吗

    springmvc 中的拦截器可以对请求进行判别, 在请求到达控制器之前, 把非法的请求给拦截掉 下面来说一说, 它在springboot中的使用 拦截器是可以有多个的, 对不同的 url 进行拦截 我们这个例子设想的是, 如果用户登录过, 就会用户设置一个 session , 如果session中 有user 的信息,就说明用户是登录过的 1.我们先创建一个 User 的实例对象 domain public class User { private Integer id; private St

  • 深入理解Spring Cache框架

    本文是缓存系列第三篇,前两篇分别介绍了 Guava 和 JetCache. 前两篇我们讲了 Guava 和 JetCache,它们都是缓存的具体实现,今天给大家分析一下 Spring 框架本身对这些缓存具体实现的支持和融合.使用 Spring Cache 将大大的减少我们的Spring项目中缓存使用的复杂度,提高代码可读性.本文将从以下几个方面来认识Spring Cache框架. 背景 SpringCache 产生的背景其实与Spring产生的背景有点类似.由于 Java EE 系统框架臃肿.低

  • 利用spring的拦截器自定义缓存的实现实例代码

    本文研究的主要是利用spring的拦截器自定义缓存的实现,具体实现代码如下所示. Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.本文利用Memcached 的实例和spring的拦截器实现缓存自定义的实现.利用拦截器读取自定义的缓存标签,key值的生成策略. 自定义的Cacheable package com.jeex.sci; @Target(ElementT

  • 关于Spring Cache 缓存拦截器( CacheInterceptor)

    目录 Spring Cache 缓存拦截器( CacheInterceptor) spring cache常用的三种缓存操作 具体整个流程是这样的 CacheInterceptor.java 定义Cacheable注解 定义Rediskey.java Cache.java RedisCache.java CacheManager.java AbstractCacheManager.java RedisCacheManager.java 实现CacheInterceptor.java 配置Spri

  • Android 中okhttp自定义Interceptor(缓存拦截器)

    Android 中okhttp自定义Interceptor(缓存拦截器) 前言: 新公司项目是没有缓存的,我的天,坑用户流量不是么.不知道有人就喜欢一个界面没事点来点去的么.怎么办?一个字"加". 由于项目的网络请求被我换成了retrofit.而retrofit的网络请求默认基于okhttp okhttp的缓存由返回的header 来决定.如果服务器支持缓存的话返回的headers里面会有这一句 "Cache-Control","max-age=time&

  • Asp.net Core 3.1基于AspectCore实现AOP实现事务、缓存拦截器功能

    最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性Attribute,那这个方法就会启用事务处理.给一个方法加一个缓存特性,那这个方法就会进行缓存. 这个也是网上说的面向切面编程AOP. AOP的概念也很好理解,跟中间件差不多,说白了,就是我可以任意地在方法的前面或后面添加代码,这很适合用于缓存.日志等处理. 在net core2.2时,我当时就尝试过用autofac实现aop,但这次我不想用autofac,我用了一个更轻量级的框架,AspectCore. 用起来非常非常的简单,但一

  • Springboot 集成spring cache缓存的解决方案

    目录 一.为什么要做缓存 二.常用缓存操作流程 三.整合Spring Cache 四.在ArticleController类上实现一个简单的例子 五.更改Redis缓存的序列化方式 一.为什么要做缓存 提升性能 绝大多数情况下,关系型数据库select查询是出现性能问题最大的地方.一方面,select 会有很多像 join.group.order.like 等这样丰富的语义,而这些语义是非常耗性能的:另一方面,大多数应用都是读多写少,所以加剧了慢查询的问题. 分布式系统中远程调用也会耗很多性能,

  • spring boot的拦截器简单使用示例代码

    1.spring boot拦截器默认有: HandlerInterceptorAdapter AbstractHandlerMapping UserRoleAuthorizationInterceptor LocaleChangeInterceptor ThemeChangeInterceptor 其中 LocaleChangeInterceptor 和 ThemeChangeInterceptor 比较常用. 2.实现自定义拦截器只需要3步: 1).创建我们自己的拦截器类并实现 Handler

  • spring boot配置拦截器代码实例

    这篇文章主要介绍了spring boot配置拦截器代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 首先引入web模块的依赖: 复制代码 <!-- spring boot web 组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</ar

  • Spring Boot配置拦截器及实现跨域访问的方法

    拦截器功能强大,能够深入方法前后,常应用于日志记录.权限检查和性能检测等,几乎是项目中不可或缺的一部分,本文就来实现Spring Boot自定义拦截器的配置. 理论指导 问:Spring Boot怎么配置拦截器? 答:配置一个拦截器需要两步完成. 自定义拦截器,实现HandlerInterceptor这个接口.这个接口包括三个方法,preHandle是请求执行前执行的,postHandler是请求结束执行的,但只有preHandle方法返回true的时候才会执行,afterCompletion是

  • spring boot加入拦截器Interceptor过程解析

    这篇文章主要介绍了spring boot加入拦截器Interceptor过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.spring boot拦截器默认有 HandlerInterceptorAdapter AbstractHandlerMapping UserRoleAuthorizationInterceptor LocaleChangeInterceptor ThemeChangeInterceptor 2.配置spring

  • Spring中自定义拦截器的使用

    1.创建自定义拦截器类(UserTokenInterceptor)并实现HandlerInterceptor 接口,再重写方法,代码如下: public class UserTokenInterceptor implements HandlerInterceptor { /** * @description 访问Controller之前执行 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletR

  • 详解Spring MVC的拦截器与异常处理机制

    目录 1.SpringMVC拦截器 1.1拦截器(interceptor)的作用 1.2拦截器和过滤器的区别 1.3拦截器的快速入门 1.4多拦截器操作 1.5拦截器方法说明 2.SpringMVC异常处理 2.1异常处理的思路 2.2异常处理的两种方式 2.3简单的异常处理器SimpleMappingExceptinResolver 2.4自定义异常处理步骤 总结 1. SpringMVC拦截器 1.1 拦截器(interceptor)的作用 Spring MVC 的拦截器类似于 Servle

随机推荐