Java Play Framework & Depedency Injection mit Spring

Timmey92

Commodore
Registriert
Okt. 2008
Beiträge
4.563
Moin,

ich arbeite mich im Moment in AngularJS auf Frontend und Play Framework (Java) auf Serverseite ein. Als DI Framework kommt Spring zum Einsatz. Auf das Problem bin ich gestoßen, als ich in einer Authentifizierungsklasse das UserRepository injizieren wollte. NullPointerException, geht also nicht.

Versionen: (build.sbt)
Play Framework 2.3.8
Code:
libraryDependencies ++= Seq(
  javaCore,
  javaJpa,
  javaJpa.exclude("org.hibernate.javax.persistence", "hibernate-jpa-2.0-api"),
  "org.hibernate" % "hibernate-entitymanager" % "4.3.9.Final",
  "org.springframework" % "spring-context" % "4.1.6.RELEASE",
  "javax.inject" % "javax.inject" % "1",
  "org.springframework.data" % "spring-data-jpa" % "1.8.0.RELEASE",
  "org.postgresql" % "postgresql" % "9.4-1201-jdbc41",
  "org.mockito" % "mockito-core" % "1.9.5" % "test"
)

Um das ganze lauffähig zu bekommen habe ich in der Global.java (nach dem Tutorial auf der Play Seite) folgendermaßen:
Code:
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.hibernate3.HibernateExceptionTranslator;
import org.springframework.orm.jpa.JpaTransactionManager;
import play.Application;
import play.GlobalSettings;

import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

/**
 * Application wide behaviour. We establish a Spring application context for the dependency injection system and
 * configure Spring Data.
 */
public class Global extends GlobalSettings {

    /**
     * The name of the persistence unit we will be using.
     */
    static final String DEFAULT_PERSISTENCE_UNIT = "default";

    /**
     * Declare the application context to be used - a Java annotation based application context requiring no XML.
     */
    final private AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();

    /**
     * Sync the context lifecycle with Play's.
     */
    @Override
    public void onStart(final Application app) {
        super.onStart(app);
        System.out.println("Loading spring configuration");
        // AnnotationConfigApplicationContext can only be refreshed once, but we do it here even though this method
        // can be called multiple times. The reason for doing during startup is so that the Play configuration is
        // entirely available to this application context.
        ctx.register(SpringDataJpaConfiguration.class);
        ctx.scan("controllers", "models");
        ctx.refresh();

        // This will construct the beans and call any construction lifecycle methods e.g. @PostConstruct
        ctx.start();


        // load the demo data in dev mode
        /*if (Play.isDev() && !userRepository.findAll().iterator().hasNext()) {
            DemoData.loadDemoData(userRepository);
        }*/
    }

    /**
     * Sync the context lifecycle with Play's.
     */
    @Override
    public void onStop(final Application app) {
        // This will call any destruction lifecycle methods and then release the beans e.g. @PreDestroy
        ctx.close();

        super.onStop(app);
    }

    /**
     * Controllers must be resolved through the application context. There is a special method of GlobalSettings
     * that we can override to resolve a given controller. This resolution is required by the Play router.
     */
    @Override
    public <A> A getControllerInstance(Class<A> aClass) {
        return ctx.getBean(aClass);
    }

    /**
     * This configuration establishes Spring Data concerns including those of JPA.
     */
    @Configuration
    @EnableJpaRepositories("models")
    public static class SpringDataJpaConfiguration {

        @Bean
        public EntityManagerFactory entityManagerFactory() {
            return Persistence.createEntityManagerFactory(DEFAULT_PERSISTENCE_UNIT);
        }

        @Bean
        public HibernateExceptionTranslator hibernateExceptionTranslator() {
            return new HibernateExceptionTranslator();
        }

        @Bean
        public JpaTransactionManager transactionManager() {
            return new JpaTransactionManager();
        }
    }
}

In den Controllern funktioniert die Injizierung ganz gut (Konstruktorbasierte Injizierung):
Code:
@Named
@Singleton
@Security.Authenticated(SecuredApi.class)
public class WorkoutREST extends Controller {

    private final WorkoutRepository workoutRepository;

    @Inject
    public WorkoutREST(final WorkoutRepository workoutRepository) {
        this.workoutRepository = workoutRepository;
    }

    public Result getAll() {
        List<Workout> workout = (List<Workout>) workoutRepository.findAll();
        return ok(toJson(workout));
    }

    public Result get(long id) {
        Workout workout = workoutRepository.findOne(id);
        return ok(toJson(workout));
    }

    public Result add() {
        Http.RequestBody body = request().body();
        Workout workout = Workout.fromJson(body.asJson());
        workoutRepository.save(workout);
        return ok("Got json: " + body.asJson());
    }

    public Result update(Long id) {
        Workout workout = fromJson(request().body().asJson(), Workout.class);
        workoutRepository.save(workout);
        return ok(toJson(workout));
    }

In diesem Code kommt das Problem, Zeile 22. Über den Konstruktor kann ich das nicht injizieren, weil das Play Framework dann irgendwie einen Default Konstruktor vermisst (der wird erst zur Laufzeit erstellt).
Code:
package controllers.security;

import models.User;
import org.springframework.beans.factory.annotation.Autowired;
import play.mvc.Http.Context;
import play.mvc.Result;
import play.mvc.Security;
import models.UserRepository;

import javax.inject.Inject;

public class SecuredApi extends Security.Authenticator {

    private UserRepository userRepository;

    @Override
    public String getUsername(Context ctx) {
        User user = null;
        String[] authTokenHeaderValues = ctx.request().headers().get(SecurityController.AUTH_TOKEN_HEADER);
        if ((authTokenHeaderValues != null) && (authTokenHeaderValues.length == 1) && (authTokenHeaderValues[0] != null)) {
            System.out.println("Auth Token Content: "+ authTokenHeaderValues[0]);
            user = userRepository.findByAuthToken(authTokenHeaderValues[0]);
            if (user != null) {
                ctx.args.put("user", user);
                return user.getEmailAddress();
            }
        }

        return null;
    }

    @Inject
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public Result onUnauthorized(Context ctx) {
        return unauthorized();
    }
}

Ideen was ich falsch mache, bzw. wie ich dort mein JPA Repository injiziert bekomme?
 
Zuletzt bearbeitet:
Ich kann dir sagen warum es nicht funktioniert. Allerdings keine technisch saubere Lösung anbieten. (Hab Play noch nie verwendet)
In der Klasse "Global" werden in der Methode "public <A> A getControllerInstance(Class<A> aClass)" die controller vom application context geholt -> Injection.
Die Instanz der Klasse "SecuredApi" ist allerdings keine Spring bean (und wird auch nicht von derControllerInstance) erzeugt. Daher kannst du auch nichts Injecten.
 
Zurück
Oben