Aspect 取消 Spring Controller 的映射。

3
我创建了一个Spring网站。我使用了一个抽象泛型控制器类,并提供不同的实现。如果我没有在任何控制器上激活Aspect类,则它运作良好。
如果我激活了Aspect,那么所有的映射似乎都被禁用了:

DEBUG: org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - 未找到[/contact/2]的处理程序方法
WARN : org.springframework.web.servlet.PageNotFound - 没有映射HTTP请求URI为[/clubhelperbackend/contact/2] 的调度Servlet名称'appServlet'

这是我的抽象控制器:
public abstract class AbstractController<T extends Data> implements ClubController<T> {

protected Dao<T> dao;
private Class<T> elementClass;

public AbstractController(Dao<T> dao, Class<T> element) {
    super();
    this.dao = dao;
    this.elementClass = element;
}

@Override
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String getAsView(@PathVariable("id") long id, @RequestParam(required = false) boolean ajax, Model m) {
    String mapping = elementClass.getSimpleName();
    m.addAttribute(mapping, getById(id));
    return mapping + "Get" + (ajax ? "Ajax" : "");
}

@Override
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = "application/json")
public T delete(@PathVariable("id") long id) {
    T obj = getById(id);
    // dao.delete(id);
    return obj;
}
}

一个实现:

@Controller
@RequestMapping("/contact")
public class ContactController extends AbstractController<Contact> {

    @Autowired
    public ContactController(Dao<Contact> contactDao) {
        super(contactDao, Contact.class);
    }

}

这是我的切面(Aspect):
@Aspect
@Component
public class DeletedStorageAspect {
    //
    // private DeletedEntriesDao deletedEntriesDao;
    //
    // @Autowired
    // public DeletedStorageAspect(DeletedEntriesDao deletedEntriesDao) {
    // super();
    // this.deletedEntriesDao = deletedEntriesDao;
    // }

    @Pointcut("execution (public * de.kreth.clubhelperbackend.controller.abstr.AbstractController.delete(..))")
    private void invocation() {
    }

    @AfterReturning(pointcut = "invocation()", returning = "deleted")
    public void storeDeleted(JoinPoint joinPoint, Data deleted) {
        System.out.println("Deleted: " + deleted);
        String tableName = deleted.getClass().getSimpleName();
        long id = deleted.getId();
        Date now = new Date();
        DeletedEntries entry = new DeletedEntries(-1L, tableName, id, now, now);
        System.out.println(entry);
        // deletedEntriesDao.insert(entry);
    }
}

这是我beans.xml文件的一部分:

<aop:aspectj-autoproxy>
    <aop:include name="mysqlDbCheckAspect" />
    <aop:include name="daoLoggerAspect" />
    <aop:include name="controllerSecurityAspect" />
    <aop:include name="deletedStorageAspect" />
</aop:aspectj-autoproxy>

通过注释掉deletedStorageAspect,我可以恢复完整功能。

是什么原因导致这种行为?为什么在映射上使用aspect时无法识别?Controller上不允许使用aspect吗?

希望能得到一些帮助,谢谢。


1
请参考以下链接:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-requestmapping-proxying。简而言之,在`<aop:aspectj-autoproxy />中添加proxy-target-class=true`。 - M. Deinum
@M.Deinum:就是这样!谢谢。我不太明白它的作用。如果你在回答中解释一下,我会接受它。 - Markus Kreth
2个回答

5

在Spring中使用AOP时,默认情况下spring会创建代理。根据您的类是否实现接口,它将创建基于JDK动态代理(基于接口)或基于CGLIB的代理(基于类)。

public abstract class AbstractController<T extends Data> implements ClubController<T> {

在接口代理的情况下(这也适用于您),MVC基础架构无法再看到@RequestMapping注解,也无法再检测到您的映射。由于您正在实现一个接口,所以这种情况也适用于您。另请参见有关使用请求映射代理的参考指南
要解决此问题,必须强制使用基于类的代理,为此,请将proxy-target-class="true"添加到<aop:aspectj-auto-proxy />中。
<aop:aspectj-autoproxy proxy-target-class="true">

我理解的是否正确,删除接口也应该可以工作? - Markus Kreth
理论上来说,那应该可行,因为这样就会创建一个基于类的代理。 - M. Deinum

0

我不是AOP专家。但是通过查看您的代码,我可以说抽象类没有执行,这可能是根本原因。因此,必须修改切入点执行表达式。

解决方案1

如果您没有为子类使用删除签名,则可以轻松地移动到下面的抽象表达式。它只在包内有效。

@Pointcut("execution (public * de.kreth.clubhelperbackend.controller.*.*.delete(..))")
private void invocation() {
}

解决方案2

您可以使用逻辑门来处理这样的表达式

@Pointcut("target(de.kreth.clubhelperbackend.controller.abstr.AbstractController)")
private void anyAbstractOperation() {}

@Pointcut("execution(public * *.delete(..))")
private void anyDeleteOperation() {}


@Pointcut("anyAbstractOperation() && anyDeleteOperation()")
private void invocation() {}

参考:
1.http://docs.spring.io/spring/docs/2.5.x/reference/aop.html#aop-pointcuts-combining

2.http://www.baeldung.com/spring-aop-pointcut-tutorial

3.http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-using-aspectj

关于this和target this限制匹配到bean引用是给定类型的实例的连接点,而target限制匹配到目标对象是给定类型的实例的连接点。前者适用于Spring AOP创建基于CGLIB的代理时,后者用于创建基于JDK的代理。假设目标类实现了一个接口:
public class FooDao implements BarDao {
    ...
}

在这种情况下,Spring AOP 将使用基于 JDK 的代理,您应该使用目标 PCD,因为代理对象将是 Proxy 类的实例并实现 BarDao 接口:
@Pointcut("target(org.baeldung.dao.BarDao)")

另一方面,如果FooDao没有实现任何接口或proxyTargetClass属性设置为true,则代理对象将是FooDao的子类,可以使用这个PCD:
@Pointcut("this(org.baeldung.dao.FooDao)")

如果我移除组件注释,任何切面都不会被触发(或者甚至被实例化?)。我已经尝试过可以作用于其他类的切面。 - Markus Kreth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接