찾다

 >  Q&A  >  본문

java - 为什么当前配置下,定义在service层的aop不生效?

我定义了controller层的aop,以及service等的aop,但是service层的不生效?我用Test类直接getBean,调用userService.xx(),service层aop生效,但是在web项目中就是不生效。我初步猜测是配置或者说加载顺序问题?

新的补充:

反复测试发现,当quartz与shiro同时使用时,service层的aop就会失效。目前不是太过确定原因,但是一种猜测是shiro自带的quartz与quartz.jar冲突导致的。但是问题来了,为什么他们冲突导致service层的aop失效呢?而controller层不失效?

spring-web.xml

<?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>

spring-service.xml

<?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>

BindingResultAop.java

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();
    }
}

ClearCacheAop.java

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);
        }
    }
}

spring-shiro.xml

<?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>

spring-quartz.xml

<?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>
天蓬老师天蓬老师2769일 전880

모든 응답(2)나는 대답할 것이다

  • 巴扎黑

    巴扎黑2017-04-18 10:27:42

    스프링 컨테이너의 스캐닝 범위에 문제가 있을 것입니다
    <!-- 컴포넌트 스캐닝 기능을 활성화하고 aop의 관련 컴포넌트를 스캔합니다 -->

    으아악

    service.xml에 이 문장을 추가해 보세요
    보충 내용: 다른 구성 파일에서 해당 내용을 제거하세요

    회신하다
    0
  • PHP中文网

    PHP中文网2017-04-18 10:27:42

    반복적인 테스트 결과 quartz와 shiro를 동시에 사용할 경우 서비스 계층의 AOP가 실패하는 것으로 나타났습니다. 그 이유는 아직 확실하지 않으나, Shiro에 포함된 Quartz와 quartz.jar의 충돌로 인해 발생하는 것으로 추측됩니다. 그러나 질문이 생깁니다. 충돌로 인해 서비스 계층의 AOP가 실패하는 이유는 무엇입니까? 그리고 컨트롤러 레이어는 실패하지 않나요?

    회신하다
    0
  • 취소회신하다