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.