Facciamo l'esempio più semplice: hai un'applicazione e usi solo il classloader predefinito. Hai una classe che, per qualsiasi motivo, decidi che non dovrebbe avere più di un'istanza nell'applicazione. (Pensa a uno scenario in cui più persone lavorano su parti dell'applicazione).
Se non stai usando il framework Spring, il pattern Singleton garantisce che non ci sarà più di un'istanza di una classe nella tua applicazione. Questo perché non è possibile creare istanze della classe eseguendo "new" perché il costruttore è privato. L'unico modo per ottenere un'istanza della classe è chiamare un metodo statico della classe (solitamente chiamato "getInstance") che restituisce sempre la stessa istanza.
Dire che stai usando il framework Spring nella tua applicazione, significa semplicemente che oltre ai modi regolari per ottenere un'istanza della classe (metodi nuovi o statici che restituiscono un'istanza della classe), puoi anche chiedere a Spring di farti un'istanza di quella classe e Spring farà in modo che ogni volta che chiedi un'istanza di quella classe restituirà sempre la stessa istanza, anche se non hai scritto la classe utilizzando il pattern Singleton. In altre parole, anche se la classe ha un costruttore pubblico, se chiedi sempre a Spring un'istanza di quella classe, Spring chiamerà quel costruttore solo una volta durante la vita dell'applicazione.
Normalmente, se stai usando Spring, dovresti usare Spring solo per creare istanze e puoi avere un costruttore pubblico per la classe. Ma se il tuo costruttore non è privato, non stai realmente impedendo a nessuno di creare direttamente nuove istanze della classe, bypassando Spring.
Se vuoi veramente una singola istanza della classe, anche se usi Spring nella tua applicazione e definisci la classe in Spring come singleton, l'unico modo per assicurarti che sia implementare anche la classe usando il pattern Singleton. Ciò garantisce che ci sarà una singola istanza, indipendentemente dal fatto che le persone utilizzino Spring per ottenere un'istanza o ignorino Spring.