當springMVC上下文尚未初始化的時候如何@Autowired注入對象呢?

一個問題困擾了我一天,場景是這樣的:

  • 公司有一個獨立的SSO用戶權限驗證中心,我負責的是公司的一個其他的獨立項目;
  • 每次用戶session過期或者未登錄的時候跳統一登錄頁面;
  • 用戶成功登錄之後都會回調,回調的信息中有用戶的userAccount;
  • 此時需要根據用戶的userAccount獲取用戶的詳細信息;
  • 權限系統提供了一個獲取用戶的接口;

遇到的問題:

  • 使用的是shrio進行系統權限的控制,當用戶在SSO登錄頁面成功登錄之後會回調到shrio的配置類中;
  • 需要在shrio的配置類中調用根據用戶賬號獲取用戶信息的接口:
public class IecWepmShiroAuthService extends AbstractShiroAuthService<SessionUser> implements EnvironmentAware {

    @Autowired
    private TenantUserCloudService tenantUserCloudService;
}

調用的就是這麼一個接口

@CloudServiceClient("gap-service-tenant-auth")
public interface TenantUserCloudService {

    @RequestMapping(
        value = {"/tenant/user/by-emp-no"},
        method = {RequestMethod.GET}
    )
    APIResponse<TenantUser> getTenantUserByEmpNo(@RequestParam(name = "tenantId",required = true) Integer var1, @RequestParam(name = "empNo",required = true) String var2);
}

如果按照下面的代碼進行注入,那麼由於shrio的初始化要先與springMVC,所以導致找不到相關處理的類;


@Autowired
    private TenantUserCloudService tenantUserCloudService;

解決的思路就是:

  • 首先不進行tenantUserCloudService的初始化,只有在調用該接口的時候進行初始化;
  • 創建一個類實現ApplicationContextAware接口,實現這個接口可以很方便的從spring容器中獲取bean;
  • 機制就是當調用這個接口的時候才從spring容器中獲取這個bean

創建一個類實現ApplicationContextAware接口:

//相當於給一個bean進行代理
public class DelegateBean implements ApplicationContextAware {
    private static final Logger logger = LoggerFactory.getLogger(DelegateBean.class);
    protected ApplicationContext applicationContext;
    protected Object target;
    protected String targetBeanName;
    protected Class targetBeanType;

    public DelegateBean(String targetBeanName) {
        this.targetBeanName = targetBeanName;
    }

    public DelegateBean(Class targetBeanType) {
        this.targetBeanType = targetBeanType;
    }

    public DelegateBean(ApplicationContext applicationContext, String targetBeanName) {
        this.applicationContext = applicationContext;
        this.targetBeanName = targetBeanName;
    }

    public DelegateBean(ApplicationContext applicationContext, Class targetBeanType) {
        this.applicationContext = applicationContext;
        this.targetBeanType = targetBeanType;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    //當調用這個方法的時候才從spring容器中獲取bean;
    public Object target() {
        Assert.notNull(this.applicationContext, "A DelegateBean should be managed by ApplicationContext or pass ApplicationContext though constructor arg");
        if(this.target == null) {
            synchronized(this) {
                return this.target != null?this.target:(this.target = this.doGetBeanFromApplicationContext());
            }
        } else {
            return this.target;
        }
    }

    protected Object doGetBeanFromApplicationContext() {
        return this.targetBeanName != null?this.applicationContext.getBean(this.targetBeanName):(this.targetBeanType != null?this.applicationContext.getBean(this.targetBeanType):null);
    }
}

只是這樣配置還是不夠的,我們需要在spring初始化的時候將這個代理bean交給spring容器進行管理;

@Configuration
public class IecWepmAutoConfiguration{

    @Bean
    @Qualifier("tenantUserCloudService")
    public DelegateBean tenantUserCloudService(){
        return new DelegateBean(TenantUserCloudService.class);
    }
}

然後再shrio的類中我們需要進行這樣的使用:

public class IecWepmShiroAuthService extends AbstractShiroAuthService<SessionUser> implements EnvironmentAware {
    @Autowired
    @Qualifier("tenantUserCloudService")
    private DelegateBean tenantUserCloudService;

    @Override
    public SessionUser doLogin(String userAccount, String userPwd) {

        //根據用戶編號獲取用戶信息,調用target()是關鍵,只有在調用這個方法的時候纔會從spring容器中獲取信息
        APIResponse<TenantUser> tenantUserByEmpNo = ((TenantUserCloudService) tenantUserCloudService.target())
                .getTenantUserByEmpNo(TENANT_ID, userAccount);
        if ("success".equals(tenantUserByEmpNo.getCode()) && null != tenantUserByEmpNo.getData()){
            userAccount = tenantUserByEmpNo.getData().getDomainAccountList().get(0).getDomainAccount();
        }
        UserInfoDto userInfo = userService.getUserByAccount(userAccount);
        SessionUser sessionUser = new SessionUser();
        sessionUser.setUserId(Integer.valueOf(userAccount));
        AuthUtils.setSessionUser(sessionUser);
        // todo 返回的SessionUser 就是保存在session裏的對象 通過 SessionUser sessionUser = (SessionUser) AuthUtils.getSessionUser(); 進行獲取
        return sessionUser;
    }
}

關鍵: 以上的思路最主要的就是,在shrio初始化的時候僅僅只是初始化一個空殼,只有當使用那個bean的時候才從spring容器中獲取bean並且注入,這樣的好處就是噹噹前bean的聲明週期還未開始的時候預留一個位置,當使用的時候才從spring容器中注入,這樣不會導致項目啓動的時候就會找不到bean

發佈了84 篇原創文章 · 獲贊 551 · 訪問量 129萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章