Java « Многопоточность WEB-приложения на Spring

Доброго времени суток.

Нигде не могу найти хорошего описания многопоточной архитектуры применительно к WEB-приложению. Подскажите, пожалуйста, в конкретной ситуации, слабые места описанной ниже архитуктуры.

1. Есть контроллер, который отвечает за обработку конкретного URL. Каждое обращение к данному контроллеру Spring автоматически будет обрабатывать в отдельном потоке ?

/** * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann * (http://qbnz.com/highlighter/ and http://geshi.org/) */ .java.geshi_code {font-family:monospace;} .java.geshi_code .imp {font-weight: bold; color: red;} .java.geshi_code .kw1 {color: #000000; font-weight: bold;} .java.geshi_code .kw2 {color: #000066; font-weight: bold;} .java.geshi_code .kw3 {color: #003399;} .java.geshi_code .kw4 {color: #000066; font-weight: bold;} .java.geshi_code .co1 {color: #666666; font-style: italic;} .java.geshi_code .co2 {color: #006699;} .java.geshi_code .co3 {color: #008000; font-style: italic; font-weight: bold;} .java.geshi_code .coMULTI {color: #666666; font-style: italic;} .java.geshi_code .es0 {color: #000099; font-weight: bold;} .java.geshi_code .br0 {color: #009900;} .java.geshi_code .sy0 {color: #339933;} .java.geshi_code .st0 {color: #0000ff;} .java.geshi_code .nu0 {color: #cc66cc;} .java.geshi_code .me1 {color: #006633;} .java.geshi_code .me2 {color: #006633;} .java.geshi_code span.xtra { display:block; }

@Controller
public class YaController{  
   
    @Autowired
    private YaService service;
 
    @RequestMapping(value="/service", method=RequestMethod.POST)
    public ModelAndView service(@RequestParam("param") Srting param){
       ModelAndView m = new ModelAndView ("service");
       m.addObject("result", service.doSomething(param));
       return m;
    }
 


2. Есть сервисный уровень в котором находится логика приложения. Объект этого уровня инжектится в контроллер. В Spring все компоненты по умолчанию singletone. Не нужно ли в данному случае использовать prototype (что, например, произойдет, если два клиента обратятся к методу на сервисном уровне одновременно) ?

/** * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann * (http://qbnz.com/highlighter/ and http://geshi.org/) */ .java.geshi_code {font-family:monospace;} .java.geshi_code .imp {font-weight: bold; color: red;} .java.geshi_code .kw1 {color: #000000; font-weight: bold;} .java.geshi_code .kw2 {color: #000066; font-weight: bold;} .java.geshi_code .kw3 {color: #003399;} .java.geshi_code .kw4 {color: #000066; font-weight: bold;} .java.geshi_code .co1 {color: #666666; font-style: italic;} .java.geshi_code .co2 {color: #006699;} .java.geshi_code .co3 {color: #008000; font-style: italic; font-weight: bold;} .java.geshi_code .coMULTI {color: #666666; font-style: italic;} .java.geshi_code .es0 {color: #000099; font-weight: bold;} .java.geshi_code .br0 {color: #009900;} .java.geshi_code .sy0 {color: #339933;} .java.geshi_code .st0 {color: #0000ff;} .java.geshi_code .nu0 {color: #cc66cc;} .java.geshi_code .me1 {color: #006633;} .java.geshi_code .me2 {color: #006633;} .java.geshi_code span.xtra { display:block; }

@Service
public class YaServiceImpl implements YaService{

    @Autowired
    private Task dbTask;
 
    @Override
    public List<Object> doSomething(String param){
    return dbTask.executeQuery(param);
    }

}

public class DbTask implements Task{

    @Autowired
    private Dao dao;
 
    @Override
    public List<Object> executeQuery(String param){

          dao.update("CALL `sp_routine`()");
    return dao.query(String.format("SELECT %s FROM tbl_example", param), RowMapper rm);
    }

}
 


3. И есть Dao в который инжектится объект DataSource, который представляет собой пул потоков c3p0. Нужно ли создавать каждый объект Task в новом потоке и как быть если два таких объекта одновременно обратятся к методу Dao с одинаковым запросом. Предполагается, что объекты, если оперируют, то разными данными, поэтому в thread-safe нет необходимости.

/** * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann * (http://qbnz.com/highlighter/ and http://geshi.org/) */ .java.geshi_code {font-family:monospace;} .java.geshi_code .imp {font-weight: bold; color: red;} .java.geshi_code .kw1 {color: #000000; font-weight: bold;} .java.geshi_code .kw2 {color: #000066; font-weight: bold;} .java.geshi_code .kw3 {color: #003399;} .java.geshi_code .kw4 {color: #000066; font-weight: bold;} .java.geshi_code .co1 {color: #666666; font-style: italic;} .java.geshi_code .co2 {color: #006699;} .java.geshi_code .co3 {color: #008000; font-style: italic; font-weight: bold;} .java.geshi_code .coMULTI {color: #666666; font-style: italic;} .java.geshi_code .es0 {color: #000099; font-weight: bold;} .java.geshi_code .br0 {color: #009900;} .java.geshi_code .sy0 {color: #339933;} .java.geshi_code .st0 {color: #0000ff;} .java.geshi_code .nu0 {color: #cc66cc;} .java.geshi_code .me1 {color: #006633;} .java.geshi_code .me2 {color: #006633;} .java.geshi_code span.xtra { display:block; }

@Repository
public class JdbcTemplateDao implements Dao
{
  private JdbcTemplate jdbcTemplate;
 
  @Autowired
  @Override
  public void setDataSource(DataSource dataSource){
    this.jdbcTemplate = new JdbcTemplate(dataSource);
  }

  @Override
  public Object update(String query){
    return jdbcTemplate.update(query);
  }
 
  @Override
  public Object query(String query, RowMapper<?> rowMapper){
    return jdbcTemplate.query(query, rowMapper);
  }
}
 


Оправдано ли такое построение приложения в принципе ? Во всех примерах, что мне случалось увидеть, методы содержащие обращение к БД определялись внутри DAO, а не выносились в отдельный класс. Это неудобно по целому ряду причин. Я мог бы с удовольствием создать несколько Dao, если бы это не означало в каждый из них передать DataSource, т.е. открыть свой пул.
Также буду признателен за примеры ситуаций, в которых для WEB-приложения следует применять многопоточность.
Сорри, что так много вопросов за раз.

1 ответов


1. Не совсем так. Есть пул потоков, который обрабатывает входящие HTTP запросы. Полученный запрос будет обработан вот по такой схеме. Таким образом, по умолчанию нет информации о том, каким именно потоком будет обработан тот или иной запрос. Общая рекомендация такая - сервисы должны не должны иметь состояния, и должна быть гарантия потокобезопасности.

2. Нет, в общем случае сервисы в Spring не должны иметь тип prototype, однако бывают ситуации, когда такая настройка уместна. Однако надо помнить об особенностях, связанных с динамическим созданием контроллеров, как, например, то, что callback типа @PreDestroy в этих бинах не срабатывает, потому что контекст ничего не знает об их существовании.

3. Объекты слоя DAO совершенно точно должны быть stateless, и никаких объектов task на их уровне быть не должно. При этом надо помнить о том, что операция с БД может быть тяжеловесной, а значит выполнение таких операций потоками из пула, которые обрабатывают реквесты, может привести к тому, что в какой-то момент все потоки будут заняты, и следующий пришедший HTTP клиент получит отлуп. Тут мы приходим к концепции асинхронной обработки сообщений, но это уже совсем другая история, поскольку она уже накладывает и на клиента определённые требования, особенно если речь идёт о веб-сервисах.

Суть класса DbTask для меня вообще загадка. Зачем вручную формировать запросы, когда есть все необходимые инструменты, предоставляемые модулем Spring JDBC, которые позволяют от этого отойти.

>если бы это не означало в каждый из них передать DataSource, т.е. открыть свой пул.
Вот тут я совсем не понял, о чём вы. Вообще, в DAO не надо передавать DataSource, это низкоуровневая штука. Если уж вы не используете ORM, а работаете с JDBC напрямую, используйте для этого SimpleJdbcTemplate, который, в свою очередь, использует DAO. А пул соединений c БД - это вообще то, что не должно вас заботить на уровне кода, это всё решается на уровне конфигурации Spring-а. Для кода вообще неважно, есть пул или нет, для него это объект типа DataSource.