0

I am new to Spring, this is maybe newbie question.

I create a Repository:

import org.springframework.data.jpa.repository.JpaRepository;

public interface TaskRepository extends JpaRepository<Task, String>{
}

The Repository work fine with my RestController

import org.springframework.web.bind.annotation.*;



@RestController
public class TaskController {
    private final TaskRepository repository;

    TaskController(TaskRepository repository) {
        this.repository = repository;
    }


    // Aggregate root
    // tag::get-aggregate-root[]
    @GetMapping("/Tasks")
    public List<Task> all() {
        return repository.findAll();
    }
    // end::get-aggregate-root[]

    @PostMapping("/Tasks")
    public Task newTask(@RequestBody Task newTask) {
        return repository.save(newTask);
    }

    // Single item

    @GetMapping("/Tasks/{id}")
    public Task one(@PathVariable String id) {

        return repository.findById(id)
                .orElseThrow(() -> new TaskNotFoundException(id));
    }

    @PutMapping("/Tasks/{id}")
    public Task replaceTask(@RequestBody Task newTask, @PathVariable String id) {

        return repository.findById(id)
                .map(Task -> {
                    Task.setSparkAppId(newTask.getSparkAppId());
                    Task.setValue(newTask.getValue());
                    return repository.save(Task);
                })
                .orElseGet(() -> {
                    return repository.save(newTask);
                });
    }

    @DeleteMapping("/Tasks/{id}")
    public void deleteTask(@PathVariable String id) {
        repository.deleteById(id);
    }
}

However, I want to use that JpaRepository for my internal logic. I create a TaskHandler listener, that will update new Task in the database.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.spark.launcher.SparkAppHandle;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Optional;
import java.util.List;
public class TaskListener implements SparkAppHandle.Listener {
    @Autowired
    private TaskRepository repository;

    private static final Logger logger = LoggerFactory.getLogger(TaskListener.class);

    public Task task;

    public TaskListener(Task task) {
        this.task = task;
    }

    @Override
    public void stateChanged(SparkAppHandle handle) {
        String curAppId = handle.getAppId();
        SparkAppHandle.State curState = handle.getState();

        String curStateStr = curState.toString();
        logger.info("app id " + curAppId + " state " + curStateStr);
        Optional<Throwable> opErr = handle.getError();
        opErr.ifPresent(throwable -> logger.error(throwable.getMessage()));
        this.task.setSparkAppId(curAppId);
        this.task.setState(curStateStr);
        //replaceTask(task, task.getTaskId());
    }

    @Override
    public void infoChanged(SparkAppHandle handle) {
        String curAppId = handle.getAppId();
        SparkAppHandle.State curState = handle.getState();
        String curStateStr = curState.toString();
        logger.info("app id " + curAppId + " state " + curStateStr);
//        SparkTaskModel.getInstance().executeUpdateTask(this.taskId, curAppId, curStateStr);
        Optional<Throwable> opErr = handle.getError();
        if (opErr.isPresent()){
            logger.error(opErr.get().getMessage());
        }

        this.task.setSparkAppId(curAppId);
        this.task.setState(curStateStr);
        //replaceTask(task, task.getTaskId());
    }

    // not work because repository is null
    public Task replaceTask(Task newTask, String id) {

        return repository.findById(id)
                .map(Task -> {
                    Task.setSparkAppId(newTask.getSparkAppId());
                    Task.setValue(newTask.getValue());
                    return repository.save(Task);
                })
                .orElseGet(() -> {
                    return repository.save(newTask);
                });
    }
}

I cannot put the Repository into Contructor because I create the TaskListener object in my program, and I don't know what to put in place of Repository should it in TaskListener contructor.

Error: (xxx was my project name censored...)

java.lang.NullPointerException
    at xxx.xxx.xxx.xxx.spark_launcher_for_meow.xxx.tasksparklauncher.TaskListener.replaceTask(TaskListener.java:70)
    at xxx.xxx.xxx.xxx.spark_launcher_for_meow.xxx.tasksparklauncher.TaskListener.stateChanged(TaskListener.java:47)
    at org.apache.spark.launcher.AbstractAppHandle.fireEvent(AbstractAppHandle.java:160)
    at org.apache.spark.launcher.AbstractAppHandle.setState(AbstractAppHandle.java:137)
    at org.apache.spark.launcher.AbstractAppHandle.setState(AbstractAppHandle.java:124)
    at org.apache.spark.launcher.LauncherServer$ServerConnection.handle(LauncherServer.java:315)
    at org.apache.spark.launcher.LauncherConnection.run(LauncherConnection.java:58)
    at java.base/java.lang.Thread.run(Thread.java:829)

The error point to:

repository.findById(id).... 

in function

replaceTask

Said that repository is null.

Haha TTpro
  • 5,137
  • 6
  • 45
  • 71
  • Kindly share the error log that you see in the console – Jayanth Jul 07 '21 at 04:33
  • @Jayanth it was NullPointerException, i update question with more detail. – Haha TTpro Jul 07 '21 at 04:44
  • can you try @Repository annotation on TaskRepository class and run the application once and check ? – Jayanth Jul 07 '21 at 04:48
  • Take a look at the „Prototype“ scope and the „ObjectProvider“ interface. Let Spring create the instance for you and you‘ll get autowiring. – Martin Frey Jul 07 '21 at 05:23
  • @HahaTTpro when exactly you create your task listener? I think you are creating task listener manually right. What is the reason for manually creating listener? you can define "@component" annotation on your TaskListener then TaskRepository will be autowired – Lucia Jul 07 '21 at 05:29

2 Answers2

2

@Autowired won't work since TaskListener is not a bean managed by Spring, but a bean that you instantiate manually.

You can simply pass TaskRepository as 2nd constructor argument

public class TaskListener implements SparkAppHandle.Listener {
    
    private final TaskRepository repository;
    public final Task task;

    public TaskListener(TaskRepository repository, Task task) {
        this.repository = repository;
        this.task = task;
    }

    // other stuff
}

And then somewhere in your code your that instantiates TaskListener you autowire TaskRepository and use it as constructor parameter

@Service // << this means that bean is managed by Spring 
// and all autowirings will be automatically done by Spring 
// therefore taskRepository will be NOT null
public class SomeTaskListenerService {

    @Autowired
    private TaskRepository taskRepository;

    public void someMethod() {
        Task task = ... // obtain task
        TaskListener listener = new TaskListener(taskRepository, task);
        // do some actions with the listener
    }
}
Nikolai Shevchenko
  • 7,083
  • 8
  • 33
  • 42
  • This work for me, but I hope there are better solution not involve passing repository around as parameter in constructor. – Haha TTpro Jul 07 '21 at 17:00
-1

You can use multiple constructors in your TaskListener class. But in this case, you have to add @Autowired for the constructor where spring injection is required.

So your TaskListener class, you can modify like this.

private final TaskRepository repository;

public Task task;

public TaskListener(Task task) {
    this.task = task;
}

@Autowired
TaskListener(TaskRepository repository) {
    this.repository = repository;
}