Inicio
Arrancando con un proyecto
Para crear un proyecto de una forma rápida y sencilla he encontrado Spring Initializr. Es muy simple, basta con elegir lenguaje, versión y otras configuraciones, como los metadatos del proyecto, y finalmente las dependencias del proyecto Spring que usaremos.
Si elegimos “generar” nos descarga un archivo zip que cual contiene el proyecto creado ya preparado para empezar a trabajar con él.
Estructura de un proyecto
En el archivo pom.xml tenemos la configuración que realizamos en Spring Initializr.
En src → main → Java → com.example.demo, tenemos una clase DemoApplication que tiene un método main con algo de código, que nos servirá para comprobar que todo está bien y que nuestro proyecto está preparado para ser usado.
En src → main → Resources, tenemos dos carpetas para guardar archivos para nuestra aplicación web, como plantillas.
Inyección de dependencias
Repositorios
Por convención para guardar datos se usan los repositorios. A la clase que definamos como repositorio le añadimos la anotación “@Repository” para que el framework entienda que se usara dicha clase como la capa de repositorio.
@Repository
public class PersonaRepositoryImpl implements PersonaRepository {
private static Logger LOG = LoggerFactory.getLogger(PruebaSpringBootApplication.class);
@Override
public void registrar(String nombre) {
LOG.info("SE REGISTRO A "+ nombre);
}
}
Servicios
Es la capa de lógica de negocio. La clase debe llevar la anotación “@Service” para indicar que funcionará como la capa de negocio. Si ponemos el ejemplo de que éste servicio actúa sobre un repositorio, podemos crear una instancia del repositorio con la etiqueta “@Autowired”, para que sea el propio Spring quien se encargue de crear una instancia de la clase.
@Service
public class PersonaServiceImpl implements PersonaService {
@Autowired
private PersonaRepository repo;
@Override
public void registrar(String nombre) {
repo.registrar(nombre);
}
}
Qualifier
Para tener varias implementaciones de una interfaz usaremos la etiqueta “@Qualifier”. En éste ejemplo tenemos dos tipos de personas (repositorios), y después desde un servicio accederemos a uno de los repos con la misma anotación.
@Repository
@Qualifier("persona1")
public class PersonaRepositoryImpl implements PersonaRepository {
private static Logger LOG = LoggerFactory.getLogger(PruebaSpringBootApplication.class);
@Override
public void registrar(String nombre) {
LOG.info("SE REGISTRO A "+ nombre);
}
}
/**************************************************************************************/
@Repository
@Qualifier("persona2")
public class PersonaRepositoryImpl2 implements PersonaRepository {
private static Logger LOG = LoggerFactory.getLogger(PruebaSpringBootApplication.class);
@Override
public void registrar(String nombre) {
LOG.info("SE REGISTRO A "+ nombre + "(persona2)");
}
}
/**************************************************************************************/
@Service
public class PersonaServiceImpl implements PersonaService {
@Autowired
@Qualifier("persona2")
private PersonaRepository repo;
@Override
public void registrar(String nombre) {
repo.registrar(nombre);
}
}
Spring Data (JPA)
Vamos a crear una aplicación que se conecte a una base de datos usando JPA, un módulo que facilita la implementación de repositorios basados en JPA. Creamos una carpeta “modelos” y dentro una clase que definirá el modelo de nuestra tabla en la BD.
Persona.java
@Entity
public class Persona {
@Id
private int idPersona;
@Column(name = "nombre",length = 50)
private String nombre;
public int getIdPersona() {
return idPersona;
}
public void setIdPersona(int idPersona) {
this.idPersona = idPersona;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
}
Después, creamos dentro de “repositorios” una interfaz que extienda de “JpaRepository”.
IPersonaRepositorio.java
public interface IPersonaRepositorio extends JpaRepository<Persona,Integer> {
}
Es necesario que el archivo application.properties tenga configurado lo siguiente:
spring.jpa.database=POSTGRESQL
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost/Welcome
spring.datasource.username=postgresql
spring.datasource.password=passwword
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
El siguiente paso es crear la base de datos desde pgAdmin 4 (Postgresql), y ejecutar la aplicación para que se nos cree la estructura de tablas.
Para que se nos cree una tabla en la BD y meter datos dentro de ésta, definiremos nuestro método para insertar datos a la tabla.
WelcomeController.java
@Autowired
private IPersonaRepositorio repositorio;
@GetMapping("/welcome")
public String welcome(@RequestParam(name="name",required = false,defaultValue = "suso") String name, Model model) {
Persona p = new Persona();
p.setIdPersona((int) repositorio.count());
p.setNombre(name);
repositorio.save(p);
model.addAttribute("name", name);
return "welcome";
}
Tendremos otro método que listara todo el contenido de la tabla. Le crearemos una plantilla para mostrar los datos guardados en el modelo.
WelcomeController.java
@GetMapping("/listar")
public String todasLasPersonas(Model model) {
model.addAttribute("personas",repositorio.findAll());
return "personas";
}
Ahora nos queda recorrer en la vista la lista de personas guardadas en el Model.
Personas.html
<!DOCTYPE html>
<html lang="en" xmlns:th="<http://www.thymeleaf.org>">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table>
<th:block th:each="persona : ${personas}">
<tr>
<td th:text="${persona.idPersona}"></td>
<td th:text="${persona.nombre}"></td>
</tr>
</th:block>
</table>
</body>
</html>
Servicios Rest (CRUD)
Se usa para describir cualquier interfaz entre sistemas que utilice directamente HTTP para obtener datos o indicar la ejecución de operaciones sobre los datos en cualquier formato (XML, JSON, etc).
Para crear un controlador que utilice los servicios Rest utilizamos la anotación “@RestController”.
Método Get
Si queremos definir la operación get, usaremos “@GetMapping”.
Método Post
Si queremos definir la operación Post, usaremos “@PostMapping”. Es necesario pasar como parámetro “@RequestBody Objeto o”.
Método Put
Si queremos definir la operación Put, usaremos “@PutMapping”. Funciona igual que el Post, pero con la diferencia de que si encuentra un id ya existente la operación será una actualización.
Método Delete
Si queremos definir la operación Delete, usaremos “@DeleteMapping”, que llevar como parámetro el valor (id) que se recoge en la ruta. En el argumento del método, recogemos el id mediante “@PathVariable(id)”
RestDemoController.java
@RestController
public class RestDemoController {
@Autowired
private IPersonaRepositorio repositorio;
@GetMapping
public List<Persona> listar(){
return repositorio.findAll();
}
@PostMapping
public void insertar(@RequestBody Persona persona){
repositorio.save(persona);
}
@PutMapping
public void modificar(@RequestBody Persona persona){
repositorio.save(persona);
}
@DeleteMapping(value = "/{id}")
public void eliminar(@PathVariable("id") Integer id){
repositorio.deleteById(id);
}
}
MVC Thymeleaf
Controladores
Por un lado tenemos los controladores, anotamos la clase mediante “@Controller”. Los métodos que se desea que funcionen como ruta de nuestra aplicación web, debe tener la anotación “@GetMapping(‘ruta_a_elegir’)”.
@Controller
public class MainController {
@GetMapping("/welcome")
public String welcome(@RequestParam(name="name",required = false,defaultValue = "suso") String name, Model model){
model.addAttribute("name",name);
return "Welcome";
}
}
Si se pasa parámetro en la ruta, se recoge mediante “@RequestParam” en éste caso en la variable name(String) y se guarda en la variable model(Model), la cual lo usamos para pasar datos a las vistas.
Vistas
Para crear las vistas añadimos nuestras páginas html en la carpeta “Templates”.
El controlador debe indicar en el return el nombre de la vista a la que se dirige.
Necesitamos referenciar en la etiqueta html el motor de plantillas de thymeleaf (xmlns:th="http://www.thymeleaf.org"). Para incrustar texto en una etiqueta usando dicho motor, usamos “th:text”, al cual le podemos indicar una variable guardada en el objeto model con “${variable}”.
<!DOCTYPE html>
<html lang="en" xmlns:th="<http://www.thymeleaf.org>">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="'Bienvenido, '+${name}"/>
</body>
</html>
Spring security
Éstas dependencias nos crearán automáticamente un login para nuestra aplicación web, en el que podemos editar el usuario y contraseña de la siguiente manera en el archivo application.properties spring.security.user.name = pady spring.security.user.password= 1234 Es un ejemplo poco práctico, ya que lo óptimo sería comprobar los usuarios mediante una base de datos. Creamos la clase usuario y su repositorio.
Usuario.java
@Entity
public class Usuario {
@Id
private int id;
private String nombre;
private String clave;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getClave() {
return clave;
}
public void setClave(String clave) {
this.clave = clave;
}
}
IUsuarioRepositorio.java
public interface IUsuarioRepositorio extends JpaRepository<Usuario,Integer> {
}
Agregamos nuestro usuario a la tabla
SpringDataApplicationTests.java
@Autowired
private IUsuarioRepositorio repositorio_usuarios;
@Test
void crearUsuario() {
Usuario usuario = new Usuario();
usuario.setId(0);
usuario.setClave("1234");
usuario.setNombre("pady");
Usuario actual = repositorio_usuarios.save(usuario);
assertEquals("1234",actual.getClave());
}
Vamos a crear una clase de configuración en nuestro proyecto, en la que usaremos un @Bean de Spring para cifrar las contraseñas que se guardan en la base datos.
SecurityConfig.java
@Configuration
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder passwordEncoder(){
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
}
Finalmente lo instanciamos en la clase donde interese, y usamos su método encode.
SpringDataApplicationTests.java
@Autowired
private BCryptPasswordEncoder encoder;
@Test
void crearUsuario() {
Usuario usuario = new Usuario();
usuario.setId(1);
usuario.setClave(encoder.encode("1234"));
String expected = usuario.getClave();
usuario.setNombre("pady");
Usuario actual = repositorio_usuarios.save(usuario);
assertEquals(expected,actual.getClave());
}
Para habilitar @WebSecurity necesitamos que nuestra clase de configuración extienda de WebSecurityConfigureAdapter . Sobreescibrimos los métodos configure para indicar de donde sacaremos los usuarios a comparar. Pero primero debemos crear una clase UserService que extienda de UserDetailsService , en la cual instanciamos el repositorio de usuarios, y sobrescribimos el método de la clase extendida. Para configurar el método de la clase extendida, tenemos que tener un método que busque por un nombre de usuario, y no por un id, así que vamos a la interfaz repositorio de usuarios y creamos dicho método.
IUsuarioRepositorio.java
public interface IUsuarioRepositorio extends JpaRepository<Usuario,Integer> {
Usuario findByNombre(String nombre);
}
UserService.java
@Service
public class UserService implements UserDetailsService {
@Autowired
private IUsuarioRepositorio repositorio;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Usuario encontrado = repositorio.findByNombre(username);
List<GrantedAuthority> roles = new ArrayList<>();
roles.add(new SimpleGrantedAuthority("ADMIN"));
UserDetails userDet = new User(encontrado.getNombre(),encontrado.getClave(),roles);
return userDet;
}
}
SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userServiceDetails;
@Autowired
private BCryptPasswordEncoder bcrypt;
@Bean
public BCryptPasswordEncoder passwordEncoder(){
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServiceDetails).passwordEncoder(bcrypt);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
}
PROYECTO DE EJEMPLO
⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
RESUMEN
Todo lo necesario para implementar un login mediante JPA, utilizando Postgresql como motor de base de datos.
- Definir un modelo
@Entity
public class Usuario {
@Id
private int id;
private String nombre;
private String clave;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getClave() {
return clave;
}
public void setClave(String clave) {
this.clave = clave;
}
}
- Crear un repositorio
public interface UsuarioRepository extends JpaRepository<Usuario,Integer> {
Usuario findByNombre(String nombre);
}
- Servicio de usuarios
@Service
public class UsuarioService implements UserDetailsService {
@Autowired
private UsuarioRepository repositorio;
@Autowired
private BCryptPasswordEncoder encoder;
public void crearUsuario() {
Usuario usuario = new Usuario();
usuario.setId((int)repositorio.count()+1);
usuario.setClave(encoder.encode("1234"));
usuario.setNombre("pady");
repositorio.save(usuario);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Usuario encontrado = repositorio.findByNombre(username);
List<GrantedAuthority> roles = new ArrayList<>();
roles.add(new SimpleGrantedAuthority("ADMIN"));
UserDetails userDet = new User(encontrado.getNombre(),encontrado.getClave(),roles);
return userDet;
}
}
- Security Config
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UsuarioService userServiceDetails;
@Autowired
private BCryptPasswordEncoder bcrypt;
@Bean
public BCryptPasswordEncoder passwordEncoder(){
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServiceDetails).passwordEncoder(bcrypt);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
- Creación de usuarios
@SpringBootTest
class PracticaSpringBootApplicationTests {
@Autowired
private UsuarioService servicio;
@Test
void contextLoads() {
servicio.crearUsuario();
}
}