添加 global-method-security 命名空间时 Spring 看不到 @Transactional

Posted

技术标签:

【中文标题】添加 global-method-security 命名空间时 Spring 看不到 @Transactional【英文标题】:Spring doesn't see @Transactional when global-method-security namespace is added 【发布时间】:2012-09-18 11:04:49 【问题描述】:

我创建了一个服务,负责通过 dao 与数据库联系。我使用@Transactional注解来处理事务。

@Service("aclService")
public class HibernateAclServiceImpl implements HibernateAclService

private final Log logger = LogFactory.getLog(HibernateAclServiceImpl.class);
@Autowired
private AclObjectIdentityDao objectIdentityDao ;
private PermissionFactory permissionFactory = new DefaultPermissionFactory();
@Autowired
private AclCache aclCache;
@Autowired
private PermissionGrantingStrategy grantingStrategy;
@Autowired
private AclAuthorizationStrategy aclAuthorizationStrategy;

private final Field fieldAces = FieldUtils.getField(AclImpl.class, "aces");

@Override
@Transactional
public List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) 
    AclObjectIdentity aclObjectIdentity = objectIdentityDao
            .get((Long) parentIdentity.getIdentifier());
    List<ObjectIdentity> list = new ArrayList<ObjectIdentity>(
            aclObjectIdentity.getChildren().size());
    for (AclObjectIdentity aoid : aclObjectIdentity.getChildren()) 
        final ObjectIdentity oid = new ObjectIdentityImpl(aoid.getObjectClass().getClazz());
        list.add(oid);
    
    return list;


@Override
@Transactional
public Acl readAclById(ObjectIdentity object) throws NotFoundException 
    final Map<ObjectIdentity, Acl> objects = readAclsById(Arrays.asList(object), null);
    return objects.get(object);


@Override
@Transactional
public Acl readAclById(ObjectIdentity object, List<Sid> sids)
        throws NotFoundException 
    Map<ObjectIdentity, Acl> objects = readAclsById(Arrays.asList(object), sids);
    return objects.get(object);



@Override
@Transactional
public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects)
        throws NotFoundException 
    return readAclsById(objects, null);


@Override
public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects,
        List<Sid> sids) throws NotFoundException 
    Map<ObjectIdentity, Acl> result = new HashMap<ObjectIdentity, Acl>();
    Set<Long> objectsToLoad = new HashSet<Long>();

    for (int i = 0; i < objects.size(); i++) 
        final ObjectIdentity oid = objects.get(i);
        boolean aclFound = false;

        if (result.containsKey(oid)) 
            aclFound = true;
        

        if (!aclFound) 
            Acl acl = aclCache.getFromCache(oid);

            if (acl != null) 
                if (acl.isSidLoaded(sids)) 
                    result.put(acl.getObjectIdentity(), acl);
                    aclFound = true;
                 else 
                    throw new IllegalStateException(
                            "Error: SID-filtered element detected when implementation does not perform SID filtering "
                                    + "- have you added something to the cache manually?");
                
            
        
        if (!aclFound) 
            objectsToLoad.add((Long) oid.getIdentifier());
        
    

    if (objectsToLoad.size() > 0) 
        lookupAcl(result, objectsToLoad);
    
    return result;

public void lookupAcl(Map<ObjectIdentity, Acl> map, Set<Long> objects)
    final List<AclObjectIdentity> aoids = objectIdentityDao.getList(objects);
    final Map<Long, Long> parents = new HashMap<Long, Long>();
    for(AclObjectIdentity aoid : aoids)
        if(aoid.isEntriesInheriting())
            parents.put(aoid.getId(), aoid.getParent().getId());
        
    
    if(parents.size() > 0)
        lookupAcl(map, (Set<Long>)parents.values());
    
    for(AclObjectIdentity aoid : aoids)
        if(map.containsKey(aoid.getId()))
            continue;
        final Acl parentAcl = map.get(parents.get(aoid.getId()));
        final Acl acl = new AclImpl(new ObjectIdentityImpl(aoid.getObjectClass().getClazz(), aoid.getId()), aoid.getId(), aclAuthorizationStrategy, grantingStrategy, parentAcl, null, aoid.isEntriesInheriting(), new PrincipalSid(aoid.getOwnerSid().getSid()));



        List<AccessControlEntryImpl> aces = new ArrayList<AccessControlEntryImpl>(aoid.getAclEntries().size());
        for(AclEntry aclEntry : aoid.getAclEntries())
            final Permission permission = permissionFactory.buildFromMask(aclEntry.getMask());
            aces.add(new AccessControlEntryImpl(aclEntry.getId(), acl, new PrincipalSid(aclEntry.getSid().getSid()), permission, aclEntry.isGranting(), aclEntry.isAuditSuccess(), aclEntry.isAuditFailure()));
        
        setAces((AclImpl) acl, aces);
        aclCache.putInCache((AclImpl) acl);
    


private void setAces(AclImpl acl, List<AccessControlEntryImpl> aces) 
    try 
        fieldAces.set(acl, aces);
     catch (IllegalAccessException e) 
        throw new IllegalStateException("Could not set AclImpl entries", e);
    

这是我的“app-context.xml”文件的一部分

<security:global-method-security  pre-post-annotations="enabled">
    <security:expression-handler ref="expressionHandler" />
</security:global-method-security>
<bean id="expressionHandler"
    class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
    <property name="permissionEvaluator" ref="permissionEvaluator" />
    <property name="roleHierarchy" ref="roleHierarchy" />
</bean>

<bean class="org.springframework.security.acls.AclPermissionEvaluator"
    id="permissionEvaluator">
    <constructor-arg ref="aclService" />
</bean>

现在当我调用服务的功能时,例如。从控制器它会引发错误org.hibernate.HibernateException: No Session found for current thread。但是当我发表评论时,一切都很好(交易没有问题)

<security:global-method-security pre-post-annotations="enabled"> <security:expression-handler ref="expressionHandler" /> </security:global-method-security>

我检查了所有内容,并将有问题的代码缩小到上述代码。有人知道为什么会这样吗?

【问题讨论】:

您的&lt;tx:annotation-driven transaction-manager="txManager"/&gt; 配置是否正确?查看documentation 了解详细的事务管理器配置。 这取决于你的意思是正确的。我已将其配置为默认值 &lt;tx:annotation-driven/&gt; &lt;bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"&gt;&lt;property name="sessionFactory" ref="sessionFactory" /&gt; 【参考方案1】:

我不确定global-method-security 是如何在幕后实现的,但BeanPostProcessors 有一个鲜为人知的副作用——任何由BeanPostProcessor 直接引用的bean,或由BPP 引用的东西引用, 不符合 AOP 自动代理的条件

BeanPostProcessors 和 AOP 自动代理

实现 BeanPostProcessor 接口的类是特殊的并且被容器区别对待。它们直接引用的所有 BeanPostProcessor 和 bean 都在启动时实例化,作为 ApplicationContext 的特殊启动阶段的一部分。接下来,所有 BeanPostProcessor 都以排序方式注册并应用于容器中的所有其他 bean。因为 AOP 自动代理是作为 BeanPostProcessor 本身实现的,所以 BeanPostProcessor 和它们直接引用的 bean 都没有资格进行自动代理,因此没有将切面编织到其中。

对于任何此类 bean,您应该会看到一条信息性日志消息:“Bean foo 不符合所有 BeanPostProcessor 接口的处理条件(例如:不符合自动代理条件)”。

(source)

这意味着如果您在引用 BeanPostProcessor 加载的 bean 中有 @Transactional,则该注释将被有效地忽略。

解决方案通常是,如果您需要在必须引用 BeanPostProcessor 加载的 bean 中的事务行为,则需要使用非 AOP 事务定义 - 即使用 TransactionTemplate。

您可以将 org.springframework 记录器上的日志记录设置为 DEBUG 并验证是否正在为您的 aclService bean 输出此消息。

【讨论】:

以上是关于添加 global-method-security 命名空间时 Spring 看不到 @Transactional的主要内容,如果未能解决你的问题,请参考以下文章

登录后重定向到登录页面

Spring Security应用开发(19)基于方法的授权AOP

TZ_10_spring-sucrity 服务器和页面的权限控制

Java SSM 项目实战 day08 方法级别的权限操作 服务器端的权限控制(JSR-250注解)(支持表达式的注解)(@Secured)以及页面端的权限控制

jQuery添加插入元素技巧:

Vue 数组添加数组元素和添加单个元素