NoUniqueBeanDefinitionException: No qualifying bean of
type 'edu.phystech.robotlecturer.Bar' available: expected
single matching bean but found 3: bar1,bar2,bar3
@inponomarev
Иван Пономарёв, КУРС/МФТИ
NoUniqueBeanDefinitionException: No qualifying bean of
type 'edu.phystech.robotlecturer.Bar' available: expected
single matching bean but found 3: bar1,bar2,bar3
@Component
public class Foo {
//Не работает... :-(
@Autowired
private Bar bar;
//Работает!! :-)
@Autowired
private Bar bar1;
...
}
@Component
public class Foo {
@Autowired
@Qualifier("bar1")
private Bar bar;
...
}
@Component
@Primary
public class Bar1 implements Bar {...}
@Component
public class Foo {
//Подставится Bar1
@Autowired
private Bar bar;
...
}
Но это работает, лишь если действительно один из бинов помечен как @Primary
, иначе "NoUniqueBeanDefinitionException: more than one 'primary' bean found among candidates".
@Component
public class Foo {
@Autowired
private List<Bar> bar;
//ИЛИ
@Autowired
private Map<String,Bar> bar;
@Configuration
//Путь к файлу с настройками
@PropertySource("classpath:config.properties")
public class Config {
}
@Component
public class Foo {
@Value("${url}")
String url;
@Value("${password}")
String password;
Также можно использовать SpEL-выражения, например #{bar.url}
.
Конструктор не годится в качестве инициализирующего метода!
@PostConstruct
— стандартная Java-аннотация, может быть установлена над методом инициализации.
Если навесить аннотацию в классе нельзя, то @Bean(initMethod= "init")
Симметрично инициализации — при завершении работы контейнера.
При завершении приложения вызывается context.destroy()
, запускающий механизм финализации.
@PreDestroy
— стандартная Java-аннотация.
Если навесить аннотацию в классе нельзя, то @Bean(destroyMethod = "destroy")
Хотим сделать трейсинг приложения: перед началом и после завершения метода должно выводиться в лог его название и штамп времени.
Пусть у нас 500 классов по 5 методов
Значит, в 2500 мест надо копировать код:
не многовато ли??
не забудем ли какие-то из методов?
смешение бизнес-логики и служебного кода?
Типичные задачи:
Логирование: каждый вызов метода должен отмечаться в логе!
Безопасность: при вызове каждого метода надо проверять, имеем ли мы право на вызов!
Управление транзакциями: перед началом метода открывать транзакцию, при удачном завершении коммитить, при неудачном — откатывать.
AOP помогает решать эти задачи без дублирования кода внутри методов.
Большое количество аспектов уже написано.
Joinpoint — место в коде, в выполнение которого мы «вмешиваемся» (и начинаем выполнять что-то своё). В теории может соответствовать вызовам методов, инициализации классов и инстанцированию объектов, но в Spring AOP — это всегда вызов метода.
Advice — код, который «впрыскивается» и выполняется в joinpoint.
Pointcut — тем или иным способом определённое множество joinpoint-ов. Например, «все методы, начинающиеся со слова get». Или: «все методы, аннотированные аннотацией @Benchmarked
». Связывая pointcut-ы c advice-ами, мы определяем, что именно и когда будет работать.
Aspect — комбинация advices+pointcuts, оформленная в виде отдельного класса. Определяет добавляемую в приложение логику, ответственную за какую-то определённую задачу (например, трассировку).
Weaving — процесс «вплетения» кода advices в нужные места кода приложения.
Target — метод, чьё поведение изменяется с помощью AOP.
Статически: вплетение на уровне исходников или байт-кода.
Динамически: создавая прокси и используя вплетение в runtime.
Spring использует динамический AOP.
Вопрос: какие вы видите достоинства и недостатки у того и другого метода?
Создаётся по необходимости.
Перехватывает вызовы всех методов на target.
Проверяет, не сработал ли pointcut — и вызывает advice.
Для активизации BeanPostProcessor
-а, сканирующего AOP-аннотации, надо в конфигурации включить аннотацию @EnableAspectJAutoProxy
@Configuration
@EnableAspectJAutoProxy
public class AppConfig{
...
}
(что такое BeanPostProcessor
— мы увидим в дальнейшем, но вы уже знаете, что это конфигуратор объекта)
@Component @Aspect
public class BenchmarkAspect {
@Around("@annotation(Benchmark)")
//pointcut expression ^^^
public Object execEntryPoint(ProceedingJoinPoint joinPoint)
throws Throwable {
System.out.printf("[[[BENCHMARK method %s%n",
joinPoint.getSignature().getName());
long start = System.nanoTime();
Object retVal = joinPoint.proceed();
long end = System.nanoTime();
System.out.printf("Time: %dns]]]%n", end - start);
return retVal;
}
}
@EnableAspectJAutoProxy
над конфигурацией.
@Aspect
над аспектом.
@Component
/ @Bean
для аспекта, и сам аспект должен входить в конфигурацию приложения.
Аспект не перехватывает методы, которые вызываются @PostConstruct
-ом. Вопрос: почему?
@EnableAspectJAutoProxy
над конфигурацией.
@Aspect
над аспектом.
@Component
/ @Bean
для аспекта, и сам аспект должен входить в конфигурацию приложения.
Аспект не перехватывает методы, которые вызываются @PostConstruct
-ом.
@Component @Aspect
public class BenchmarkAspect {
@Around("@annotation(Benchmark)")
//pointcut expression ^^^
public Object execEntryPoint(ProceedingJoinPoint joinPoint)
throws Throwable {
System.out.printf("[[[BENCHMARK method %s%n",
joinPoint.getSignature().getName());
long start = System.nanoTime();
Object retVal = joinPoint.proceed();
long end = System.nanoTime();
System.out.printf("Time: %dns]]]%n", end - start);
return retVal;
}
}
@Before
@AfterReturning
(нормальное завершение)
@AfterThrowing
(exception)
@After
(нормальное завершение и exception)
@Around
@Before
@Before("@annotation(Benchmark)")
public void beforeFooMethods(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println(methodName);
}
@After
@AfterReturning(pointcut= "execution(* edu.phystech..*.foo())",
returning = "retVal")
public void afterFoo(Double retVal) {
System.out.println("AFTER foo()" + retVal);
}
@AfterThrowing(
pointcut= "execution(* aop.example.application..*.*(..))",
throwing = "ex")
public void dbException(DatabaseRuntimeException ex){
System.out.println(ex);
}
вызов @Tranasctional
-метода из самого бина
@Service
public class MyService {
public void doSomething() {
// ...
doSomethingElse();
}
//Transactional тут не работает... :-(((
@Transactional
public void doSomethingElse() {
// ...
}
}
@Service
public class MyService {
@Autowired
ObjectFactory<MyService> self;
public void doSomething() {
// ...
self.getObject().doSomethingElse();
}
@Transactional
public void doSomethingElse() {
// ...
}
}
public interface BeanPostProcessor {
//впрыскиваем в бин значения
default Object postProcessBeforeInitialization(
Object bean, String beanName) throws BeansException {
return bean;
}
//возвращаем обёртку над бином
default Object postProcessAfterInitialization(
Object bean, String beanName) throws BeansException {
return bean;
}
}
@Component public class InjectRandomIntAnnotationBeanPostProcessor
implements BeanPostProcessor {
@Override public Object postProcessBeforeInitialization(
Object bean, String beanName) throws BeansException {
for (Field f : ReflectionUtils.getAllFields(bean.getClass())) {
InjectRandomInt ann = f.getAnnotation(InjectRandomInt.class);
if (ann != null) {
int value = ThreadLocalRandom.current()
.nextInt(ann.min(), ann.max() + 1);
f.setAccessible(true);
try { f.set(bean, value); }
catch (IllegalAccessException e) {
throw new NotWritablePropertyException(
bean.getClass(), f.getName()); }
} }
return bean;
} }
AnnotationConfigApplicationContext
DI
AOP
BeanPostProcessor
Test with ContextConfiguration
Convention over configuration
Подключение готовых сконфигурированных блоков через «стартеры»
Включая встроенный Tomcat или Jetty (что инвертировало старую модель)
Метрики, health checks, настройка приложения через конфигурационный файл
Всё на аннотациях
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!-- ...to spring-boot-dependencies, 3356 LOC -->
<version>2.2.1.RELEASE</version>
</parent>
<!-- «Мы хотим веб-сервис»-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- А почему не указываем версию?-->
</dependency>
//Пакеты не указываем
@SpringBootApplication
public class Main {
public static void main(String[] args) throws SQLException, IOException {
/*Метод run возвращает ApplicationContext,
но он нам не нужен :-)*/
SpringApplication.run(Main.class, args);
}
}
//Это тоже @Controller, а значит, bean
@RestController
public class HelloController {
@GetMapping("/hello")
public String sayHello(@RequestParam("name") String name) {
return String.format("Hello, %s!", name);
}
}
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
Получается «жирный» executable jar.
Можно сделать так, что он будет в буквальном смысле executable.
Spring Boot сканирует файловую систему ClassPath на наличие файлов spring.factories
.
Spring Boot
Spring JDBC Template
Spring Boot Test
Всё, что я рассказал в этом курсе — уже Legacy.
Мы работали на Java 21 (LTS), но…
март 2024 — Java 22
сентябрь 2024 — Java 23
март 2025 — Java 24
Modularization (Java 9+)
Type Inference (Java 10+)
Switch Expressions (Java 12+)
Multiline Strings (Java 13+)
Records (Java 14+)
Valhalla
Loom
Не только Spring:
Microframeworks — for serverless
Не только Java language:
Groovy
Kotlin
Scala
"Here, you see, it takes all the running you can do, to keep in the same place. If you want to get somewhere else, you must run at least twice as fast as that!”
![]() | ![]() | ![]() | ![]() |
От работы программистов в XXI веке зависят судьбы людей
Даже если это не программа, управляющая самолётом или аппаратом ИВЛ
Помните про:
Security
Privacy
Accessibility
Делайте мир лучше!
JUGs: Москва, Санкт-Петербург, Новосибирск.
Conferences: JPoint, Joker, SnowOne.