05-HandlerMethod

本文详细解析了Spring MVC框架中的HandlerMethod类,介绍了其封装的目标Bean、方法对象、参数及Bean类型,以及如何通过反射调用目标方法。同时,探讨了其子类InvocableHandlerMethod和ServletInvocableHandlerMethod在请求处理过程中的角色和作用。

HandlerMethod

  • HandlerMethod封装了目标handler处理器方法和目标方法所属的Bean对象,提供了更方便的访问handler参数和返回值的接口。其子类在得到这些属性之后,对外提供了调用目标处理器的方法。

一、属性方法

1.1 主要属性

  • 主要属性是封装里了目标方法、目标Bean对象、参数以及Bean类型等细节信息。
    private final Object bean;

	@Nullable
	private final BeanFactory beanFactory;   //Bean工厂

	private final Class<?> beanType;    //Bean类型

	private final Method method;    //方法对象

	private final Method bridgedMethod;

	private final MethodParameter[] parameters; //方法参数

	@Nullable
	private HttpStatus responseStatus;

	@Nullable
	private String responseStatusReason;

	@Nullable
	private HandlerMethod resolvedFromHandlerMethod;

	@Nullable
	private volatile List<Annotation[][]> interfaceParameterAnnotations;

1.2 主要方法

1.2.1 构造方法
  • 构造方法有很多重载的,下面这个构造方法传入的是一个BeanName,Bean工厂和方法对象,后续会根据BeanName从工厂获取真正的Bean对象
	public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
		Assert.hasText(beanName, "Bean name is required");
		Assert.notNull(beanFactory, "BeanFactory is required");
		Assert.notNull(method, "Method is required");
		this.bean = beanName;
		this.beanFactory = beanFactory;
		Class<?> beanType = beanFactory.getType(beanName);
		if (beanType == null) {
			throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
		}
		this.beanType = ClassUtils.getUserClass(beanType);
		this.method = method;
		this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
		this.parameters = initMethodParameters();
		evaluateResponseStatus();
	}
1.2.2 创建对象
  • createWithResolvedBean方法用于创建HandlerMethod对象,从注释能够看出,如果提供的只是一个String类型的BeanName而不是Bean实例,在HandlerMethod创建之前会通过createWithResolvedBean方法获取Bean实例,其实就是从Bean工厂里面获取Bean实例,这个Bean实例就行目标方法所属的Bean,比如我们自己写的控制器类。
	/**
	 * If the provided instance contains a bean name rather than an object instance,
	 * the bean name is resolved before a {@link HandlerMethod} is created and returned.
	 */
	public HandlerMethod createWithResolvedBean() {
		Object handler = this.bean;
		if (this.bean instanceof String) {
			String beanName = (String) this.bean;
			//根据名称获取Bean对象
			handler = this.beanFactory.getBean(beanName);
		}
		return new HandlerMethod(this, handler);
	}
  • 其他很多都是属性获取方法、注解相关获取等方法、另外还有方法参数的辅助类MethodParameter等,细节就不关注了。

二、InvocableHandlerMethod

  • InvocableHandlerMethod是HandlerMethod的子类,我们通过Adapter调用目标执行链,最终通过反射的方式调用目标方法,而这个调用的过程就是通过InvocableHandlerMethod来完成的。
  • 下面是从InvocableHandlerMethod的注释翻译过来
InvocableHandlerMethod 提供了调用目标处理器的方法来处理对应的请求,不过在调用之前需要通
过 HandlerMethodArgumentResolver 来处理好请求的参数值。

参数处理通常需要WebDataBinder来做参数绑定或者类型转换,使用setDataBinderFactory方法来提
供一个binder factory来获取参数解析器。

2.1 主要属性

  • 下面是主要属性,属性相关的setXX方法省略,构造方法省略

    //WebDataBinder工厂,WebDataBinder用于参数绑定和参数类型转换
    private WebDataBinderFactory dataBinderFactory;

    //参数解析器,用于解析参数
	private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();

    //参数名称解析器
    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

2.2 主要方法

2.2.1 invokeForRequest
  • invokeForRequest方法是处理请求的入口,
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        
        //1.获取参数
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		
		//2.调用目标方法
		Object returnValue = doInvoke(args);
		
		//3.返回结果
		return returnValue;
	}
2.2.2 getMethodArgumentValues
  • getMethodArgumentValues用于获取参数,核心代码如下,不具体分析了
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

	    //省略...
		//循环提取参数
		for (int i = 0; i < parameters.length; i++) {
			//省略...
			if (this.argumentResolvers.supportsParameter(parameter)) {
				try {
				    //提取参数
					args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
					continue;
				}
				catch (Exception ex) {
                    //抛出异常
				}
			}
			if (args[i] == null) {
    			//抛出异常
			}
		}
		return args;
	}
  • 下面是调试的业务代码和截图
@RestController
public class TestController {

    @PostMapping(value = "/hello")
    public String hello(@RequestParam("a") String a, @RequestParam("b") String b) {
        System.out.println("--------1----------" + a + " --- " + b);
        return "hello!";
    }
}
  • 参数: 给a和b参数传值分别是:aaaaaa和bbbbbb

在这里插入图片描述

  • 下面是解析后得到的参数

在这里插入图片描述

  • 可以看到,通过getMethodArgumentValues拿到了方法的入参实参,后面就通过doInvoke来调用目标方法了。
  • 这里的提取参数的细节在argumentResolvers.resolveArgument方法,深入需要分析HandlerMethodArgumentResolver接口和其实现类,具体后续文章再分析。
2.2.3 doInvoke
  • doInvoke内部通过反射调用目标方法,
protected Object doInvoke(Object... args) throws Exception {
		//1.使目标方法可被访问
		ReflectionUtils.makeAccessible(getBridgedMethod());
		
		try {
		    //2.调用目标方法,Method.invoke(Object,args)
			return getBridgedMethod().invoke(getBean(), args);
		}
		catch (IllegalArgumentException ex) {
            //各种异常处理,省略...
		}
	}
  • 注意这里getBridgedMethod()会得到一个Method对象,然后Method.invoke(Object,args);
  • 里面的getBean()调用的就是父类的HandlerMethod里面封装的Bean,其实就是我们的目标Bean,args就是方法参数
  • 最后invoke调用就到了Method的invoke方法,代码如下,其实就是通过反射调用目标对象的方法
    @CallerSensitive
    public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor; // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        //反射调用目标对象的方法
        return ma.invoke(obj, args);
    }
  • 这里大体把 InvocableHandlerMethod 的核心方法走读了一遍,内部主要是先提取真正的参数,然后通过反射调用,因为其内部持有目标Bean对象和目标方法Method对象,得到参数之后反射调用就很容易了。

三、ServletInvocableHandlerMethod

  • ServletInvocableHandlerMethod是InvocableHandlerMethod的一个子类,调试过程中真正的对象是 ServletInvocableHandlerMethod实例,invokeForRequest方法也是从ServletInvocableHandlerMethod往父类调用的
    那么 它继承 InvocableHandlerMethod之后又增加了什么特性了?我们先还是看看源码中类的注释:
/**
 * 继承InvocableHandlerMethod,具备通过HandlerMethodReturnValueHandler来处理返回值的能力,
 * 同时支持通过方法级别的注解@ResponseStatus来设置响应状态
 *
 * Extends {@link InvocableHandlerMethod} with the ability to handle return
 * values through a registered {@link HandlerMethodReturnValueHandler} and
 * also supports setting the response status based on a method-level
 * {@code @ResponseStatus} annotation.
 *
 * 
 * 一个空返回值可以解释为请求处理的结束
 * <p>A {@code null} return value (including void) may be interpreted as the
 * end of request processing in combination with a {@code @ResponseStatus}
 * annotation, a not-modified check condition
 * (see {@link ServletWebRequest#checkNotModified(long)}), or
 * a method argument that provides access to the response stream.
 */

3.1 属性方法

  • 属性
//结果处理器,处理目标方法的返回结果
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
  • invokeAndHandle方法是ServletInvocableHandlerMethod在处理请求过程中最重要的方法,请求先被该方法处理,然后才走到父类 InvocableHandlerMethod 的invokeForRequest方法
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
        
        //1.调用父类的invokeForRequest方法得到返回结果,该方法前面已经分析过
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		
		//2.后面就是关于响应状态相关的处理、返回结果的处理了
		setResponseStatus(webRequest);

		if (returnValue == null) {
			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
			mavContainer.setRequestHandled(true);
			return;
		}

		mavContainer.setRequestHandled(false);
		try {
			this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			throw ex;
		}
	}

3.2 内部类ConcurrentResultHandlerMethod

  • ConcurrentResultHandlerMethod是ServletInvocableHandlerMethod的一个内部类,也是ServletInvocableHandlerMethod的子类,按照注释来看是关于异步处理相关的;

四、小结

  • 本文主要介绍HandlerMethod这个类的作用,它内部封装了目标对象Bean,方法对象Method,方法参数、Bean类型和Bean工厂等参数,便于外部直接调用目标方法。
  • 不过在HandlerMethod中只定义了相关的属性和一些属性的读取方法,处理请求的主体逻辑在子类InvocableHandlerMethod的invokeForRequest和doInvoke方法,内部通过反射来调用目标对象的指定方法。
  • 子类ServletInvocableHandlerMethod通过 InvocableHandlerMethod的invokeForRequest和doInvoke方法处理请求,自身会做关于响应状态的操作以及通过结果处理器处理结果等细节。
05-30 14:29:36.486 11192 11192 W System.err: java.lang.IllegalArgumentException 05-30 14:29:36.486 11192 11192 W System.err: at android.os.BinderProxy.transactNative(Native Method) 05-30 14:29:36.486 11192 11192 W System.err: at android.os.BinderProxy.transact(BinderProxy.java:689) 05-30 14:29:36.486 11192 11192 W System.err: at vendor.xiaomi.hardware.misys.common.IMiSysImpl$Stub$Proxy.MiSysWriteFile(IMiSysImpl.java:469) 05-30 14:29:36.486 11192 11192 W System.err: at com.longcheertel.AutoTest.MiSysUtils.writeFileToPersist(MiSysUtils.java:83) 05-30 14:29:36.486 11192 11192 W System.err: at com.longcheertel.AutoTest.AutoTest.checkCamOtp(AutoTest.java:602) 05-30 14:29:36.486 11192 11192 W System.err: at com.longcheertel.AutoTest.AutoTest.-$$Nest$mcheckCamOtp(AutoTest.java:0) 05-30 14:29:36.486 11192 11192 W System.err: at com.longcheertel.AutoTest.AutoTest$MyReceiver.onReceive(AutoTest.java:369) 05-30 14:29:36.486 11192 11192 W System.err: at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0(LoadedApk.java:1880) 05-30 14:29:36.486 11192 11192 W System.err: at android.app.LoadedApk$ReceiverDispatcher$Args.$r8$lambda$mcNAAl1SQ4MyJPyDg8TJ2x2h0Rk(Unknown Source:0) 05-30 14:29:36.486 11192 11192 W System.err: at android.app.LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0) 05-30 14:29:36.486 11192 11192 W System.err: at android.os.Handler.handleCallback(Handler.java:959) 05-30 14:29:36.486 11192 11192 W System.err: at android.os.Handler.dispatchMessage(Handler.java:100) 05-30 14:29:36.486 11192 11192 W System.err: at android.os.Looper.loopOnce(Looper.java:249) 05-30 14:29:36.486 11192 11192 W System.err: at android.os.Looper.loop(Looper.java:337) 05-30 14:29:36.486 11192 11192 W System.err: at android.app.ActivityThread.main(ActivityThread.java:9678) 05-30 14:29:36.486 11192 11192 W System.err: at java.lang.reflect.Method.invoke(Native Method) 05-30 14:29:36.486 11192 11192 W System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:625) 05-30 14:29:36.486 11192 11192 W System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) 05-30 14:29:36.486 2557 3414 W ActivityManager: pid 11192 com.longcheertel.AutoTest sent binder code 4 with flags 0 to frozen apps and got error -22
最新发布
05-31
收到认证请求,路径:/KuCun2/users/login 请求方法:POST Content-Type:application/json Authentication attempt with: 123456_987987 123456 2025-05-29 16:22:32.170 DEBUG 28236 --- [nio-8080-exec-2] org.hibernate.SQL : select * from user where andy=? Hibernate: select * from user where andy=? {id:1, name:超管, andy:123456, pass:$2a$10$JflS0yjBRY6yDRxdhAuHVunetrG2P6q8gj8HQzuaPtW8tt/OqO73S, role:0} 0 [{"authority":"ROLE_ADMIN"}] 0 com.kucun.Config.user.CustomUserDetails@2362529c123456 0 2025-05-29 16:22:34.049 DEBUG 28236 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/index.html", parameters={} 2025-05-29 16:22:34.049 DEBUG 28236 --- [nio-8080-exec-3] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.054 DEBUG 28236 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.138 DEBUG 28236 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/css/util.css", parameters={} 2025-05-29 16:22:34.139 DEBUG 28236 --- [nio-8080-exec-5] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.150 DEBUG 28236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/fonts/font-awesome-4.7.0/css/font-awesome.min.css", parameters={} 2025-05-29 16:22:34.152 DEBUG 28236 --- [nio-8080-exec-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.155 DEBUG 28236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.156 DEBUG 28236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/css/main.css", parameters={} 2025-05-29 16:22:34.158 DEBUG 28236 --- [nio-8080-exec-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.162 DEBUG 28236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.168 DEBUG 28236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/css/index2.css", parameters={} 2025-05-29 16:22:34.169 DEBUG 28236 --- [nio-8080-exec-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.171 DEBUG 28236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.174 DEBUG 28236 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/js/jquery-3.2.1.min.js", parameters={} 2025-05-29 16:22:34.175 DEBUG 28236 --- [nio-8080-exec-6] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.198 DEBUG 28236 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/js/jsyilai.js", parameters={} 2025-05-29 16:22:34.199 DEBUG 28236 --- [nio-8080-exec-8] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.202 DEBUG 28236 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.210 DEBUG 28236 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.225 DEBUG 28236 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.309 DEBUG 28236 --- [nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/main/index.html", parameters={} 2025-05-29 16:22:34.309 DEBUG 28236 --- [nio-8080-exec-9] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.314 DEBUG 28236 --- [nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.319 DEBUG 28236 --- [nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/js/main.js?1748506954294&_=1748506954258", parameters={masked} 2025-05-29 16:22:34.319 DEBUG 28236 --- [nio-8080-exec-9] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.322 DEBUG 28236 --- [nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.324 DEBUG 28236 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/js/index.js?1748506954296&_=1748506954259", parameters={masked} 2025-05-29 16:22:34.324 DEBUG 28236 --- [io-8080-exec-10] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.326 DEBUG 28236 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.456 DEBUG 28236 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/check-session", parameters={} 2025-05-29 16:22:34.457 DEBUG 28236 --- [nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.kucun.Config.SecurityConfig$SessionCheckController#checkSession(HttpServletRequest) 2025-05-29 16:22:34.473 DEBUG 28236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/main/bootstrap-3.3.7-dist/js/MyTable.js", parameters={} 2025-05-29 16:22:34.473 DEBUG 28236 --- [nio-8080-exec-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.476 DEBUG 28236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.493 DEBUG 28236 --- [nio-8080-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor : No match for [*/*], supported: [] 2025-05-29 16:22:34.494 DEBUG 28236 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.628 DEBUG 28236 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/login.html", parameters={} 2025-05-29 16:22:34.628 DEBUG 28236 --- [nio-8080-exec-4] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.631 DEBUG 28236 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.744 DEBUG 28236 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/fonts/font-awesome-4.7.0/css/font-awesome.min.css", parameters={} 2025-05-29 16:22:34.746 DEBUG 28236 --- [nio-8080-exec-7] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.747 DEBUG 28236 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/css/util.css", parameters={} 2025-05-29 16:22:34.748 DEBUG 28236 --- [nio-8080-exec-8] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.758 DEBUG 28236 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/js/jquery-3.2.1.min.js", parameters={} 2025-05-29 16:22:34.758 DEBUG 28236 --- [nio-8080-exec-5] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.777 DEBUG 28236 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/css/main.css", parameters={} 2025-05-29 16:22:34.778 DEBUG 28236 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.779 DEBUG 28236 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/js/jsyilai.js", parameters={} 2025-05-29 16:22:34.780 DEBUG 28236 --- [nio-8080-exec-2] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.779 DEBUG 28236 --- [nio-8080-exec-6] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.782 DEBUG 28236 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.786 DEBUG 28236 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.787 DEBUG 28236 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.799 DEBUG 28236 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.854 DEBUG 28236 --- [nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/images/bg-01.jpg", parameters={} 2025-05-29 16:22:34.855 DEBUG 28236 --- [nio-8080-exec-9] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.859 DEBUG 28236 --- [nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.905 DEBUG 28236 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/js/main.js?1748506954900&_=1748506954847", parameters={masked} 2025-05-29 16:22:34.905 DEBUG 28236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/KuCun2/js/login.js?1748506954903&_=1748506954848", parameters={masked} 2025-05-29 16:22:34.907 DEBUG 28236 --- [nio-8080-exec-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.909 DEBUG 28236 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK 2025-05-29 16:22:34.910 DEBUG 28236 --- [io-8080-exec-10] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/jsp/"] 2025-05-29 16:22:34.914 DEBUG 28236 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet : Completed 200 OK package com.kucun.Config; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.json.Json; import javax.servlet.Filter; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.databind.ObjectMapper; import com.kucun.Config.user.CustomUserDetails; // 2. 基础安全配置 @Configuration @EnableWebSecurity // 启用Web安全功能 public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Override public void configure(WebSecurity web) { web.ignoring().antMatchers("/check-session"); } // 添加自定义Controller @RestController public static class SessionCheckController { @GetMapping("/check-session") public ResponseEntity<?> checkSession(HttpServletRequest request) { return request.getSession(false) != null ? ResponseEntity.ok().build() : ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } /** * 核心安全过滤器链配置 * @param http HTTP安全构建器 * @return 安全过滤器链 * @throws Exception 配置异常 * * █ 配置逻辑说明: * 1. authorizeHttpRequests: 定义访问控制规则 * 2. formLogin: 配置表单登录 * 3. logout: 配置注销行为 * 4. exceptionHandling: 处理权限异常[^3] */ // 修正后的配置方法 @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .invalidSessionUrl("/login.html?session=invalid") .maximumSessions(1) .maxSessionsPreventsLogin(false) .and() .and() .addFilterBefore(jsonAuthFilter(), UsernamePasswordAuthenticationFilter.class) // 关键配置 .authorizeRequests() .antMatchers("/login.html", "/users/login").permitAll() .antMatchers("/js/**", "/css/**", "/fonts/**", "/images/**").permitAll() .antMatchers("/users/guanli/**").hasAuthority("ROLE_ADMIN") .anyRequest().authenticated() .and() .formLogin().disable() // .loginPage("/login.html") // .loginProcessingUrl("/users/login") // // .successHandler(ajaxAuthenticationSuccessHandler()) // 自定义成功处理器 // .failureHandler(ajaxAuthenticationFailureHandler()) // 自定义失败处理器 // .defaultSuccessUrl("/index.html") // .failureUrl("/login.html?error=true") // .usernameParameter("andy") // 修改用户名参数名 // .passwordParameter("pass") // 修改密码参数名 // .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login.html") .and() .csrf() .ignoringAntMatchers("/users/login") .and() .headers() .frameOptions().sameOrigin() .and() .exceptionHandling() .accessDeniedHandler(accessDeniedHandler()); // 统一使用Handler } // 返回JSON格式的成功响应 @Bean public AuthenticationSuccessHandler ajaxAuthenticationSuccessHandler() { return (request, response, authentication) -> { // 强制创建服务端会话 request.getSession(true); HttpSession session = request.getSession(true); Cookie cookie = new Cookie("JSESSIONID", session.getId()); cookie.setPath("/KuCun2/"); // 明确指定上下文路径 cookie.setMaxAge(1800); // 30分钟 response.addCookie(cookie); //构建安全响应数据 Map<String, Object> responseData = new HashMap<>(); responseData.put("sessionId", request.getSession().getId()); responseData.put("userInfo",Collections.unmodifiableMap(new HashMap<String, Object>() {/** * */ private static final long serialVersionUID = 1L; { put("Name", ((CustomUserDetails)authentication.getPrincipal()).getName()); put("role", ((CustomUserDetails)authentication.getPrincipal()).getRole()); }})); // 统一返回JSON格式 response.setContentType(MediaType.APPLICATION_JSON_VALUE); // new ObjectMapper().writeValue(response.getWriter(), responseData); response.setContentType(MediaType.APPLICATION_JSON_VALUE); CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); response.setStatus(HttpStatus.OK.value()); System.out.println(authentication.getPrincipal()+""+authentication.getName()); if (request.getHeader("X-Requested-With") == null) { // 非AJAX请求 response.sendRedirect("/index.html"); } else { //String re=userDetails.getUser().toString() new ObjectMapper().writeValue(response.getWriter(), userDetails.getUser() ); } }; } // 返回401状态码和错误信息 @Bean public AuthenticationFailureHandler ajaxAuthenticationFailureHandler() { return (request, response, exception) -> { if (request.getHeader("X-Requested-With") == null) { response.sendRedirect("/login.html?error=true"); } else { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"error\":\"Authentication failed\"}"); } }; } // 处理未认证请求 @Bean public AuthenticationEntryPoint ajaxAuthenticationEntryPoint() { return (request, response, exception) -> { if (request.getHeader("X-Requested-With") == null) { response.sendRedirect("/login.html?error=true"); } else { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"error\":\"Authentication failed\"}"); } }; } @Bean public JsonUsernamePasswordAuthenticationFilter jsonAuthFilter() throws Exception { JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(); filter.setAuthenticationManager(authenticationManagerBean()); filter.setUsernameParameter("andy"); // 设置自定义参数名 filter.setPasswordParameter("pass"); filter.setFilterProcessesUrl("/users/login"); filter.setAuthenticationSuccessHandler(ajaxAuthenticationSuccessHandler()); filter.setAuthenticationFailureHandler(ajaxAuthenticationFailureHandler()); return filter; } /** * 密码编码器(必须配置) * 使用BCrypt强哈希算法加密 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AccessDeniedHandler accessDeniedHandler() { System.out.println("0000"); return (request, response, ex) -> { if (!response.isCommitted()) { response.sendRedirect("/error/403"); } }; } } class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { System.out.println("收到认证请求,路径:" + request.getRequestURI()); System.out.println("请求方法:" + request.getMethod()); System.out.println("Content-Type:" + request.getContentType()); if (request.getContentType() != null && request.getContentType().startsWith(MediaType.APPLICATION_JSON_VALUE)) { try (InputStream is = request.getInputStream()) { Map<String, String> authMap = objectMapper.readValue(is, Map.class); String username = authMap.getOrDefault(getUsernameParameter(), ""); String password = authMap.getOrDefault(getPasswordParameter(), ""); // 调试日志 System.out.println("Authentication attempt with: " + username+'_'+ password); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } catch (IOException e) { throw new AuthenticationServiceException("认证请求解析失败", e); } } Authentication aut= super.attemptAuthentication(request, response); System.out.println("结果:"+aut.isAuthenticated()); return aut; } }
05-30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值