昨天在重構(gòu)項目時 ,抽取了Controller的公共方法到AbstractController中, 調(diào)整了原有Controller的路徑 (包名) . 然后啟動時發(fā)現(xiàn)幾乎所有移動過包的Controller都失效了.
主要對象
- ControllerLogInterceptor : aop攔截器 ,攔截目標(biāo)路徑下所有Controller
@Pointcut("execution(public * com.eddy..*.*Controller.*(..))")
public void handleController() {
}
- AppController : 一個接口
public interface AppController {
String getAppName();
}
- AbstractController : 抽象的Controller ,包含一些公用方法 ;實現(xiàn)了AppController接口
@Slf4j
public abstract class AbstractController implements AppController {
@Autowired
protected HttpSession session;
@Override
public String getAppName() {
return OSC_APPNAME;
}
}
- ContactController : 業(yè)務(wù)邏輯的Controller
@RestController
@RequestMapping("/eddy/contact")
public class ContactController extends AbstractOSCController {
...
}
原因分析
- 因為是移動路徑過后出現(xiàn)的 ,首先考慮路徑影響
- 使用的SpringBoot ,全注解配置的Controller ,路徑并不會影響B(tài)ean的解析
- 同路徑下某一Controller可用 ,該Controller沒有繼承AbstractController ,繼承了另一個類
- 考慮AbstractController的影響
- 改變路徑前也是使用繼承Controller的做法 ,沒有出現(xiàn)問題
- 這個類也在AOP的匹配范圍內(nèi)
- 修改了AbstractController->AbstractAction ,依舊無效
- AOP主要是代理原有Bean ,Abstract不會生成Spring Bean ,不會產(chǎn)生作用
- 終極大招 ,源碼斷點:
- 打印Debug Log : ContactController注冊為Bean ,但沒有掃描到mapping路徑
- 斷點查看Spring如何掃描Controller并添加mapping的
RequestMapping
通過日志 ,找到掃描Mapping的目標(biāo)類RequestMappingHandlerMapping (父對象AbstractHandlerMethodMapping#initHandlerMethods方法)
//AbstractHandlerMethodMapping.class
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
可以看到 ,通過循環(huán)已加載的Bean ,然后通過相應(yīng)Handler (這里是RequestMappingHandler) 判斷是否應(yīng)該處理.
//RequestMappingHandlerMapping.class
@Override
protected boolean isHandler(Class<?> beanType) {
//判斷對應(yīng)class類是否 有Controller注解或繼承自Controller注解 (RestController)
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
這里斷點調(diào)試后 ,發(fā)現(xiàn)ContactController直接跳過 ,它沒有@Controller注解!!!! 但代碼里確實進行了配置 ,在經(jīng)過多次重編譯過后 ... 發(fā)現(xiàn)ContactController對應(yīng)Bean的classType是 com.sun.Proxy...
也就是說SpringAOP代理將這個Bean本有的信息抹除了 ,所以我修改了SpringAOP的匹配路徑 ,果然Controller可用了.
但是AOP部分的代碼也很重要 ,我不希望丟掉.同時我發(fā)現(xiàn)
- 單一的Controller(無繼承關(guān)系) 和 AOP 工作的很好
- 繼承自其他類的Controller也工作的很好
那么 ,Spring的AOP應(yīng)該是不會破壞Bean的類型判斷的 : 他們的類型都是com.eddy.ContactController$Spring..Cglib...
這樣的 ,是能夠通過SpringMVC的檢查的
結(jié)論
最后的不同就在于那個簡單的接口...
- Spring默認使用JDK ,對接口進行動態(tài)代理 : ContactController extends AbstractController implement AppController ,所以將與接口有關(guān)的類都代理成了Proxy .
- 而繼承自單一類/沒有接口的 ,使用cglib代理 ,就不存在這個問題 ,他依舊保留了原有class的信息.
增加配置 spring.aop.proxy-target-class=true ,默認統(tǒng)一使用cglib即可