【问题标题】:Spring error "Bean named 'x' is expected to be of type 'y', but was actually of type [com.sun.proxy.$Proxy]"Spring错误“名为'x'的Bean应该是'y'类型,但实际上是[com.sun.proxy.$Proxy]类型”
【发布时间】:2021-03-21 22:27:11
【问题描述】:

我正在尝试使用 Spring Security 在应用程序中实现基于 DAO 的身份验证。

当我尝试使用用户登录应用程序时,出现此错误:

failed to lazily initialize a collection of role: com.intellivest.app.dao.User.groups, could not initialize proxy - no Session

看着@jcmwright80 对this question 的回答,我明白我最好将UserDetailsServiceImpl 类注释为@Transactional。之后我在登录时遇到了错误:

Bean named 'userDetailsService' is expected to be of type 'com.intellivest.app.service.UserDetailsServiceImpl' but was actually of type 'com.sun.proxy.$Proxy238'"}}

这似乎是与在 UserDetailsS​​erviceImpl 上创建的代理对象有关的问题 - 我该如何优雅地解决这个问题?

代码

安全配置的相关部分:

@Configuration
@ComponentScan("com.example.app.service")
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter  {

    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }
    
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

}

UserDetailsS​​erviceImpl.java

@Service
@Transactional
public class UserDetailsServiceImpl implements UserDetailsService{

    public UserDetailsServiceImpl () {};
    
    @Autowired
    private UserDao userDao;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        
        User user = userDao.getUser(username);
        
        if (user == null) {
            throw new UsernameNotFoundException ("User not found.");
        }
        return new UserDetailsImpl(user);
    }
  }

用户.java

@Entity
@Table(name="users",schema="sec")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="userGen")
    @SequenceGenerator(name="userGen", sequenceName="user_id_seq", schema="sec")
    private long id;    

    // Validation constraints on the fields ...
    private String username;
    private String password;
    private boolean enabled;
    
    @ManyToMany
    @JoinTable(name="group_members", schema="sec", joinColumns= { @JoinColumn(name="user_id") }, inverseJoinColumns = { @JoinColumn(name="group_id") } )
    private Set<Group> groups;

 // Getters, Setters etc. ...
 }

(在UserGroup 类中的集合类型字段上使用@ManyToMany(fetch = FetchType.EAGER) 的替代解决方案有效,尽管它可能会影响性能。)

【问题讨论】:

  • 我认为你的代码有问题:你有@Bean UserDetailsService userDetailsService() vs. @Service("userDetailsService") class UserDetailsServiceImpl。看起来有两种名称相同的方法可以用两种不同的类型实例化服务 bean。 @Bean 具有接口类型,这是作为 JDK 代理创建的。在另一个地方,您明确声明 @Service 具有实现类型,这与 bean 工厂方法相矛盾。我认为您需要修复这种不一致,因为它会导致您的问题,如错误消息所示。
  • @kriegaex 实际上这可能与问题有关。当我在SecurityConfig 中将@Bean 的返回类型更改为UserDetailsServiceImpl 时,它对错误给出了更清晰的描述:java.lang.IllegalStateException: @Bean method SecurityConfig.userDetailsService called as bean reference for type [com.intellivest.app.service.UserDetailsServiceImpl] but overridden by non-compatible bean instance of type [com.sun.proxy.$Proxy330]. Spring 建议>“只要你有选择,JDK 动态代理是首选”什么解决方案你建议记住这一点吗?
  • 我不是 Spring 用户。但基本上,在使用 JDK 代理时,您必须确保所有内容都被引用并实例化为接口类型,从类名 com.sun.proxy.$Proxy330 可以看出,这是在您的应用程序某处创建的类型。也许将@EnableAspectJAutoProxy 添加到您的配置类就足够了。或者,您可以通过@EnableAspectJAutoProxy(proxyTargetClass=true) 强制使用 CGLIB 代理。我太忙了,没有时间把你所有的类都复制到一个新项目中去玩,如果你有一个 GitHub 项目我可以看看。

标签: java spring aop


【解决方案1】:

解决方案 1(最佳)

@Service 类中使用的方法都应该在@Service 实现的接口中声明。然后可以在任何地方通过其接口类型引用该服务(例如在@Controller 类中)。这样,Spring 可以继续使用 JDK 动态代理(优于 CGLIB)。

这一点尤其重要,因为实现了 Spring 提供的接口:org.springframework.security.core.userdetails.UserDetailsService,它只声明了 1 个方法,但也需要其他方法。
这与其说是一个简单的编码问题,不如说是一个架构问题。

一步一步:

public interface CustomizedUserDetailsService extends UserDetailsService {
    void add(User user);
}

在服务中实现方法:

@Service
public class UserDetailsServiceImpl implements CustomizedUserDetailsService{
    @Override
    @Transactional
    public void add (User user) {
        //...
    }
}

通过@Configuration中的接口类型引用bean 但返回实现

@Bean
@Primary
public CustomizedUserDetailsService userDetailsService() {
    return new UserDetailsServiceImpl();
}

在使用实现时,通过对其实现的接口的引用来注入 bean:

@Controller
public class UserController {

    private CustomizedUserDetailsService userDetailsService;
    
    @Autowired
    public void setUsersService(CustomizedUserDetailsService customizedUserDetailsService) {
        this.userDetailsService = customizedUserDetailsService;
    }
}

关于代理here的更多详细信息。

解决方案 2

使用注解在服务类上启用 CGLIB 代理。 这需要 spring-aop 和 aspectjweaver 依赖项(不使用 Spring Boot)。

@EnableAspectJAutoProxy(proxyTargetClass=true)

那么安全配置类可以像这样生成bean:

@Bean
public UserDetailsServiceImpl userDetailsService() {
    return new UserDetailsServiceImpl();
}

(DAO层不应该是@Transactional,应该只应用于@Service类或后者类的特定方法。)

【讨论】:

    猜你喜欢
    • 2019-03-07
    • 1970-01-01
    • 2012-01-13
    • 2014-12-09
    • 2016-12-31
    • 2021-07-18
    • 2021-10-17
    • 2018-10-05
    • 2013-01-10
    相关资源
    最近更新 更多