在Java filter中调用service层方法

本文探讨了在Spring Web应用中,Filter中Autowired注解注入Service失败的问题,提供了三种解决方法,包括使用WebApplicationContext、避免懒加载和使用HandlerInterceptor。还介绍了Spring ContextLoaderListener的工作原理和如何正确使用不同场景下的ServletContext获取方式。

在项目中遇到一个问题,在 Filter中注入 Serivce失败,注入的service始终为null。如下所示:

public class WeiXinFilter implements Filter{
    
    @Autowired
     private UsersService usersService;

     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         HttpServletRequest req = (HttpServletRequest)request;
         HttpServletResponse resp = (HttpServletResponse)response;
     Users users = this.usersService.queryByOpenid(openid);
 }

上面的 usersService 会报空指针异常。

解决方法一:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest)request;
         HttpServletResponse resp = (HttpServletResponse)response;
        ServletContext sc = req.getSession().getServletContext();
       XmlWebApplicationContext cxt = (XmlWebApplicationContext)WebApplicationContextUtils.getWebApplicationContext(sc);
         
        if(cxt != null && cxt.getBean("usersService") != null && usersService == null)
            usersService = (UsersService) cxt.getBean("usersService");
       
        Users users = this.usersService.queryByOpenid(openid);

解决方法二:

public class WeiXinFilter implements Filter{
    
    private UsersService usersService;
    
    public void init(FilterConfig fConfig) throws ServletException {
        ServletContext sc = fConfig.getServletContext(); 
        XmlWebApplicationContext cxt = (XmlWebApplicationContext)WebApplicationContextUtils.getWebApplicationContext(sc);
        
        if(cxt != null && cxt.getBean("usersService") != null && usersService == null)
            usersService = (UsersService) cxt.getBean("usersService");        
    }

public class WeiXinFilter implements Filter{

private UsersService usersService;

public void init(FilterConfig fConfig) throws ServletException {
    ServletContext sc = fConfig.getServletContext(); 
    XmlWebApplicationContext cxt = (XmlWebApplicationContext)WebApplicationContextUtils.getWebApplicationContext(sc);
    
    if(cxt != null && cxt.getBean("usersService") != null && usersService == null)
        usersService = (UsersService) cxt.getBean("usersService");        
}

相关原理:

  1. 如何获取 ServletContext:

1)在javax.servlet.Filter中直接获取
ServletContext context = config.getServletContext();

2)在HttpServlet中直接获取
this.getServletContext()

3)在其他方法中,通过HttpServletRequest获得
request.getSession().getServletContext();

  1. WebApplicationContext 与 ServletContext (转自:http://blessht.iteye.com/blog/2121845):

Spring的 ContextLoaderListener是一个实现了ServletContextListener接口的监听器,在启动项目时会触发contextInitialized方法(该方法主要完成ApplicationContext对象的创建),在关闭项目时会触发contextDestroyed方法(该方法会执行ApplicationContext清理操作)。

ConextLoaderListener加载Spring上下文的过程

①启动项目时触发contextInitialized方法,该方法就做一件事:通过父类contextLoader的initWebApplicationContext方法创建Spring上下文对象。

②initWebApplicationContext方法做了三件事:创建 WebApplicationContext;加载对应的Spring文件创建里面的Bean实例;将WebApplicationContext放入 ServletContext(就是Java Web的全局变量)中。

③createWebApplicationContext创建上下文对象,支持用户自定义的上下文对象,但必须继承自ConfigurableWebApplicationContext,而Spring MVC默认使用ConfigurableWebApplicationContext作为ApplicationContext(它仅仅是一个接口)的实 现。

④configureAndRefreshWebApplicationContext方法用 于封装ApplicationContext数据并且初始化所有相关Bean对象。它会从web.xml中读取名为 contextConfigLocation的配置,这就是spring xml数据源设置,然后放到ApplicationContext中,最后调用传说中的refresh方法执行所有Java对象的创建。

⑤完成ApplicationContext创建之后就是将其放入ServletContext中,注意它存储的key值常量。

解决方法三:

直接使用spring mvc中的HandlerInterceptor或者HandlerInterceptorAdapter 来替换Filter:

public class WeiXinInterceptor implements HandlerInterceptor {
@Autowired
private UsersService usersService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    // TODO Auto-generated method stub
    return false;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
        throws Exception {
    // TODO Auto-generated method stub
    
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    // TODO Auto-generated method stub
    
}

}
配置拦截路径:

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**" />
        <bean class="net.xxxx.interceptor.WeiXinInterceptor" />
    </mvc:interceptor>     
</mvc:interceptors>

Filter 中注入 Service 的示例:

public class WeiXinFilter implements Filter{    
     private UsersService usersService;    
     public void init(FilterConfig fConfig) throws ServletException {}
     public WeiXinFilter() {}
     public void destroy() {}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse resp = (HttpServletResponse)response;
         
         String userAgent = req.getHeader("user-agent");
        if(userAgent != null && userAgent.toLowerCase().indexOf("micromessenger") != -1){    // 微信浏览器
            String servletPath = req.getServletPath();
            String requestURL = req.getRequestURL().toString();
            String queryString = req.getQueryString();

             if(queryString != null){
                if(requestURL.indexOf("mtzs.html") !=-1 && queryString.indexOf("LLFlag")!=-1){
                    req.getSession().setAttribute("LLFlag", "1");
                    chain.doFilter(request, response);
                    return;
               }
             }
            
             String openidDES = CookieUtil.getValueByName("openid", req);
            String openid = null;
           if(StringUtils.isNotBlank(openidDES)){
                try {
                   openid = DesUtil.decrypt(openidDES, "rxxxxxxxxxde");    // 解密获得openid
                 } catch (Exception e) {
                    e.printStackTrace();
                }    
            }
            // ... ...
            String[] pathArray = {"/weixin/enterAppFromWeiXin.json", "/weixin/getWeiXinUserInfo.json",
                                    "/weixin/getAccessTokenAndOpenid.json", "/sendRegCode.json", "/register.json", 
                                    "/login.json", "/logon.json", "/dump.json", "/queryInfo.json"};
            List<String> pathList = Arrays.asList(pathArray);
             
             String loginSuccessUrl = req.getParameter("path");
             String fullLoginSuccessUrl = "http://www.axxxxxxx.cn/pc/";
             if(requestURL.indexOf("weixin_gate.html") != -1){
                req.getSession().setAttribute("loginSuccessUrl", loginSuccessUrl);
          // ... ...
            }
            ServletContext sc = req.getSession().getServletContext();
 XmlWebApplicationContext cxt = (XmlWebApplicationContext)WebApplicationContextUtils.getWebApplicationContext(sc);
        
            if(cxt != null && cxt.getBean("usersService") != null && usersService == null)
               usersService = (UsersService) cxt.getBean("usersService");
         
             Users users = this.usersService.queryByOpenid(openid);
            // ... ...
             if(pathList.contains(servletPath)){    // pathList 中的访问路径直接 pass 
                chain.doFilter(request, response);
                 return;
            }else{
                if(req.getSession().getAttribute(CommonConstants.SESSION_KEY_USER) == null){ // 未登录
                    String llFlag = (String) req.getSession().getAttribute("LLFlag");
                    if(llFlag != null && llFlag.equals("1")){    // 处理游客浏览
                         chain.doFilter(request, response);
                         return;
                     }                  
                    // ... ...// 3. 从腾讯服务器去获得微信的 openid ,
                    req.getRequestDispatcher("/weixin_gate.html").forward(request, response);
                   return;
                }else{    // 已经登录
                   // 4. 已经登录时的处理                    
                    chain.doFilter(request, response); 
                    return;
                }
             }            
         }else{    // 非微信浏览器
             chain.doFilter(request, response);
        }
    }
 
}
<think>嗯,用户现在想在JavaServiceImpl实现批量调用方法的优化和异常处理的最佳实践。根据之前的对话,用户之前有一个方法需要更新夹具和车辆的关系,但原来的代码存在一些问题,比如循环单次更新、异常处理不完善等。用户现在希望进一步优化批量调用方法,并处理异常情况。首先,我需要回顾用户之前的问题和我的回答。之前的优化建议包括使用批处理、事务控制、资源管理以及异常分类处理。现在用户可能希望将这些最佳实践应用到ServiceImpl的其他批量方法中,或者进一步深入探讨如何优化。接下来,用户提供的站内引用提到了Spring的单例Service、线程安全、双检锁、Thrift服务的初始化等。这些引用可能和用户的问题相关,比如在批量处理时需要考虑线程安全,或者如何初始化和管理多个服务实例。但用户当前的关注点是ServiceImpl的批量方法优化和异常处理,所以需要结合这些引用中的知识点。比如,引用1提到SpringService默认是单例,多线程访问时需要考虑线程安全。在批量处理方法中,如果涉及到共享资源(如缓存或数据库连接),可能需要使用同步机制或线程安全的数据结构。引用2和3涉及Thrift服务的初始化和路由,可能与当前问题关联不大,但可能用户之前处理过相关服务,需要注意上下文。用户的需求是优化批量调用方法,可能的优化点包括:1.使用批处理减少数据库交互次数。 2.事务管理确保数据一致性。3.合理的异常处理,如分类处理异常、事务回滚。 4.资源管理,如使用连接池。5.线程安全考虑,尤其在单例Service中。需要考虑如何将之前的优化点(如批处理、事务)应用到ServiceImpl,并结合线程安全的问题。例如,在批量操作时,如果Service是单例的,且方法中使用了非线程安全的变量,可能会导致并发问题。需要确保方法内的资源是线程安全的,比如使用局部变量而非实例变量。另外,异常处理方面,需要区分业务异常和系统异常,进行适当的回滚或日志记录。例如,在批量处理中,某个记录失败是否影响整体事务,或者需要部分提交。 可能的解决方案包括:-使用Spring的@Transactional注解管理事务。 -使用JdbcTemplate或MyBatis的批量操作支持。-异常处理中使用try-catch块,捕获特定异常并回滚事务。-在多线程环境下,考虑使用同步或并发工具类。需要结合用户提供的代码示例,进一步分析如何将这些优化应用到ServiceImpl。例如,之前的代码示例使用了显式的事务控制(conn.setAutoCommit(false)),但在Spring中通常使用声明式事务管理更佳。 可能用户现在的问题是如何在Spring管理的Service中实现类似的批量操作优化,包括事务和异常处理。因此,回答需要涵盖Spring的事务管理、批处理实现方式、异常处理策略,以及线程安全的注意事项。 总结,用户需要的是在ServiceImpl实现批量方法的最佳实践,包括性能优化、事务控制、异常处理和线程安全。需要结合Spring框架的特性,如@Transactional、JdbcTemplate批处理,以及适当的异常处理机制,确保代码高效且健壮。</think>### Java ServiceImpl批量方法优化与异常处理最佳实践 --- #### 一、核心优化策略 1. **批处理机制** ```java @Transactional public void batchUpdate(List<Entity> entities) { jdbcTemplate.batchUpdate( "UPDATE table SET field=? WHERE id=?", entities.stream().map(e -> new Object[]{e.getField(), e.getId()}) .collect(Collectors.toList()) ); // Spring JdbcTemplate批量操作[^1] } ``` - **性能公式**: $$ T = \frac{n}{batchSize} \times t_{roundTrip} $$ 通过增大`batchSize`减少网络往返次数 2. **事务控制** ```java @Transactional(rollbackFor = Exception.class) // 声明式事务管理 public void transactionalBatchProcess(List<Data> dataList) { dataList.forEach(this::validateBusinessRule); // 业务校验前置 repository.batchInsert(dataList); // 批量持久化 } ``` --- #### 二、异常处理模式 ```mermaid graph TD A[开始批量操作] --> B{参数校验} B -->|失败| C[抛IllegalArgumentException] B -->|通过| D[执行数据库操作] D --> E{发生异常?} E -->|是| F[分类处理异常] F --> G[数据冲突?] G -->|是| H[抛DataIntegrityViolationException] G -->|否| I[记录日志并抛ServiceException] E -->|否| J[提交事务] ``` 1. **分异常处理** ```java try { batchOperation(); } catch (DataAccessException e) { // Spring数据访问异常基类 logger.error("数据库异常: {}", e.getMessage()); throw new ServiceException("DB_OPERATION_FAILED", e); } catch (BusinessException e) { // 自定义业务异常 throw new ServiceException(e.getErrorCode(), e.getMessage()); } ``` 2. **事务回滚策略** - 使用`@Transactional(rollbackFor=CustomException.class)`指定回滚条件 - 对非受检异常(RuntimeException)默认回滚 --- #### 三、线程安全与资源管理 1. **单例Service注意事项** ```java @Service public class BatchService { // 错误示例:非线程安全的成员变量 private List<Data> tempBuffer = new ArrayList<>(); // 正确做法:使用ThreadLocal或方法内局部变量 private ThreadLocal<List<Data>> threadLocalBuffer = new ThreadLocal<>(); } ``` 2. **连接池配置建议** ```yaml # application.yml spring: datasource: hikari: maximum-pool-size: 20 # 根据CPU核心数设置 connection-timeout: 3000 leak-detection-threshold: 60000 ``` --- #### 四、性能增强技巧 1. **并行流处理**(需权衡事务边界) ```java dataList.parallelStream() .filter(this::validate) .forEach(repository::save); ``` 2. **分段批量提交** ```java int BATCH_SIZE = 500; Lists.partition(bigDataList, BATCH_SIZE).forEach(subList -> { jdbcTemplate.batchUpdate(sql, subList); entityManager.flush(); // 定期清理缓存 entityManager.clear(); }); ``` --- #### 五、监控与调试 1. **性能日志标记** ```java @Around("execution(* com.service..*(..))") public Object logPerformance(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); Object result = pjp.proceed(); logger.info("{} executed in {} ms", pjp.getSignature(), System.currentTimeMillis()-start); return result; } ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值