@Autowired失效时如何获取需要的容器对象

数据部
• 阅读 4343

问题描述

还是为了解决上次的Hibernate拦截器问题,@Autowired不管用了。

以下是部分代码,因本文主要解决手动从容器中获取对象的问题,所以将validateWebAppMenuRoute方法的业务逻辑删除,我们只打印webAppMenuService,来判断我们的注入是否成功。

public class WebAppMenuInterceptor extends EmptyInterceptor {

    private WebAppMenuService webAppMenuService;

    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        if (entity instanceof WebAppMenu) {
            this.validateWebAppMenuRoute((WebAppMenu) entity);
        }
        return super.onSave(entity, id, state, propertyNames, types);
    }

    private void validateWebAppMenuRoute(WebAppMenu webAppMenu) {
        System.out.println(webAppMenuService);
    }
}

获取上下文

想要获取容器中的对象,我们想的就是先获取容器(上下文),然后再调用容器的getBean方法获取其中某个对象。

@ComponentScan("com.mengyunzhi.spring")
public class Application {

    public static void main(String []args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        TestService testService = context.getBean(TestService.class);
    }
}

这是我们学习Spring IOC时写的代码,我们手动根据我们的注解配置自己生成的上下文环境,所以我们可以任性地去操作我们的上下文。

然而,在Spring Boot项目中。

@SpringBootApplication
public class ResourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(ResourceApplication.class, args);
    }
}

@Autowired失效时如何获取需要的容器对象

点开这行run方法,会发现其返回值就是我们的context,这是一种最简单的获取上下文的办法。但是一看到这么优雅的一行代码,实在不忍心破坏,永远保持main只有这一行,这太美了。

实现接口,获取上下文

这是在Spring揭秘一书中截下的一段话:

@Autowired失效时如何获取需要的容器对象

所以,我们只要实现上面的任一一个接口,就能获取到注入的上下文实例。

package com.mengyunzhi.measurement.context;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * 上下文,用于获取本项目的上下文/容器
 * 在@Autowired失效时可使用上下文手动获取相关对象
 */
@Component
public class ResourceApplicationContext implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return context;
    }
}

获取对象

对拦截器的代码修改如下,新建inject方法,用于注入无法自动装配的Bean

public class WebAppMenuInterceptor extends EmptyInterceptor {

    private WebAppMenuService webAppMenuService;

    private void inject() {
        if (null == this.webAppMenuService) {
            this.webAppMenuService = ResourceApplicationContext.getApplicationContext().getBean(WebAppMenuService.class);
        }
    }

    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        if (entity instanceof WebAppMenu) {
            this.validateWebAppMenuRoute((WebAppMenu) entity);
        }
        return super.onSave(entity, id, state, propertyNames, types);
    }

    private void validateWebAppMenuRoute(WebAppMenu webAppMenu) {
        this.inject();
        System.out.println(webAppMenuService);
    }
}

执行保存菜单的单元测试,结果如下:

@Autowired失效时如何获取需要的容器对象

注入成功,并且是Bean默认的单例模式。

思考

为什么我们一直使用的@Autowired注解失效了呢?

找了一晚上,终于找到失效的问题了,但是自己的问题没解决,却顺带把历史上遗留的一个问题解决了。

用户认证拦截器

@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
    
    Logger logger = Logger.getLogger(TokenInterceptor.class.getName());
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("在触发action前,先触发本方法");
        logger.info("获取路径中的token值");
        String tokenString = request.getParameter("token");

        UserService userService = new UserServiceImpl();
        
        logger.info("根据token获取对应的用户");
        User user = userService.getUserByToken(tokenString);
        if (user == null) {
            throw new SecurityException("传入的token已过期");
        }

        logger.info("注册spring security,进行用户名密码认证");
        Authentication auth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
        SecurityContextHolder.getContext().setAuthentication(auth);
        return true;
    }
}

这是同一个包下的拦截器,该拦截器用于拦截请求,使用Spring Security对用户进行认证。这里的UserService并不是@Autowired进来的,而是new出来的,可能前辈在写这块的时候也遇到了注入失败的问题。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    static private Logger logger = Logger.getLogger(WebConfig.class.getName());

    /**
     * 配置拦截器
     * @param interceptorRegistry
     */
    @Override
    public void addInterceptors(InterceptorRegistry interceptorRegistry) {
        logger.info("添加拦截器/**/withToken/**, 所以包含/withToken字符串的url都要被TokenInterceptor拦截");
        interceptorRegistry.addInterceptor(new TokenInterceptor()).addPathPatterns("/**/withToken/**");
    }
}

可能第一次看这段添加拦截器的代码可能有些摸不着头绪,其实我们点开源码,可能就会理解得更深刻。

@Autowired失效时如何获取需要的容器对象

没错,这就是我们学习过的观察者模式

其实很简单,调用interceptorRegistry添加拦截器的方法,添加一个我们写的拦截器进去。这里是new出来的拦截器。

new出来的东西,Spring是不管的,所以,如果我们是new的拦截器,那@Autowired注解标注的属性就是空。所以引发@Autowired失效。

为了验证我们的猜想,测试一下。

1.我们先删除new UserServiceImpl(),将其改为@Autowired

@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private UserService userService;

    Logger logger = Logger.getLogger(TokenInterceptor.class.getName());
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("在触发action前,先触发本方法");
        logger.info("获取路径中的token值");
        String tokenString = request.getParameter("token");

        logger.info("根据token获取对应的用户");
        User user = userService.getUserByToken(tokenString);
        if (user == null) {
            throw new SecurityException("传入的token已过期");
        }

        logger.info("注册spring security,进行用户名密码认证");
        Authentication auth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
        SecurityContextHolder.getContext().setAuthentication(auth);
        return true;
    }
}

@Autowired失效时如何获取需要的容器对象

单元测试,应该出错,因为我们是new的拦截器,而在拦截器中@AutowiredSpring是不管里这个注入的。并且错误信息就是我们调用userService的那行代码。

2.将new的拦截器改为@Autowired

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private TokenInterceptor tokenInterceptor;

    static private Logger logger = Logger.getLogger(WebConfig.class.getName());

    /**
     * 配置拦截器
     * @param interceptorRegistry
     */
    @Override
    public void addInterceptors(InterceptorRegistry interceptorRegistry) {
        logger.info("添加拦截器/**/withToken/**, 所以包含/withToken字符串的url都要被TokenInterceptor拦截");
        interceptorRegistry.addInterceptor(tokenInterceptor).addPathPatterns("/**/withToken/**");
    }
}

单元测试,通过。

@Autowired失效时如何获取需要的容器对象

解决了这个问题,因为本问题是由我们在配置拦截器时一个new的失误造成的。但是Hibernate的拦截器我们并没有自己配置,扫描到就会调用,这里猜测问题应该出现在Hibernate在实例化我自定义的拦截器时使用的是new或某种其他方式,反正应该不是在容器中获取的,所以得到的拦截器对象的@Autowired相关属性就为空。

总结

@Autowired描述对象之间依赖的关系,这里的关系指的是Spring IOC容器中相关的关系。

所以,其实这个问题并不是我们的注解失效了,其实如果我们从容器中拿一个WebAppMenuInterceptor的拦截器对象,它其实是已经将WebAppMenuService注入进来的,只不过Hibernate调用我们的拦截器时,采用并非从容器中获取的方式,只是“看起来”,注解失效了。

配置的时候能@Autowired就不用new,如果第三方库中new实例化我们的实现,我们需要使用手动从容器中获取的方法,就不能让Spring为我们注入了。
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Stella981 Stella981
3年前
JS 苹果手机日期显示NaN问题
问题描述newDate("2019122910:30:00")在IOS下显示为NaN原因分析带的日期IOS下存在兼容问题解决方法字符串替换letdateStr"2019122910:30:00";datedateStr.repl
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(