alistairKane
alistairKane

Reputation: 23

Storing state within a controller in Spring

Looking for some advice, trying to make a card game. I am a complete beginner in Spring MVC (incase the following question doesn't already give that away..)

Using a proxy session scoped Bean to manage the Game state, my routes seem to work correctly using Postman and are able to create a new player.

Game class

@Component
public class Game {
private Deck deck;
private ArrayList<Player> players;
private HashMap<Player, Integer> wins;

@Autowired
public Game(Deck deck){
    this.deck = deck;
    this.players = new ArrayList<>();
    this.wins = new HashMap<>();
}

public ArrayList<Player> getPlayers() {
    return players;
}

public void addPlayer(String name){
    this.players.add(new Player(name));

ApplicationConfig class

@EnableWebMvc
@Configuration
@ComponentScan("com.kane.cardgame")
public class ApplicationConfig {
  private Deck deck = new Deck();

  @Bean
  @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = 
  ScopedProxyMode.TARGET_CLASS)
  public Game game(){
    return new Game(deck);
  }
}

Controller

@CrossOrigin
@RestController(value="PlayerController")
@RequestMapping("/game/players")
public class PlayerController {
  @Autowired
  private Game game;

  @RequestMapping(method=RequestMethod.GET, value="/")
  public ArrayList<Player> allPlayers(){
    return game.getPlayers();
  }

  @RequestMapping(method= RequestMethod.POST, value="/")
  public ArrayList<Player> addPlayer(@RequestParam(value="name") String 
  name){
    game.addPlayer(name);
    return game.getPlayers();
  }

React Component that makes Request

const handleLinkClick = e => {
const url = `http://localhost:8080/game/players/? 
name=${this.state.name}`
  fetch(url, {
    method: 'POST',
    mode: 'cors'
  })
  .then(res => res.json())
  .then(res => {
    const newState = this.state.newPlayers;
    newState.push(res);
    this.setState({newPlayers: newState})
  })
 }

 return(
 <div>
   <h1>Enter your name</h1>
   <input id="name-input" type="text" name="name" onChange= . 
   {handleNameChange}/>
   <Link to="/newPlayer" onClick={handleLinkClick}>Start Game</Link>
  </div>
  )

When I make these requests in Postman, everything is fine. It works as it should - the post route is triggered and it adds a new player, and when I trigger the Get route it returns all the players with a new one added.

When I use React to make this request, it seems to create an instance of my Player class (since the id increments), but it does not seem to add a player to the ArrayList contained within the Game. I can get it to return the Player that it just created as JSON and store it in state. But when I fetch the URL associated to the Get route to return the players of the game, there are none stored. My hunch is that I am not storing this Game state properly.

My guess is that this probably comes down to a fundamental misunderstanding of how this Bean works. Any advice would be greatly appreciated.

Upvotes: 1

Views: 3223

Answers (2)

akourt
akourt

Reputation: 5563

OK here goes,

There are several things here that are amiss. First let's start with the Game class. This is annotated with @Component. This means that Spring's automatic classpath scanning with pick this up and configure automatically. With that being said this will be readily available for you to inject whenever you need it. At the same time, this means that the manual instatiation you perform in the ApplicationConfig class is unecessary. In there you manually instantiate Game and attempt to bring it into Spring context with the @Bean annotation. This is wrong and can lead to unexpected results. Either keep the @Component class or the other.

Secondly, Game is actually a service component that manages the whole game. With that in mind I would suggest to remove the @Component and add a @Service annotation to better denote it's actually usage (it's a service after all). The @Autowired annotation on it's contructor is also unneeded since you're not effectively injecting any Spring managed beans in there. Instead if you want to perform, post bean creation actions (i.e initialize the Deck object and the various lists and maps) just create a new void method and annotate it with @PostConstruct. While there also, make sure to change these declarations to from private ArrayList<Player> players; to private List<Player> players; as it's always better to code to the interface than the actual implementation. Same goes for the return argument of getPlayers.

Finally with all those in place, just inject the Game class into your PlayerController (also make sure to do this using the preferred constructor based DI method) and you should be good to go.

Finally, I suggest you have a good read on how Spring and in general DI works before you proceed with this, otherwise you'll keep running into problems.

Edit: also as mentioned in the above answer, client side changes should also take place.

Upvotes: 3

Gnk
Gnk

Reputation: 720

Change @RequestParam to @RequestBody and try to use something like this

fetch('http://localhost:8080/game/players/', {
    method: 'POST',
    mode: 'cors'
},
body: JSON.stringify({
    name: ${this.state.name},
})
)
.then(res => res.json())
.then(res => {
    const newState = this.state.newPlayers;
    newState.push(res);
    this.setState({newPlayers: newState})
})

Upvotes: 0

Related Questions