我定义了controller层的aop,以及service等的aop,但是service层的不生效?我用Test类直接getBean,调用userService.xx(),service层aop生效,但是在web项目中就是不生效。我初步猜测是配置或者说加载顺序问题?
新的补充:
反复测试发现,当quartz与shiro同时使用时,service层的aop就会失效。目前不是太过确定原因,但是一种猜测是shiro自带的quartz与quartz.jar冲突导致的。但是问题来了,为什么他们冲突导致service层的aop失效呢?而controller层不失效?
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--扫描web相关的controller-->
<context:component-scan base-package="com.yingjun.ssm.web"/>
<!-- 激活组件扫描功能,扫描aop的相关组件组件 -->
<context:component-scan base-package="com.yingjun.ssm.aop"/>
<!--启动对@AspectJ注解的支持 , proxy-target-class设置为true,表示通知spring使用cglib而不是jdk的来生成代理方法,
这样AOP可以拦截到Controller -->
<!--写在spring-mvc.xml中-->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!--简化配置:
1、自动注册DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter
2、提供一系列:数据绑定,数字和日期的format,@NumberFormat,@DataTimeFormat,xml,json默认读写支持
-->
<mvc:annotation-driven/>
<!--静态资源默认servlet配置
1、加入对静态资源的处理:js,css,gif,png
2、允许使用"/"做整体映射
-->
<!-- 当在web.xml 中 DispatcherServlet使用 <url-pattern>/</url-pattern> 映射时,能映射静态资源 -->
<mvc:default-servlet-handler/>
<!-- 静态资源映射 -->
<mvc:resources mapping="/static/**" location="/WEB-INF/static/"/>
<!--配置JSP 显示ViewResolver-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--全局异常捕捉 -->
<bean class="com.yingjun.ssm.exception.GlobalExceptionResolver" />
<bean class="com.yingjun.ssm.spring.SpringUtils"/>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--扫描service包(包含子包)下所有使用注解的类型-->
<context:component-scan base-package="com.yingjun.ssm.service"/>
<!--配置事务管理器(mybatis采用的是JDBC的事务管理器)-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置基于注解的声明式事务,默认使用注解来管理事务行为-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
package com.yingjun.ssm.aop;
import com.yingjun.ssm.dto.BaseResult;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
/**
*
* 采用AOP的方式处理@Valid 参数验证。
*/
@Component
@Aspect
public class BindingResultAop {
private static final Logger LOG = LoggerFactory.getLogger(BindingResultAop.class);
@Pointcut("execution(* com.yingjun.ssm.web.*.*(..))")
public void bindingResultPointcut(){
//该方法的内容不重要,该方法的本身只是个标识,供@Pointcut注解依附
}
/**
* Aspect = Advice + Pointcut
* Advice: @Around
* Pointcut: execution(* com.yingjun.ssm.web.*.*(..))
*/
@Around("bindingResultPointcut()")
public Object aroundAdvice(ProceedingJoinPoint jp) throws Throwable{
System.out.println("--->BindingResultAop start...");
String className = jp.getTarget().getClass().getName();
String methodName = jp.getSignature().getName();
LOG.info("before " + className + "." + methodName + "() invoking!");
// 遍历参数,找到BindingResult,判断是否hasError
BindingResult bindingResult = null;
for(Object arg: jp.getArgs()){
if(arg instanceof BindingResult){
bindingResult = (BindingResult) arg;
}
}
if(bindingResult != null){
if(bindingResult.hasErrors()){
LOG.info("--->bindingResult hasError!");
String errorInfo="["+bindingResult.getFieldError().getField()+"]"+bindingResult.getFieldError().getDefaultMessage();
return new BaseResult<Object>(false, errorInfo);
}
}
// 执行目标方法
return jp.proceed();
}
}
package com.yingjun.ssm.aop;
import com.yingjun.ssm.cache.RedisCache;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.asm.AnnotationVisitor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
* 采用AOP的方式处理: XXService关于数据更新(增删改)时,缓存的清理。
*/
@Component
@Aspect
public class ClearCacheAop {
private static final Logger LOG = LoggerFactory.getLogger(ClearCacheAop.class);
private static final String[] UPDATE_USER_MOTHOD = new String[]{"createUser", "updateUser", "deleteUser", "changePassword"};
private static final String[] UPDATE_ROLE_MOTHOD = new String[]{"createRole", "updateRole", "deleteRole"};
private static final String[] UPDATE_RESOURCE_MOTHOD = new String[]{"createResource", "updateResource", "deleteResource"};
@Autowired
private RedisCache cache;
// 声明切入点
@Pointcut("execution(* com.yingjun.ssm.service..*.*(..))")
public void clearCachePointcut(){}
@Before("clearCachePointcut()")
public void beforeAdvice(JoinPoint jp) {
System.out.println("--->clearCachePointcut start...");
String className = jp.getTarget().getClass().getName();
String methodName = jp.getSignature().getName();
LOG.info("before " + className + "." + methodName + "() invoking!");
if(StringUtils.contains(className, "UserService") && ArrayUtils.contains(UPDATE_USER_MOTHOD, methodName)){
// 此时缓存中的数据不是最新的,需要对缓存进行清理(具体的缓存策略还是要根据具体需求制定)
String cache_key = RedisCache.CAHCENAME + "|UserService|*";
cache.deleteCacheWithPattern(cache_key);
LOG.info("aop: delete cache with key: " + cache_key);
} else if(StringUtils.contains(className, "RoleService") && ArrayUtils.contains(UPDATE_ROLE_MOTHOD, methodName)){
String cache_key = RedisCache.CAHCENAME + "|RoleService|*";
cache.deleteCacheWithPattern(cache_key);
LOG.info("aop: delete cache with key: " + cache_key);
} else if(StringUtils.contains(className, "ResourceService") && ArrayUtils.contains(UPDATE_RESOURCE_MOTHOD, methodName)){
String cache_key = RedisCache.CAHCENAME + "|ResourceService|*";
cache.deleteCacheWithPattern(cache_key);
LOG.info("aop: delete cache with key: " + cache_key);
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 缓存管理器 -->
<bean id="cacheManager" class="com.yingjun.ssm.spring.SpringCacheManagerWrapper">
<property name="cacheManager" ref="springCacheManager"/>
</bean>
<!-- 凭证匹配器 -->
<bean id="credentialsMatcher" class="com.yingjun.ssm.credentials.RetryLimitHashedCredentialsMatcher">
<constructor-arg ref="cacheManager"/>
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="2"/>
<property name="storedCredentialsHexEncoded" value="true"/>
</bean>
<!-- Realm实现 -->
<bean id="userRealm" class="com.yingjun.ssm.realm.UserRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
<property name="cachingEnabled" value="false"/>
<!--<property name="authenticationCachingEnabled" value="true"/>-->
<!--<property name="authenticationCacheName" value="authenticationCache"/>-->
<!--<property name="authorizationCachingEnabled" value="true"/>-->
<!--<property name="authorizationCacheName" value="authorizationCache"/>-->
</bean>
<!-- 会话ID生成器 -->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
<!-- 会话Cookie模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="sid"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="180000"/>
</bean>
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="2592000"/><!-- 30天 -->
</bean>
<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!-- rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)-->
<property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
<property name="cookie" ref="rememberMeCookie"/>
</bean>
<!-- 会话DAO -->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean>
<!-- 会话验证调度器 -->
<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
<property name="sessionValidationInterval" value="1800000"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="1800000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<property name="sessionDAO" ref="sessionDAO"/>
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
</bean>
<!-- step1: 配置securityManager,并set给SecurityUtils -->
<!-- 安全管理器 (上面的都是为此处铺垫) -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
<property name="sessionManager" ref="sessionManager"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
<!-- step2: 配置shiroFilter(securityManager+url拦截器) -->
<!-- shiroFilter: shiro启动的核心。web.xml中的DelegatingFilterProxy会寻找Spring容器中的shiroFilter,把所有请求交给他过滤-->
<!-- 基于Form表单的身份验证过滤器 -->
<bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<property name="usernameParam" value="username"/>
<property name="passwordParam" value="password"/>
<property name="rememberMeParam" value="rememberMe"/>
<property name="loginUrl" value="/login"/>
</bean>
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/static/** = anon <!-- 静态资源不拦截 -->
/welcome = anon <!-- 匿名拦截器,定义不拦截的url -->
/login = authc <!-- 直接访问/login,如果之前是'记住我'登录的不算,需要重新登录 -->
/register = anon
/logout = logout <!-- 注销拦截器 -->
/authenticated = authc
/** = user <!-- 所有请求必须通过user拦截器,否则跳转loginUrl(即使用'subject.login'或者'记住我'登录的用户通过) -->
</value>
</property>
</bean>
<!-- step3: 其他配置-->
<!-- Shiro生命周期处理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 定义aop切面,用于代理如@RequiresPermissions注解的控制器,进行权限控制 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd ">
<!--开启这个配置,spring才能识别@Scheduled注解-->
<!--但是和shiro一起使用,service层的aop将失效-->
<!--<task:annotation-driven />-->
<!--<context:annotation-config />-->
<!--<context:component-scan base-package="com.yingjun.ssm.quartz"/>-->
</beans>
스프링 컨테이너의 스캐닝 범위에 문제가 있을 것입니다
으아악<!-- 컴포넌트 스캐닝 기능을 활성화하고 aop의 관련 컴포넌트를 스캔합니다 -->
service.xml에 이 문장을 추가해 보세요
보충 내용: 다른 구성 파일에서 해당 내용을 제거하세요
반복적인 테스트 결과 quartz와 shiro를 동시에 사용할 경우 서비스 계층의 AOP가 실패하는 것으로 나타났습니다. 그 이유는 아직 확실하지 않으나, Shiro에 포함된 Quartz와 quartz.jar의 충돌로 인해 발생하는 것으로 추측됩니다. 그러나 질문이 생깁니다. 충돌로 인해 서비스 계층의 AOP가 실패하는 이유는 무엇입니까? 그리고 컨트롤러 레이어는 실패하지 않나요?