Конспект по основам контейнера + практические сниппеты. XML оставлен сжато — для сопровождения легаси; в новых проектах предпочтительны Java-конфиг и auto-configuration (Spring Boot).
В XML:
<context:component-scan base-package="soundsystem" />В Java-конфиге:
@Configuration
@ComponentScan // пакет — пакет класса с @Configuration
public class AppConfig { }Явный базовый пакет:
@ComponentScan("soundsystem")
@ComponentScan(basePackages = "soundsystem")
@ComponentScan(basePackages = {"soundsystem", "video"})
@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})@Component("lonelyHeartsClub")
@Named("lonelyHeartsClub") // JSR-330@Bean(name = "lonelyHeartsClubBand")
public CompactDisc sgtPeppers() { return new SgtPeppers(); }Конструктор, сеттер или произвольный метод — через @Autowired; альтернатива — @Inject (JSR-330).
@Autowired
public CDPlayer(CompactDisc cd) { ... }@Inject
public CDPlayer(CompactDisc cd) { ... }В Java-конфиге зависимости часто передают параметром метода @Bean:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}Конструктор vs поле: для обязательных зависимостей предпочтительнее конструктор; опциональные — через сеттер/поле при необходимости.
JUnit 4 (легаси, всё ещё встречается в старых проектах):
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
// ...
}Захват stdout в тестах (библиотека System Rules + JUnit 4 @Rule) — по сути устарело, но полезно знать при чтении старых тестов:
@Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog(); // system-rules / аналогиВ новых тестах предпочтительнее JUnit 5 и OutputCaptureExtension (Spring Boot) или проверка логгера.
JUnit 5:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
class CDPlayerTest {
// ...
}В Spring Boot чаще @SpringBootTest (или срезы @WebMvcTest, @DataJpaTest и т.д.).
@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig { }
@Configuration
@Import({CDPlayerConfig.class, CDConfig.class})
public class SoundSystemConfig { }Java-конфиг + XML:
@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig { }В XML:
<import resource="cd-config.xml" />Регистрация @Configuration из XML:
<bean class="soundsystem.CDConfig" />Один «корневой» конфиг собирает модули и обычно включает component scan (@ComponentScan или <context:component-scan>).
Рекомендация (из конспекта): по возможности auto-configuration; явная конфигурация — в Java, а не в XML (типобезопасность, рефакторинг).
Объявление бина: <bean id="..." class="...">. Конструктор + ref:
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>c-namespace (нужны объявления xmlns:c в <beans>):
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_0-ref="compactDisc" />
<!-- один параметр: c:_-ref="..." -->Литералы и коллекции — через <constructor-arg value="..."/>, <list> / <set> с <value> или <ref bean="..."/>, <null/>.
p-namespace и вложенный <property> — для setter injection; вынесенный список — <util:list id="trackList">...</util:list> и p:tracks-ref="trackList".
Элементы util:: constant, list, map, properties, property-path, set — см. документацию Spring.
Плейсхолдеры в XML: <context:property-placeholder /> (разрешение через Environment).
@Bean(destroyMethod = "shutdown")
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}Обычно под капотом H2 (раньше в книгах часто упоминали Hypersonic).
@Bean
public DataSource dataSource() throws Exception {
JndiObjectFactoryBean factory = new JndiObjectFactoryBean();
factory.setJndiName("jdbc/myDS");
factory.setResourceRef(true);
factory.setProxyInterface(javax.sql.DataSource.class);
factory.afterPropertiesSet();
return (DataSource) factory.getObject();
}В Spring Boot по умолчанию — HikariCP и настройки spring.datasource.*. Пример явного бина:
@Bean(destroyMethod = "close")
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:h2:tcp://dbserver/~/test");
ds.setDriverClassName("org.h2.Driver");
ds.setUsername("sa");
ds.setPassword("password");
ds.setMaximumPoolSize(30);
ds.setMinimumIdle(5);
return ds;
}Старый BasicDataSource (Commons DBCP) с setMaxActive / setInitialSize для новых проектов не рекомендуется — API и зависимости другие; предпочтительны Boot + Hikari.
@Configuration
@Profile("dev")
public class DevelopmentProfileConfig { ... }
@Bean(destroyMethod = "shutdown")
@Profile("dev")
public DataSource embeddedDataSource() { ... }В XML: корневой <beans profile="dev"> или атрибут profile (см. схему beans).
Активация: spring.profiles.active (env, JVM, application.properties), параметры веб-приложения, JNDI; в тестах — @ActiveProfiles.
Если задан spring.profiles.active, spring.profiles.default не перекрывает его. Несколько профилей — через запятую.
JUnit 4 + профиль:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = PersistenceTestConfig.class)
@ActiveProfiles("dev")
public class PersistenceTest {
// ...
}JUnit 5 + профиль:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = PersistenceTestConfig.class)
@ActiveProfiles("dev")
class PersistenceTest { ... }@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() { ... }
public class MagicExistsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().containsProperty("magic");
}
}ConditionContext: getRegistry(), getBeanFactory(), getEnvironment(), getResourceLoader(), getClassLoader().
@Component
@Primary
public class IceCream implements Dessert { ... }
@Bean
@Primary
public Dessert iceCream() { ... }<bean id="iceCream" class="com.desserteater.IceCream" primary="true" />@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) { ... }Кастомные стерео-аннотации с @Qualifier на типе; на точке внедрения — несколько таких аннотаций для сужения выбора.
- singleton — один на контекст (по умолчанию).
- prototype — новый экземпляр при каждом запросе из контекста.
- session / request — в веб-приложении на сессию / на запрос.
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad { ... }
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad() { return new Notepad(); }Сессионный бин в синглтон нужно внедрять через scoped proxy (иначе один экземпляр «прилипнет» к синглтону):
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ShoppingCart {
// ...
}Для интерфейса можно ScopedProxyMode.INTERFACES. В XML: <aop:scoped-proxy /> (при необходимости proxy-target-class="false" для JDK-прокси).
@PropertySource + Environment:
@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig {
@Autowired
Environment env;
// env.getProperty("disc.title")
// env.getProperty("db.connection.count", Integer.class, 30)
// env.getRequiredProperty("disc.title")
// env.containsProperty("disc.title")
}Плейсхолдеры ${...} в XML/Java. Для явной регистрации (без Boot) иногда нужен:
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}В Boot это обычно не требуется.
public BlankDisc(
@Value("${disc.title}") String title,
@Value("${disc.artist}") String artist) { ... }SpEL (#{...}): литералы, T(System).currentTimeMillis(), свойства бинов, systemProperties[...], безопасная навигация ?., T(java.lang.Math).PI, тернарный оператор, Elvis ?:, matches, индексы и отборы .?[], .^[], .$[], проекция .![], комбинации.
Обзор: Spring validation (blog.upagge.ru)
Контроллер
- Тело запроса (
@RequestBody): аннотации Bean Validation на DTO +@Validна параметре → типичноMethodArgumentNotValidExceptionи ответ 400 (при настроенной обработке). - Путь и query (
@PathVariable,@RequestParam): аннотации на параметрах +@Validatedна классе контроллера → нарушения черезConstraintViolationException(часто настраивают маппинг в 400).
Сервис
@Validatedна классе +@Validна параметре метода →ConstraintViolationException.
Точные коды ответа зависят от @ControllerAdvice и версии; при необходимости сверяйте с текущей документацией Boot.
Stack Overflow: check string in response body
MvcResult result = mockMvc.perform(post("/api/users")
.header("Authorization", base64ForTestUser)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"userName\":\"testUserDetails\",...}"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isBadRequest())
.andReturn();
String content = result.getResponse().getContentAsString();Альтернатива без MvcResult: andExpect(content().string(containsString("..."))) и статические импорты из MockMvcResultMatchers.
new ClassPathResource("ccp-" + name.toLowerCase() + ".yaml").getFile();getFile() работает только для ресурса на реальном диске; в упакованном JAR используйте getInputStream() или Resource.getFile() только если уверены в раскладке (например, тесты из IDE).
@Autowired
private Environment env;
@PostConstruct
void init() {
String name = env.getProperty("fabric.org.name");
}В Boot те же ключи задаются через application.properties, переменные окружения и т.д.