During learning Spring Boot I noticed attributes to methods in Spring Boot controllers can be automatically injected.

To migrate my blog from Grails to Spring Boot I created an implementation to inject the current blog instance based on an session attribute.

The following code snippets show a basic implementation. Be aware there is no proper error handling (e.g. null checks) or security (check if user has access to the instance) implemented.

Annotation

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.PARAMETER, ElementType.TYPE}) 
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentBlogInstance { 
}

Annotation Resolver

import javax.servlet.http.HttpSession;
import java.lang.annotation.Annotation;

@Component
public class BlogArgumentResolver implements HandlerMethodArgumentResolver {

    private final BlogRepository blogRepository;

    private final ObjectFactory<HttpSession> httpSessionFactory;

    @Autowired
    public BlogArgumentResolver(BlogRepository blogRepository, 
            ObjectFactory<HttpSession> httpSessionFactory) {
        this.blogRepository = blogRepository;
        this.httpSessionFactory = httpSessionFactory;
    }

    public boolean supportsParameter(MethodParameter parameter) {
        return findMethodAnnotation(CurrentBlogInstance.class, parameter) != null;
    }

    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer, 
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {

        Object blogId = httpSessionFactory.getObject().getAttribute("blogId");

        if (!(blogId instanceof Long)) {
            return null;
        }

        Blog blogInstance = blogRepository.findById((Long) blogId).get();
        
        if (!parameter.getParameterType().isAssignableFrom(blogInstance.getClass())) {

            throw new ClassCastException(blogInstance + " is not assignable to "
                    + parameter.getParameterType());
            
        }
        return blogInstance;
    }


    private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass,
                                                          MethodParameter parameter) {
        T annotation = parameter.getParameterAnnotation(annotationClass);
        if (annotation != null) {
            return annotation;
        }
        Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
        for (Annotation toSearch : annotationsToSearch) {
            annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(),
                    annotationClass);
            if (annotation != null) {
                return annotation;
            }
        }
        return null;
    }
}

Configuration

import ch.kobelnet.blog.util.BlogArgumentResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    BlogArgumentResolver blogArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(blogArgumentResolver);
    }

}

Usage

import ch.kobelnet.blog.domain.Article;
import ch.kobelnet.blog.domain.Blog;
import ch.kobelnet.blog.repo.ArticleRepository;
import ch.kobelnet.blog.util.CurrentBlogInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@Controller
public class ArticleController {

    @Autowired
    ArticleRepository articleRepository;

    @GetMapping("/admin/article/list")
    public String list(Model model, @CurrentBlogInstance Blog blog) {

        List<Article> articles = articleRepository.findAllByBlog(blog);
        model.addAttribute("articleList", articles);

        return "admin/article/list";
    }

}