avatar

zian

A text-focused Halo theme

  • Java
  • 面试
  • 首页
  • C语音
  • liunx
  • 数据结构与算法
  • 控制台
Home 若依多表认证
文章

若依多表认证

Posted 2024-11-26 Updated 2024-11- 26
By Administrator
55~71 min read

若依采用的SpringSecurity 作为认证授权框架,SpringSecurity 默认只支持单表的认证授权。

如果需要多表的认证授权的,

实现步骤

1. 创建新的用户表

DROP TABLE IF EXISTS `tb_mobile_user`;
CREATE TABLE `tb_mobile_user`  (
  `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `user_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '用户账号',
  `nick_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '小明' COMMENT '用户昵称',
  `email` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '用户邮箱',
  `sex` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
  `avatar` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT 'https://laf-files.oss-cn-shenzhen.aliyuncs.com/laf-images/2024/11/03/6727302f9d5432242cb05022' COMMENT '头像地址',
  `password` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '' COMMENT '密码',
  `c_id` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '设备id , 每次的登录都需要更新',
  `client_platform` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '设备平台 每次的登录都需要更新',
  `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
  `del_flag` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
  `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建人',
  `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '修改人',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`user_id`) USING BTREE,
  UNIQUE INDEX `uk_username`(`user_name` ASC) USING BTREE COMMENT '用户名唯一',
  FULLTEXT INDEX `idx_email`(`email`)
) ENGINE = InnoDB AUTO_INCREMENT = 27 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户信息表' ROW_FORMAT = DYNAMIC;

SET FOREIGN_KEY_CHECKS = 1;


2. 创建一个新模块 user-modile

在该模块中给为 AuthenticationManager 提供 loadUserByUsername 方法

  /**
     * 根据用户名查询用户
     *
     * @param userName 用户名
     * @return
     */
    @Override
    public MobileUser selectUserByUserName(String userName) {
        return baseMapper.selectUserByUserName(userName);
    } 

3. 在 framework.web.service 模块定义个类实现 UserDetailsService 接口

@Service("mobileUserDetailsServiceImpl") 
public class MobileUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IMobileUserService mobileUserService;

    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        MobileUser mobileUser = mobileUserService.selectUserByUserName(username);
        System.out.println("这是移动端用户:" + mobileUser);
        if (StringUtils.isNull(mobileUser)) {
            log.info("登录用户:{} 不存在.", username);
            throw new ServiceException("登录用户:" + username + " 不存在");
        }

        return createLoginUser(mobileUser);
    }
}

4. 在定义的 LoginUser 类 , 注意实现 UserDetails 的方法全部返回 true ,如果需要实现类似功能,在登陆的时候和进行一系列的判断。


@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginMobileUser implements UserDetails {

    private static final long serialVersionUID = 1L;
    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 用户唯一标识
     */
    private String token;

    /**
     * 过期时间
     */
    private Long expireTime;

    /**
     * 登录时间
     */
    private Long loginTime;

    /**
     * 用户信息
     */
    private MobileUser mobileUser;


    public LoginMobileUser(Long userId, MobileUser mobileUser) {
        this.userId = userId;
        this.mobileUser = mobileUser;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    @JSONField(serialize = false)
    public String getPassword() {
        return mobileUser.getPassword();
    }

    @Override
    public String getUsername() {
        return mobileUser.getUserName();
    }

    /**
     * 账户是否未过期,过期无法验证
     */
    @JSONField(serialize = false)
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     *
     * @return
     */
    @JSONField(serialize = false)
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     *
     * @return
     */
    @JSONField(serialize = false)
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否可用 ,禁用的用户不能身份验证
     *
     * @return
     */
    @JSONField(serialize = false)
    @Override
    public boolean isEnabled() {
        return true;
    }
}

5. 申明对应的 AuthenticationManager Bean 对象对新表的认证和授权

/**
 * spring security配置
 *
 * @author ruoyi
 */
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig {
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    @Qualifier("userDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    @Autowired
    @Qualifier("mobileUserDetailsServiceImpl")
    private UserDetailsService mobileUserDetailsServiceImpl;

    /**
     * 认证失败处理类
     */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出处理类
     */
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * token认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 跨域过滤器
     */
    @Autowired
    private CorsFilter corsFilter;

    /**
     * 允许匿名访问的地址
     */
    @Autowired
    private PermitAllUrlProperties permitAllUrl;

    /**
     * 身份验证实现
     */
    @Bean(name = "userAuthenticationManager")
    @Primary  // 当存在多个同样的bean对象,则通过@Primary来指定当前的bean 是主要的,如果没有明确的指定就是使用该bean
    public AuthenticationManager authenticationManager() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
        return new ProviderManager(daoAuthenticationProvider);
    }


    /**
     * 移动端用户的认证实现
     *
     * @return AuthenticationManager
     */
    @Bean("mobileUserAuthenticationManager")
    public AuthenticationManager MemberAuthenticationManagerBean() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(mobileUserDetailsServiceImpl);
        authenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
        return new ProviderManager(authenticationProvider);
    }


    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                // CSRF禁用,因为不使用session
                .csrf(csrf -> csrf.disable())
                // 禁用HTTP响应标头
                .headers((headersCustomizer) -> {
                    headersCustomizer.cacheControl(HeadersConfigurer.CacheControlConfig::disable).frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin);
                })
                // 认证失败处理类
                .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
                // 基于token,所以不需要session
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 注解标记允许匿名访问的url
                .authorizeHttpRequests((requests) -> {
                    permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
                    // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                    requests.antMatchers(
                                    "/login",
                                    "/register",
                                    "/user/users/login",
                                    "/user/users/register",
                                    "/user/users/resetPwd",
                                    "/user/categorys/tree/list",
                                    "/user/lostItem/wait-picked/page",
                                    "/captchaImage",
                                    "/register/sendSms",
                                    "/pwd/sendSms",
                                    "/check/smsCode",
                                    "/common/upload",
                                    "/common/uploads",
                                    "/common/download"
                            ).permitAll()
                            // 静态资源,可匿名访问
                            .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                            .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
                            // 除上面外的所有请求全部需要鉴权认证
                            .anyRequest().authenticated();
                })
                // 添加Logout filter
                .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
                // 添加JWT filter
                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                // 添加CORS filter
                .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
                .addFilterBefore(corsFilter, LogoutFilter.class)
                .build();
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

6. 修改 TokenService , 添加专门为 user-mobile 表的相关方法 , 如果的 user-mobile 表的不需要添加 Token 前缀 Bearer , 在过滤器用户与区分登录用户查询的表的


/**
 * token验证处理
 *
 * @author ruoyi
 */
@Component
public class TokenService
{
    private static final Logger log = LoggerFactory.getLogger(TokenService.class);

    // 令牌自定义标识
    @Value("${token.header}")
    private String header;

    // 令牌秘钥
    @Value("${token.secret}")
    private String secret;

    // 令牌有效期(默认30分钟)
    @Value("${token.expireTime}")
    private int expireTime;

    protected static final long MILLIS_SECOND = 1000;

    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

    @Autowired
    private RedisCache redisCache;

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request)
    {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token))
        {
            try
            {
                Claims claims = parseToken(token);
                // 解析对应的权限以及用户信息
                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
                String userKey = getTokenKey(uuid);
                LoginUser user = redisCache.getCacheObject(userKey);
                return user;
            }
            catch (Exception e)
            {
                log.error("获取用户信息异常'{}'", e.getMessage());
            }
        }
        return null;
    }


    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginMobileUser getLoginMobileUser(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = getMobileToken(request);
        if (StringUtils.isNotEmpty(token)) {
            try {
                Claims claims = parseToken(token);
                // 解析对应的权限以及用户信息
                String uuid = (String) claims.get(Constants.LOGIN_MOBILE_USER_KEY);
                String userKey = getTokenMobileKey(uuid);
                LoginMobileUser user = redisCache.getCacheObject(userKey);
                return user;
            } catch (Exception e) {
                log.error("获取用户信息异常'{}'", e.getMessage());
            }
        }
        return null;
    }




    /**
     * 设置用户身份信息
     */
    public void setLoginUser(LoginUser loginUser)
    {
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
        {
            refreshToken(loginUser);
        }
    }

    /**
     * 设置用户身份信息
     */
    public void setLoginMobileUser(LoginMobileUser loginMobileUser) {
        if (StringUtils.isNotNull(loginMobileUser) && StringUtils.isNotEmpty(loginMobileUser.getToken())) {
            refreshTokenMobile(loginMobileUser);
        }
    }


    /**
     * 删除用户身份信息
     */
    public void delLoginUser(String token)
    {
        if (StringUtils.isNotEmpty(token))
        {
            String userKey = getTokenKey(token);
            redisCache.deleteObject(userKey);
        }
    }


    /**
     * 删除用户身份信息
     */
    public void delLoginMobileUser(String token) {
        if (StringUtils.isNotEmpty(token)) {
            String userKey = getTokenMobileKey(token);
            redisCache.deleteObject(userKey);
        }
    }

    /**
     * 创建令牌
     *
     * @param loginUser 用户信息
     * @return 令牌
     */
    public String createToken(LoginUser loginUser)
    {
        String token = IdUtils.fastUUID();
        loginUser.setToken(token);
        setUserAgent(loginUser);
        refreshToken(loginUser);

        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_USER_KEY, token);
        return createToken(claims);
    }

    /**
     * 创建令牌 移动端的
     *
     * @param loginMobileUser
     * @return
     */
    public String createTokenMobile(LoginMobileUser loginMobileUser) {
        String token = IdUtils.fastUUID();
        loginMobileUser.setToken(token);
        refreshTokenMobile(loginMobileUser);
        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_MOBILE_USER_KEY, token);
        return createToken(claims);
    }


    /**
     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
     *
     * @param loginUser
     * @return 令牌
     */
    public void verifyToken(LoginUser loginUser)
    {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
        {
            refreshToken(loginUser);
        }
    }

    /**
     * 移动端 验证令牌有效期,相差不足20分钟,自动刷新缓存
     *
     * @param loginMobileUser
     */
    public void verifyTokenMobile(LoginMobileUser loginMobileUser) {
        long expireTime = loginMobileUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
            refreshTokenMobile(loginMobileUser);
        }
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser)
    {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }

    /**
     * 移动端 刷新令牌有效期
     *
     * @param loginMobileUser 登录信息
     */
    public void refreshTokenMobile(LoginMobileUser loginMobileUser) {
        loginMobileUser.setLoginTime(System.currentTimeMillis());
        loginMobileUser.setExpireTime(loginMobileUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getTokenMobileKey(loginMobileUser.getToken());
        redisCache.setCacheObject(userKey, loginMobileUser, expireTime, TimeUnit.MINUTES);
    }


    /**
     * 设置用户代理信息
     *
     * @param loginUser 登录信息
     */
    public void setUserAgent(LoginUser loginUser)
    {
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = IpUtils.getIpAddr();
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOperatingSystem().getName());
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String createToken(Map<String, Object> claims)
    {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims parseToken(String token)
    {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUsernameFromToken(String token)
    {
        Claims claims = parseToken(token);
        return claims.getSubject();
    }

    /**
     * 获取请求token
     *
     * @param request
     * @return token
     */
    private String getToken(HttpServletRequest request)
    {
        String token = request.getHeader(header);
        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
        {
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        return token;
    }


    /**
     * 获取移动端 token ,移动端token,不携带前缀
     *
     * @param request
     * @return token
     */
    private String getMobileToken(HttpServletRequest request) {
        return request.getHeader(header);
    }


    private String getTokenKey(String uuid)
    {
        return CacheConstants.LOGIN_TOKEN_KEY + uuid;
    }

    /**
     * 获取移动端tokenKey
     *
     * @param uuid
     * @return
     */
    private String getTokenMobileKey(String uuid) {
        return CacheConstants.LOGIN_MOBILE_TOKEN_KEY + uuid;
    }

}

7. 定义登录接口

 /**
     * 移动端用户登录验证
     *
     * @param username 账号
     * @param password 密码
     * @param code     验证码
     * @param uuid     uuid
     * @return
     */
    public String login(String username, String password, String code, String uuid) {
        // 验证码校验
        validateCaptcha(username, code, uuid);
        // 登录前置校验
        loginPreCheck(username, password);
        Authentication authenticate;
        try {
            // 去到  mobileUserDetailsServiceImpl 调用 loadUserByUsername 方法 TODO 登录错误
            authenticate = mobileUserAuthenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                throw new UserPasswordNotMatchException();
            } else {
                throw new ServiceException("登录失败:" + e.getMessage());
            }
        }
        LoginMobileUser curUser = (LoginMobileUser) authenticate.getPrincipal();
        return tokenService.createTokenMobile(curUser);
    }

8. 修改过滤器


/**
 * token过滤器 验证token有效性
 *
 * @author ruoyi
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{

    @Autowired
    private TokenService tokenService;

    // 令牌自定义标识
    @Value("${token.header}")
    private String header;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        // TODO 分别 pc 端 和 移动端 进行拦截认证
        String token = request.getHeader(header);
        // 判断token 是否以 Bearer 开头 ,如果是就是 PC 端 否则就是移动端
        if (StringUtils.isNotEmpty(token) && token.startsWith("Bearer")) {
            // PC 端
            LoginUser loginUser = tokenService.getLoginUser(request);
            if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
                tokenService.verifyToken(loginUser);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
            chain.doFilter(request, response);

        } else if (StringUtils.isNotEmpty(token)) {
            // 移动端
            LoginMobileUser loginMobileUser = tokenService.getLoginMobileUser(request);
            if (StringUtils.isNotNull(loginMobileUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
                tokenService.verifyTokenMobile(loginMobileUser);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginMobileUser, null, loginMobileUser.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
            chain.doFilter(request, response);
        } else {
            // 没有token 直接放行
            chain.doFilter(request, response);
        }
    }
}

借鉴:若依多用户表登录(sping security多用户)_spring security 多用户表-CSDN博客

Java
ruoyi
License:  CC BY 4.0
Share

Further Reading

Feb 9, 2025

其他

问: git 如何解决分支冲突 查看分支冲突文件 删除冲突代码 重新提交代码 liunx 常用命令 1. chmod 功能:修改文件或目录的权限。 常见用法示例: chmod +x script.sh:为 script.sh 文件添加可执行权限。 chmod 755 file.txt:使用数字模式设

Feb 9, 2025

Elasticsearch 面试

问:你了解 ElasticSearch 吗? 答:了解,ElasticSearch 一个强大的搜索引擎,在失物招领项目中我就使用 ES ,给 ES 安装中文分词器,就可以根据中文词语来检索索引库。Java

Feb 9, 2025

Spring 面试

问:解释 IOC 容器和DI依赖注入 答: IOC容器:控制反转,将创建对象权利交给IOC容器来完成 DI 依赖注入:获取 IOC 中获取创建好的对象,在Spring 常用的依赖注入有: 构造器注入 set 方法注入 @Autowired 注入 使用 IOC容器和DI依赖注入的好处: 降低代码耦合

OLDER

Docker 笔记

NEWER

C语言核心语法

Recently Updated

  • 其他
  • Elasticsearch 面试
  • Spring 面试
  • RabbitMQ 面试
  • Redis 面试

Trending Tags

ruoyi docker java

Contents

©2025 zian. Some rights reserved.

Using the Halo theme Chirpy