← Назад к вопросам

Что делает @Controller с точки зрения контекста?

1.3 Junior🔥 91 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что делает аннотация @Controller с точки зрения контекста Spring

Аннотация @Controller — это bean аннотация, которая регистрирует класс в Spring контексте с особыми правилами обработки HTTP запросов. Разберу детально что происходит.

Базовое определение

@Controller
public class UserController {
    @GetMapping("/users")
    public String getUsers(Model model) {
        model.addAttribute("users", userService.getAll());
        return "users";  // Возвращает имя view (JSP, Thymeleaf)
    }
}

@Controller говорит Spring:

  1. Создай singleton bean из этого класса
  2. Регистрируй в ApplicationContext
  3. Обработай методы с @RequestMapping / @GetMapping / @PostMapping
  4. Возвращаемое значение — имя view (не JSON)

Жизненный цикл при запуске Spring

1. Spring сканирует @ComponentScan
2. Находит класс с @Controller
3. Создаёт BeanDefinition
4. Instantiates объект (singleton)
5. Инъектирует зависимости (@Autowired)
6. Вызывает @PostConstruct методы
7. Регистрирует в ApplicationContext
8. Регистрирует URL handlers в DispatcherServlet

Как Spring регистрирует @Controller

// Spring видит эту аннотацию
@Controller
public class UserController {
    // И создаёт это в контексте:
}

// Эквивалент в XML:
<bean id="userController" class="com.example.UserController">
    <property name="userService" ref="userService" />
</bean>

// Или программно:
ApplicationContext context = new AnnotationConfigApplicationContext();
UserController controller = context.getBean(UserController.class);

@Controller vs @RestController

@Controller:

@Controller
public class UserController {
    @GetMapping("/users")
    public String getUsers(Model model) {
        return "users";  // Возвращает имя view
    }
}

// HTTP Response:
// 200 OK
// Content-Type: text/html; charset=UTF-8
// <html>...</html>  (Thymeleaf template)

@RestController:

@RestController
public class UserController {
    @GetMapping("/users")
    public List<User> getUsers() {
        return userService.getAll();  // Возвращает JSON
    }
}

// HTTP Response:
// 200 OK
// Content-Type: application/json
// [{"id": 1, "name": "Alice"}, ...]

@RestController = @Controller + @ResponseBody на все методы

Что происходит в Spring контексте

1. Регистрация в ApplicationContext

ApplicationContext context = SpringApplication.run(App.class);

// UserController создан и зарегистрирован
UserController controller = context.getBean(UserController.class);
System.out.println(controller);  // Выведет бин

// Проверить все беины
String[] beanNames = context.getBeanDefinitionNames();
for (String name : beanNames) {
    System.out.println(name);
}
// Включит "userController"

2. Инъекция зависимостей

@Controller
public class UserController {
    @Autowired  // Spring инъектирует зависимость
    private UserService userService;
    
    @Autowired  // Также инъектируется
    private UserRepository userRepository;
    
    // Spring поймёт эти зависимости и инъектирует
}

3. Регистрация URL handlers

@Controller
public class UserController {
    @GetMapping("/users")      // Spring регистрирует эту URL
    public String getUsers() { return "users"; }
    
    @PostMapping("/users")     // И эту
    public String createUser() { return "user-created"; }
}

// DispatcherServlet знает:
// GET /users -> userController.getUsers()
// POST /users -> userController.createUser()

Полный жизненный цикл

@Controller
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserService userService;
    
    @PostConstruct
    public void init() {
        System.out.println("Controller initialized");
    }
    
    @GetMapping
    public String list(Model model) {
        model.addAttribute("users", userService.getAll());
        return "users-list";  // View name
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("Controller destroyed");
    }
}

// Порядок выполнения при старте Spring:
// 1. Создание экземпляра UserController
// 2. Инъекция userService
// 3. Вызов @PostConstruct (init())
// 4. Регистрация в контексте
// 5. Регистрация URL handlers (GET /users -> list())
// 6. "Controller initialized" вывод

// При запросе GET /users:
// 1. DispatcherServlet получает запрос
// 2. Ищет handler для /users
// 3. Находит UserController.list()
// 4. Вызывает метод
// 5. list() возвращает "users-list"
// 6. ViewResolver ищет template/users-list.html
// 7. Renders HTML и отправляет клиенту

// При shutdown Spring:
// 1. Вызов @PreDestroy (cleanup())
// 2. "Controller destroyed" вывод
// 3. Удаление бина из контекста

Области видимости (Scopes)

// По умолчанию @Controller — Singleton
@Controller
public class UserController {
    // Этот объект создаётся ОДИН раз
    // И переиспользуется для всех запросов
}

// Но можно изменить scope
@Controller
@Scope("prototype")  // Новый объект для каждого запроса
public class UserController {
    // Требует особой конфигурации для работы с контекстом
}

// Или request scope
@Controller
@Scope("request")  // Новый объект для каждого HTTP запроса
public class UserController {
    // Специфично для web приложений
}

Получение доступа к контексту из Controller

@Controller
public class UserController {
    @Autowired
    private ApplicationContext context;  // Инъектируем контекст
    
    @GetMapping("/info")
    public String info(Model model) {
        // Получить другой бин из контекста
        UserService service = context.getBean(UserService.class);
        
        // Получить все беины определённого типа
        Map<String, UserService> services = 
            context.getBeansOfType(UserService.class);
        
        // Проверить наличие бина
        boolean exists = context.containsBean("userService");
        
        return "info";
    }
}

Обработка исключений в контексте

@Controller
public class UserController {
    @GetMapping("/users/{id}")
    public String getUser(@PathVariable Long id, Model model) {
        User user = userService.getUser(id);
        if (user == null) {
            throw new UserNotFoundException("User not found");
        }
        model.addAttribute("user", user);
        return "user-detail";
    }
    
    @ExceptionHandler(UserNotFoundException.class)
    public String handleUserNotFound(UserNotFoundException e, Model model) {
        // Spring контекст знает о всех @ExceptionHandler
        model.addAttribute("error", e.getMessage());
        return "error";
    }
}

Прокси создание (AOP)

Если используешь @Transactional или другие аспекты, Spring создаёт прокси:

@Controller
public class UserController {
    @Transactional  // Spring создаёт прокси
    public void saveUser(User user) {
        userService.save(user);
    }
}

// Spring на самом деле создаёт:
// UserController proxy = new Cglib$UserController$$EnhancedByCGLIB() {
//     public void saveUser(User user) {
//         // Начать транзакцию
//         try {
//             super.saveUser(user);
//             // Commit
//         } catch (Exception e) {
//             // Rollback
//         }
//     }
// }

Отладка контекста

@Component
public class ContextDebugger implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext context) {
        // Получаем контекст
        String[] beanNames = context.getBeanDefinitionNames();
        
        System.out.println("=== Registered Beans ===");
        for (String name : beanNames) {
            if (name.contains("Controller")) {
                BeanDefinition def = 
                    ((DefaultListableBeanFactory) context)
                        .getBeanDefinition(name);
                System.out.println("Bean: " + name);
                System.out.println("Class: " + def.getBeanClassName());
                System.out.println("Scope: " + def.getScope());
            }
        }
    }
}

Итог

@Controller с точки зрения контекста:

  1. Регистрация в ApplicationContext — бин создаётся при startup
  2. Singleton по умолчанию — переиспользуется для всех запросов
  3. Инъекция зависимостей — все @Autowired поля инъектируются
  4. Регистрация URL handlers — методы с @GetMapping/PostMapping регистрируются в DispatcherServlet
  5. Обработка View — возвращаемое значение — имя view, не JSON
  6. AOP прокси — если используются аспекты (@Transactional), создаётся прокси
  7. Lifecycle методы — @PostConstruct и @PreDestroy вызываются
  8. Scope управляется — по умолчанию singleton, но можно изменить на request или prototype