Spring Security循环bean依赖

时间:2016-11-19 17:33:24

标签: java spring spring-security vaadin spring-jdbc

我目前正在研究Vaadin spring应用程序。我唯一能说的是,用户的身份验证/授权必须通过$visitor_ip = getUserIP(); if(!isset($db)) $db = connect_db(); // connecting to the databaste with PDO $userName = $_SESSION['username']; $query = "SELECT entries from visitorsCounter WHERE ip=:ip"; $stmt = $db->prepare ( $query ); $stmt->bindParam ( ":ip", $visitor_ip); $stmt->execute (); $result = $stmt->fetch ( PDO::FETCH_ASSOC ); if($result != null){ // **** if there isn't a row with that IP **** $addEntryQuery = "UPDATE visitorsCounter SET entries = entries + 1 WHERE ip=:ip"; $stmt = $db->prepare ( $addEntryQuery ); $stmt->bindParam ( ":ip", $visitor_ip); $stmt->execute (); } 查询数据库来完成。如何解决这个问题?我使用的是Spring Boot 1.4.2.RELEASE。

    $userName = $_SESSION['username'];
$query = "INSERT INTO visitorsCounter(ip, entries, user)
    VALUES (:ip, 1,:user)
    ON DUPLICATE KEY UPDATE entries = entries + 1;
        ";
$stmt = $db->prepare ( $query );
$stmt->bindParam ( ":ip", $visitor_ip);
$stmt->bindParam ( ":user", $userName);
$stmt->execute ();


if(isset($_SESSION['username'])){
$addEntryQuery = "UPDATE visitorsCounter SET user = :user WHERE ip=:ip";
$stmt = $db->prepare ( $addEntryQuery );
$stmt->bindParam ( ":ip", $visitor_ip);
$stmt->bindParam ( ":user", $userName);
$stmt->execute ();
} 

原始代码如下所示:

AccountRepository:

jdbcTemplate

JdbcAccountRepository:

Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
|  jdbcAccountRepository defined in file [repositories\JdbcAccountRepository.class]
↑     ↓
|  securityConfiguration.WebSecurityConfig (field services.JdbcUserDetailsServicessecurity.SecurityConfiguration$WebSecurityConfig.userDetailsService)
↑     ↓
|  jdbcUserDetailsServices (field repositories.JdbcAccountRepository services.JdbcUserDetailsServices.repository)
└─────┘

JdbcUserDetailsS​​ervices:

public interface AccountRepository {
    void createAccount(Account user) throws UsernameAlreadyInUseException;
    Account findAccountByUsername(String username);
}

SecurityConfiguration:

@Repository
public class JdbcAccountRepository implements AccountRepository {

    private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    private final JdbcTemplate jdbcTemplate;
    private final PasswordEncoder passwordEncoder;

    @Autowired
    public JdbcAccountRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
        this.jdbcTemplate = jdbcTemplate;
        this.passwordEncoder = passwordEncoder;
    }

    @Transactional
    @Override
    public void createAccount(Account user) throws UsernameAlreadyInUseException {
        try {
            jdbcTemplate.update(
                "insert into Account (firstName, lastName, username, password, role) values (?, ?, ?, ?, ?)",
                user.getFirstName(),
                user.getLastName(),
                user.getUsername(),
                passwordEncoder.encode(
                        user.getPassword()),
                        user.getRole()
            );
        } catch (DuplicateKeyException e) {
            throw new UsernameAlreadyInUseException(user.getUsername());
        }
    }

    @Override
    public Account findAccountByUsername(String username) {
        return jdbcTemplate.queryForObject(
            "select username, password, firstName, lastName, role from Account where username = ?",
            (rs, rowNum) -> new Account(
                    rs.getString("username"),
                    rs.getString("password"),
                    rs.getString("firstName"),
                    rs.getString("lastName"),
                    rs.getString("role")),
            username
        );
    }
}

P.S如果将Spring Boot版本降级为[1.1.5,1.2.0],则不会出现此问题(由于其他依赖性,我必须使用最新版本)

6 个答案:

答案 0 :(得分:24)

您可以将constructor-based dependency injection替换为setter-based dependency injection来解决周期问题,请参阅Spring Framework Reference Documentation

  

循环依赖

     

如果您主要使用构造函数注入,则可以创建无法解析的循环依赖关系场景。

     

例如:A类需要通过构造函数注入实现B类实例,而B类需要通过构造函数注入实现A类实例。如果将A类和B类的bean配置为相互注入,则Spring IoC容器会在运行时检测此循环引用,并抛出BeanCurrentlyInCreationException

     

一种可能的解决方案是编辑由setter而不是构造函数配置的某些类的源代码。或者,避免构造函数注入并仅使用setter注入。换句话说,虽然不推荐使用,但您可以使用setter injection配置循环依赖关系。

     

与典型情况(没有循环依赖关系)不同,bean A和bean B之间的循环依赖关系会强制其中一个bean在完全初始化之前注入另一个bean(一个经典的鸡/蛋场景)。 / p>

答案 1 :(得分:18)

我更喜欢@Lazy方法。这样我就可以坚持一种模式。

请参阅http://www.baeldung.com/circular-dependencies-in-spring

答案 2 :(得分:7)

您的PasswordEncoder bean定义位于WebSecurityConfig中,需要JdbcUserDetailsServicesJdbcUserDetailsServices再次取决于需要JdbcAccountRepository的{​​{1}}。这样就形成了循环。一个简单的解决方案是从PasswordEncoder中取出PasswordEncoder bean定义。甚至在WebSecurityConfig类内部也可以解决循环问题。

答案 3 :(得分:1)

我今天也面临着同样的问题。我在一个类的构造函数中使用了@Lazy,它解决了我的问题:

lang-java 

public class AService {  
    private BService b;   
    public ApplicantService( @NonNull @Lazy BService b){    
        this.b=b;  
        }
   }  

public class BService {  
    private AService a;   
    public ApplicantService( @NonNull  BService a){     
        this.a=a;  
        }
   } 

答案 4 :(得分:1)

@Zeeshan Adnan 是对的。从 PasswordEncoder 中取出 WebSecurityConfig 解决了循环依赖问题

答案 5 :(得分:0)

解决方案之一是不使用构造函数。例如,而不是:

private final JdbcTemplate jdbcTemplate;
private final PasswordEncoder passwordEncoder;

@Autowired
public JdbcAccountRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
    this.jdbcTemplate = jdbcTemplate;
    this.passwordEncoder = passwordEncoder;
}

您可以使用:

@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private PasswordEncoder passwordEncoder;