欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

springmvc方法解释器(MultiActionController)使用aop无效

程序员文章站 2023-03-07 20:29:54
springmvc的MultiActionController类是一个年代比较久远的类了,现在几乎没人用了。但公司里有些老项目是这样做的,现在要增加一个日志记录的功能,很自然的想到用aop来做。但是不管是用aspectj还是实现advice还是其他的什么方法都不能生效,而且是既不报错也不生效,很头疼,只能自己找源码分析。通过查看源码发现,MultiActionController的一个最重要的方法是handl...

    springmvc的MultiActionController类是一个年代比较久远的类了,现在几乎没人用了。
    但公司里有些老项目是这样做的,现在要增加一个日志记录的功能,很自然的想到用aop来做。但是不管是用aspectj还是实现advice还是其他的什么方法都不能生效,而且是既不报错也不生效,很头疼,只能自己找源码分析。
通过查看源码发现,MultiActionController的一个最重要的方法是handleRequestInternal,代码如下:

/**
	 * Determine a handler method and invoke it.
	 * @see MethodNameResolver#getHandlerMethodName
	 * @see #invokeNamedMethod
	 * @see #handleNoSuchRequestHandlingMethod
	 */
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		try {
			String methodName = this.methodNameResolver.getHandlerMethodName(request);
			return invokeNamedMethod(methodName, request, response);
		}
		catch (NoSuchRequestHandlingMethodException ex) {
			return handleNoSuchRequestHandlingMethod(ex, request, response);
		}
	}

    看起来invokeNamedMethod方法中会有些线索:

/**
	 * Invokes the named method.
	 * <p>Uses a custom exception handler if possible; otherwise, throw an
	 * unchecked exception; wrap a checked exception or Throwable.
	 */
	protected final ModelAndView invokeNamedMethod(
			String methodName, HttpServletRequest request, HttpServletResponse response) throws Exception {

		Method method = this.handlerMethodMap.get(methodName);
		if (method == null) {
			throw new NoSuchRequestHandlingMethodException(methodName, getClass());
		}

		try {
			Class<?>[] paramTypes = method.getParameterTypes();
			List<Object> params = new ArrayList<Object>(4);
			params.add(request);
			params.add(response);

			if (paramTypes.length >= 3 && paramTypes[2].equals(HttpSession.class)) {
				HttpSession session = request.getSession(false);
				if (session == null) {
					throw new HttpSessionRequiredException(
							"Pre-existing session required for handler method '" + methodName + "'");
				}
				params.add(session);
			}

			// If last parameter isn't of HttpSession type, it's a command.
			if (paramTypes.length >= 3 &&
					!paramTypes[paramTypes.length - 1].equals(HttpSession.class)) {
				Object command = newCommandObject(paramTypes[paramTypes.length - 1]);
				params.add(command);
				bind(request, command);
			}

			Object returnValue = method.invoke(this.delegate, params.toArray(new Object[params.size()]));
			return massageReturnValueIfNecessary(returnValue);
		}
		catch (InvocationTargetException ex) {
			// The handler method threw an exception.
			return handleException(request, response, ex.getTargetException());
		}
		catch (Exception ex) {
			// The binding process threw an exception.
			return handleException(request, response, ex);
		}
	}

    聪明的小伙伴一眼就能看到method对象是从handlerMethodMap中获取的,并且直接拿来被调用了。看起来十有八九问题就出在这个handlerMethodMap上了。他是在哪里注入的呢?ctrl+f找一下就能发现是在构造器里注入的了。

/**
	 * Constructor for {@code MultiActionController} that looks for
	 * handler methods in the present subclass.
	 */
	public MultiActionController() {
		this.delegate = this;
		registerHandlerMethods(this.delegate);
	}
	
	
	/**
	 * Registers all handlers methods on the delegate object.
	 */
	private void registerHandlerMethods(Object delegate) {
		this.handlerMethodMap.clear();
		this.lastModifiedMethodMap.clear();
		this.exceptionHandlerMap.clear();

		// Look at all methods in the subclass, trying to find
		// methods that are validators according to our criteria
		Method[] methods = delegate.getClass().getMethods();
		for (Method method : methods) {
			// We're looking for methods with given parameters.
			if (isExceptionHandlerMethod(method)) {
				registerExceptionHandlerMethod(method);
			}
			else if (isHandlerMethod(method)) {
				registerHandlerMethod(method);
				registerLastModifiedMethodIfExists(delegate, method);
			}
		}
	}

    registerHandlerMethods方法是直接分析委托类(参数delegate),对委托类反射分析,把方法和方法名放进handlerMethodMap里。
但是构造函数里大家看到了,调用registerHandlerMethods方法的时候传递的是this,而不是经过aop增强的代理类,这就是对controller进行增强既不报错又不生效的原因了。

    然而MultiActionController还有一个有参构造器,大家可以看一下这个构造器。

	public MultiActionController(Object delegate) {
		setDelegate(delegate);
	}

	public final void setDelegate(Object delegate) {
		Assert.notNull(delegate, "Delegate must not be null");
		this.delegate = delegate;
		registerHandlerMethods(this.delegate);
		// There must be SOME handler methods.
		if (this.handlerMethodMap.isEmpty()) {
			throw new IllegalStateException("No handler methods in class [" + this.delegate.getClass() + "]");
		}
	}

    有参构造器直接调用了setDelegate,在setDelegate方法也调用了registerHandlerMethods方法。

    看到这里就明白了,无参构造器不支持aop,有参构造器支持aop。

    有参构造器的使用方法如下:

<bean id="menuController" name="/menus.php" class="com.lsp.controller.MenuController"></bean>
    
    <bean class="org.springframework.web.servlet.mvc.multiaction.MultiActionController">
    	<constructor-arg index="0" ref="menuController"></constructor-arg>
    </bean>

    这样就可以正常使用aop了。

    但是这样不太方便,原本写一个bean,现在我要写两个,这里我提供给大家我的解决方案:
1、首先继承MultiActionController类

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;

public class BaseController extends MultiActionController implements BeanFactoryAware{
	
	public BaseController() {
		super();
	}
	
	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		//从spring容器中获取controller
		//因为是从容器中获取的,所以获取到的是AOP增强过的类(如果没有使用AOP当然就是原始类了)
		BaseController controller = beanFactory.getBean(this.getClass());
		//把委托类设置为从容器中获取的controller
		setDelegate(controller);
	}
	
}

2、让原本继承MultiActionController的类改为继承BaseController

public class UsersController extends BaseController {
	...
}

3、设置aop的属性proxy-target-class为true,如果没有设置为true会报错,设置这个属性的意思是使用cglib来做代理,而不是jdk的proxy。xml配置如下:

<aop:config proxy-target-class="true">
		<aop:pointcut expression="execution(* com.lsp.controller.*UsersController.*(..))" id="logPoint"/>
		<aop:advisor advice-ref="logAop" pointcut-ref="logPoint"/>
	</aop:config>

其他的部分是正常来做就行。如果帮到你了还请点个赞哟!

本文地址:https://blog.csdn.net/Hpluvalbe/article/details/107159441