在改造某旧系统时,遇到的问题及解决方式如下所述:
方式1。BusinessAction直接重写execute的情况
BusinessAction类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的Spring是无法实现AOP切面拦截的。此时,若要被AOP切面拦截,需要默认的构造函数,具体问题及分析见后面分析。
BusinessAction重写execute方法,可以被AOP拦截。
BusinessAction.java
注解AspectJ Bean LogonAspectInterceptor.java如下
appliaction.xml片段
@LogonRequridAnnotation与@Privilege,此处略过。注解加自动代理,精简了很多XML配置,简单的权限管理也采用注解方式。
此时,BusinessAction重写了execute方法,AOP拦截一切正常。
方式2。BusinessAction继承业务父类BaseAction
BaseAction.java如下
BusinessAction.java继承此BaseAction,实现doExecute方法,代码如下:
appliaction.xml的配置无变化。
BusinessAction的doExecute方法能被AOP 切面拦截,此时,如果去掉 BaseAction.execute方法的final关键字,那么AOP将无法拦截BusinessAction的doExecute方法。
开始分析
我们将LOG的级别调整为DEBUG级别,会看到很多细节,对比SPRING的Cglib2AopProxy类,可以通过日志查找到一些端倪。
1模式下,为何需要无参构造函数?
因为其通过有参的构造函数注入了属性,而不是set/get.
首先来看Cglib2AopProxy.java getProxy的完整代码段
其次来看Cglib2AopProxy类的setConstructorArguments方法
最后是Cglib2AopProxy类的getProxy方法产生proay对象的片段
getProxy方法调用时,并没有调用setConstructorArguments方法,Spring通过CGLIB生成代理类对象时,没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,而如果TargetClass没有默认构造函数,异常信息Could not generate CGLIB subclass of class云云就会打印出来了。
2。BaseAction的execute为什么要加final关键字
目标对象targetClass没有变,依然是BusinessAction。很显然,此时若去掉final,拦截BaseAction的execute方法是顺利成章的事,如同方式1中拦截BusinessAction一样,因为他们都override execute方法。但是BusinessAction无法被拦截。
由于BusinessAction的execute为final,此时在Cglib2Proxy.getProxy方法中校验BaseAction的superClass时会产生警告::WARN [org.springframework.aop.framework.Cglib2AopProxy] - Unable to proxy method [public final org.apache.struts.action.ActionForward com.xxx.core.struts.action.BaseAction.execute(org.apache.struts.action.ActionMapping,org.apache.struts.action.ActionForm,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.lang.Exception] because it is final: All calls to this method via a proxy will be routed directly to the proxy.
但是没关系,继承父类的子类的方法拦截正常。
Cglib2Proxy.getProxy中校验代码块
validateClassIfNecessary代码块
警告来源:doValidateClass
方式1。BusinessAction直接重写execute的情况
BusinessAction类没有实现任何接口,并且没有默认构造函数的情况下,通过构造函数注入时,目前的Spring是无法实现AOP切面拦截的。此时,若要被AOP切面拦截,需要默认的构造函数,具体问题及分析见后面分析。
BusinessAction重写execute方法,可以被AOP拦截。
BusinessAction.java
public class BusinessAction extends Action{
private String name;
public BusinessAction (){}
public BusinessAction (String name){
this.name = name;
}
@Override
@LogonRequridAnnotation("这是Aspect的pointcut")
@Privilege(userType=PrivilegeType.Admin,message="需管理员权限!")
public ActionForward execute(ActionMapping mapping,ActionForm actionform,
HttpServletRequest request,HttpServletResponse response) throws Exception {
//代码略
}
}
注解AspectJ Bean LogonAspectInterceptor.java如下
@Aspect
public class LogonAspectInterceptor implements Ordered {
private int order = 1;
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Pointcut("@annotation (LogonRequridAnnotation)")
public void pointCutMethod() {
}
@Around("pointCutMethod()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
doSomething();
return (ActionForward)pjp.proceed();
}
}
appliaction.xml片段
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean name="/BusinessActionPath"
class="com.xxx.core.struts.action.BusinessAction"
scope="prototype">
<!-- AspectJ Bean -->
<bean id="logonRequired" class="com.megaeyes.msp.plugins.commons.interceptor.LogonAspectInterceptor">
<property name="order" value="100"></property>
</bean>
<!--自动代理 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
@LogonRequridAnnotation与@Privilege,此处略过。注解加自动代理,精简了很多XML配置,简单的权限管理也采用注解方式。
此时,BusinessAction重写了execute方法,AOP拦截一切正常。
方式2。BusinessAction继承业务父类BaseAction
BaseAction.java如下
public abstract class BaseAction extends Action {
public BaseAction () {}
@Override
public final ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
try {
doSomeThing();
return doExecute(mapping, form, request, response);
} catch (Exception ex) {
ex.printStackTrace();
log.error(ex);
return doException(ex, mapping, form, request, response);
}
}
public abstract ActionForward doException(Exception ex, ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) {
return mapping.findForward("ErrorsPage");
}
public abstract ActionForward doExecute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws Exception;
}
BusinessAction.java继承此BaseAction,实现doExecute方法,代码如下:
public class BusinessAction extends BaseAction{
@Override
@LogonRequridAnnotation("这是Aspect的pointcut")
@Privilege(userType=PrivilegeType.Admin,message="需管理员权限!")
public ActionForward doExecute(ActionMapping mapping,ActionForm actionform,
HttpServletRequest request,HttpServletResponse response) throws Exception {
//代码略
}
}
appliaction.xml的配置无变化。
BusinessAction的doExecute方法能被AOP 切面拦截,此时,如果去掉 BaseAction.execute方法的final关键字,那么AOP将无法拦截BusinessAction的doExecute方法。
开始分析
我们将LOG的级别调整为DEBUG级别,会看到很多细节,对比SPRING的Cglib2AopProxy类,可以通过日志查找到一些端倪。
1模式下,为何需要无参构造函数?
因为其通过有参的构造函数注入了属性,而不是set/get.
首先来看Cglib2AopProxy.java getProxy的完整代码段
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating CGLIB2 proxy: target source is " + this.advised.getTargetSource());
}
try {
Class rootClass = this.advised.getTargetClass();
Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
Class proxySuperClass = rootClass;
if (AopUtils.isCglibProxyClass(rootClass)) {
proxySuperClass = rootClass.getSuperclass();
Class[] additionalInterfaces = rootClass.getInterfaces();
for (int i = 0; i < additionalInterfaces.length; i++) {
Class additionalInterface = additionalInterfaces[i];
this.advised.addInterface(additionalInterface);
}
}
// Validate the class, writing log messages as necessary.
validateClassIfNecessary(proxySuperClass);
// Configure CGLIB Enhancer...
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class));
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setInterceptDuringConstruction(false);
Callback[] callbacks = getCallbacks(rootClass);
enhancer.setCallbacks(callbacks);
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
Class[] types = new Class[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
enhancer.setCallbackTypes(types);
// Generate the proxy class and create a proxy instance.
Object proxy;
if (this.constructorArgs != null) {
proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
}
else {
proxy = enhancer.create();
}
return proxy;
}
catch (CodeGenerationException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of class [" +
this.advised.getTargetClass() + "]: " +
"Common causes of this problem include using a final class or a non-visible class",
ex);
}
catch (IllegalArgumentException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of class [" +
this.advised.getTargetClass() + "]: " +
"Common causes of this problem include using a final class or a non-visible class",
ex);
}
catch (Exception ex) {
// TargetSource.getTarget() failed
throw new AopConfigException("Unexpected AOP exception", ex);
}
}
其次来看Cglib2AopProxy类的setConstructorArguments方法
public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) {
if (constructorArgs == null || constructorArgTypes == null) {
throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified");
}
if (constructorArgs.length != constructorArgTypes.length) {
throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length +
") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")");
}
this.constructorArgs = constructorArgs;
this.constructorArgTypes = constructorArgTypes;
}
最后是Cglib2AopProxy类的getProxy方法产生proay对象的片段
// Generate the proxy class and create a proxy instance.
Object proxy;
if (this.constructorArgs != null) {
proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
}
else {
proxy = enhancer.create();
}
getProxy方法调用时,并没有调用setConstructorArguments方法,Spring通过CGLIB生成代理类对象时,没有将目标对象的构造函数的参数及其类型进行设定,导致了CGLIB在生成代理类对象时,会使用默认的构造函数生成,而如果TargetClass没有默认构造函数,异常信息Could not generate CGLIB subclass of class云云就会打印出来了。
2。BaseAction的execute为什么要加final关键字
目标对象targetClass没有变,依然是BusinessAction。很显然,此时若去掉final,拦截BaseAction的execute方法是顺利成章的事,如同方式1中拦截BusinessAction一样,因为他们都override execute方法。但是BusinessAction无法被拦截。
由于BusinessAction的execute为final,此时在Cglib2Proxy.getProxy方法中校验BaseAction的superClass时会产生警告::WARN [org.springframework.aop.framework.Cglib2AopProxy] - Unable to proxy method [public final org.apache.struts.action.ActionForward com.xxx.core.struts.action.BaseAction.execute(org.apache.struts.action.ActionMapping,org.apache.struts.action.ActionForm,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.lang.Exception] because it is final: All calls to this method via a proxy will be routed directly to the proxy.
但是没关系,继承父类的子类的方法拦截正常。
Cglib2Proxy.getProxy中校验代码块
// Validate the class, writing log messages as necessary.
validateClassIfNecessary(proxySuperClass);
validateClassIfNecessary代码块
private void validateClassIfNecessary(Class proxySuperClass) {
if (logger.isWarnEnabled()) {
synchronized (validatedClasses) {
if (!validatedClasses.containsKey(proxySuperClass)) {
doValidateClass(proxySuperClass);
validatedClasses.put(proxySuperClass, Boolean.TRUE);
}
}
}
}
警告来源:doValidateClass
private void doValidateClass(Class proxySuperClass) {
Method[] methods = proxySuperClass.getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (!Object.class.equals(method.getDeclaringClass()) && Modifier.isFinal(method.getModifiers())) {
logger.warn("Unable to proxy method [" + method + "] because it is final: " +
"All calls to this method via a proxy will be routed directly to the proxy.");
}
}
}