Marc Hauschildt
Web Technologies and Computer Software Development Instructor at Kirkwood Community College in Cedar Rapids, IA.
Week 1 - Setup Pet Clinic App. Deployment using Docker.
Week 2 - Domains, AWS, ER Diagrams and Databases, Understand the Pet Clinic Setup.
Week 3 - MVC, Repository interfaces, Thymeleaf, Internationalization, Controller Unit Tests,
Week 4 - Use Case Narrative, Sequence Diagram, State Chart Diagram, Create new objects.
Lesson 5 - Users, roles, and permissions setup from Java 2. User Registration.
Lesson 6 - Homepage with content from the database. Custom not found and error pages.
Lesson 7 - Login and logout. Cookies. User permission access.
Lesson 8 - Edit and delete user profile. Password reset.
Lesson 9 - Pagination. Filtering and limiting records by category.
Lesson 10 - Web Sockets. Date and currency formatting.
Lesson 11 - Email and SMS messaging. Failed login attempts.
Lesson 12 - Shopping Cart. Payment processing.
Open Docker Desktop. Click the Images tab to see your previous images.
If you are running out of space, delete any old containers/images you no longer need. When deleting an image, you may get "Image is in use. Delete the container that's using it and try again."
Build a new image.
docker build -t <your-docker-username>/<your-projectname>:<your-version> .
For example:
docker build -t mlhaus/petclinic:v2 .
Refresh your list of images in Docker Desktop to see the newly created one.
In the world of containers, tags like v1 are treated as "immutable". The best practice is to increment the version number (v2, v3) or use the Git commit hash (e.g., :git-a1b2c3d).
Use Docker to run your image locally.
docker run -p 8099:8080 `
-e MYSQL_URL="jdbc:mysql://<your-db-host>:3306/<your-db-name>?useSSL=true" `
-e MYSQL_USER="<your-db-user>" `
-e MYSQL_PASS="<your-db-password>" `
<your-docker-username>/<your-projectname>:<your-version>
Remember to use backticks ` with Windows Powershell or backslashes \ with Git Bash.
Change MYSQL_PASS to MYSQL_PASSWORD if that is what you used.
Visit http://localhost:8099 to confirm it works.
Press Ctrl + C in the terminal to stop the server.
Use Docker to push your image to Docker Hub.
docker push <your-docker-username>/<your-projectname>:<your-version>
Go to your Docker Hub Repositories list to see your image.
https://hub.docker.com/repositories/
Click the repository name to see your tag list.
To deploy a new Docker container image to an existing Azure Container App, you need to update the container app's image reference using the Azure CLI.
Update the container app with the new image tag.
az containerapp update `
--name <your-container-app-name> `
--resource-group <your-resource-group-name> `
--image <your-docker-username>/<your-imagename>:<your-version>
During the "create" phase you included environment variables. You do not need to include them again.
View your live Azure Container App.
https://portal.azure.com/#browse/Microsoft.App%2FcontainerApps
"Find Owners" should be changed to "Find Schools".
For extra credit, set up a custom domain.
In ISO/IEC 12207:2008, Process 7.1.4 refers to the Software Detailed Design Process.
The purpose of the Software Detailed Design process is to provide a detailed design for the software components, including data structures, databases, and internal interfaces, that enables coding to take place.
In other words, you need to spend time planning and designing before coding.
In a Spring Boot application, behavior (Sequence) and data state (State Chart) diagrams are often valuable.
Description: Create New School
Actor: Administrator
Precondition: User is logged in and viewing the School List.
Normal Flow:
User clicks "Add School".
System displays the School Creation Form.
User enters required values "Name" and "Domain".
System validates that the domain does not already exist.
System saves the new School with status ACTIVE.
System redirects user to the School Details page (or School List).
Alternative Flows:
Validation Fail: Name or Domain is empty -> System re-displays form with error "Name is required" or "Domain is required".
Duplicate Fail: Domain exists -> System re-displays form with error "Domain already registered".
Here is Mermaid.js code for a Sequence Diagram that documents the flow for the "Create New School" use case.
You can render this code using the Mermaid Live Editor, by using the Mermaid plugin in IntelliJ, the Mermaid plugin in Miro, or using draw.io/diagrams.net
sequenceDiagram
autonumber
actor User as School Admin
participant Browser
participant Controller as SchoolController
participant Repo as SchoolRepository
participant View as Thymeleaf<br>Template Engine
Note over User, View: Phase 1: Requesting the Form
User->>Browser: Click "Add School" button
Browser->>Controller: GET /schools/new
activate Controller
Controller-->>Controller: new School()
Controller->>View: return "schools/createOrUpdateSchoolForm"
deactivate Controller
View-->>Browser: Render HTML Form
Note over User, View: Phase 2: Submitting the Data
User->>Browser: Fill out and submit form
Browser->>Controller: POST /schools/new
activate Controller
Controller->>Controller: Validate form
alt Validation Failed
Controller->>View: return "schools/createOrUpdateSchoolForm"
View-->>Browser: Render HTML Form with Error Messages
else Validation Passed
Controller->>Repo: save(school)
activate Repo
Repo-->>Controller: School Entity Saved
deactivate Repo
Controller-->>Browser: Redirect to "/schools"
end
deactivate Controller
Browser->>Controller: GET /schools (Redirect)
activate Controller
Controller->>Repo: findAll(Pageable)
activate Repo
Repo-->>Controller: Page<School>
deactivate Repo
Controller->>View: return "schools/schoolList"
deactivate Controller
View-->>Browser: Render HTML Updated School ListA State Chart Diagram models the lifecycle of a single object.
It shows every possible "state" the object can be in and exactly what events cause it to move from one state to another.
New School: The user typed data in the form, no ID is set yet.
Active School: The normal state. The school is visible.
Inactive/Suspended School: The school exists in the system but is flagged (e.g., stopped paying for a subscription or closed).
Deleted School: The school was "removed" from the database with a deleted_at timestamp. It is hidden from queries.
stateDiagram
[*] --> New : User clicks "Add School"
New --> Active : save() [Default]
state "Persisted (In Database)" as Persisted {
state "Visible / Live" as Live {
Active --> Inactive : Set Status = INACTIVE
Inactive --> Active : Set Status = ACTIVE
Active --> Suspended : Set Status = SUSPENDED
Suspended --> Active : Set Status = ACTIVE
}
Live --> Deleted : delete() (Set deleted_at = NOW)
state "Soft Deleted" as Deleted {
[*] --> Hidden
note right of Hidden : Excluded from default queries \n(@SQLRestriction)
}
Deleted --> Live : Restore (Set deleted_at = NULL)
}When using Microsoft OneDrive or a cloud storage, if you new unit tests, you will likely get an error: "Caused by: java.nio.file.AccessDeniedException".
Build tools like Gradle create, modify, and delete thousands of temporary files (inside build/) in milliseconds.
When Gradle creates a file, OneDrive notices the new file and immediately tries to lock it to upload it to the cloud, Gradle tries to write to or rename that file a millisecond later, and Gradle is denied access because OneDrive is holding the lock.
Option 1: Move the project to a non-synced folder to avoid the syncing process fighting with the compiler.
Option 2: Pause Sync. Click the OneDrive icon in your taskbar. Select Pause Syncing > 2 hours.
In IntelliJ, run Clean (Gradle tab > Tasks > build > clean).
Run your tests again.
Following a Test-Driven Development (TDD) approach, we can write the tests before the implementation of any code.
Add these import statements.
import org.junit.jupiter.api.DisplayName;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
Add following new test methods inside the class.
@Test
@DisplayName("User clicks \"Add School\" -> GET /schools/new")
void testInitCreationForm() throws Exception {
mockMvc.perform(get("/schools/new"))
.andExpect(status().isOk())
.andExpect(view().name("schools/createOrUpdateSchoolForm"))
.andExpect(model().attributeExists("school"));
}Add these new test methods inside the class.
@Test
@DisplayName("Validation Passed -> verify that the controller tells the repository to save() the school and then redirects us.")
void testProcessCreationFormSuccess() throws Exception {
mockMvc.perform(post("/schools/new")
.param("name", "University of Iowa")
.param("domain", "uiowa.edu"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/schools"));
// Verify that the repository.save() method was actually called
verify(schools).save(any(School.class));
}
@Test
@DisplayName("Validation Failed -> send an empty domain and ensure the controller returns us to the form instead of saving.")
void testProcessCreationFormHasErrors() throws Exception {
mockMvc.perform(post("/schools/new")
.param("name", "Bad School")
.param("domain", "")) // Empty domain should trigger @NotEmpty
.andExpect(status().isOk()) // 200 OK because we are re-rendering the form, not redirecting
.andExpect(model().attributeHasErrors("school"))
.andExpect(model().attributeHasFieldErrors("school", "domain"))
.andExpect(view().name("schools/createOrUpdateSchoolForm"));
}If you run SchoolControllerTest, it should fail: 404 Not Found (because /schools/new is not mapped in the controller yet).
We need to add a link on the schoolList.html page so users can actually find this new form.
Open src/main/resources/templates/schools/schoolList.html and add this button code right below your <h2> and above your <table>:
<a th:href="@{/schools/new}" class="btn btn-primary" style="margin-bottom: 15px;">
<span class="fa fa-plus"></span>
<span th:text="#{addSchool}">Add School</span>
</a>Now you can run the tests and app.
Click "Find Schools," click "Add School," and try creating a new entry. It should save and redirect you back to the list.
Validation occurs, but no error or success messages display. We'll add that next.
In ISO 12207, Process 7.1.5 refers to translating the logic defined in your Sequence Diagram directly into Java syntax.
Open the SchoolController.java and add the following code.
Add these imports for the validation logic and the binding result (which holds validation errors).
import jakarta.validation.Valid;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@GetMapping("/schools/new")
public String initCreationForm(Map<String, Object> model) {
// Phase 1 of Sequence Diagram
// 1. Create the blank object (State: New)
School school = new School();
// 2. Add it to the model so the Thymeleaf form can bind data to it
model.put("school", school);
// 3. Return the view
return "schools/createOrUpdateSchoolForm";
}
@PostMapping("/schools/new")
public String processCreationForm(@Valid School school, BindingResult result) {
// Phase 2 of Sequence Diagram
// 1. Check Validation
if (result.hasErrors()) {
// Validation Failed: Return to the form to show errors
return "schools/createOrUpdateSchoolForm";
}
// 2. Save Data (Validation Passed)
// Note: The status defaults to ACTIVE because of your School.java definition
schoolRepository.save(school);
// 3. Redirect to the list
return "redirect:/schools";
}If you try to run the tests or the program, it will crash because we haven't created the HTML Template yet.
Create a file called "createOrUpdateSchoolForm.html" in the "src/main/resources/templates/schools/" folder.
In the Spring PetClinic architecture, forms rely on a reusable fragment called inputField to handle the labels, inputs, and error messages automatically to keep your form code very clean.
<html xmlns:th="https://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout (~{::body},'schools')}">
<body>
<h2 th:text="#{addSchool}">Add School</h2>
<form th:object="${school}" class="form-horizontal" id="add-school-form" method="post">
<input th:replace="~{fragments/inputField :: input (#{label.school.name}, 'name', 'text')}" />
<input th:replace="~{fragments/inputField :: input (#{label.school.domain}, 'domain', 'text')}" />
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-primary" type="submit" th:text="#{addSchool}">Add School</button>
</div>
</div>
</form>
</body>
</html>
th:object="${school}": This binds the entire form to the School object we created in the Controller's initCreationForm method.
th:replace="~{fragments/inputField ...}": Instead of writing 10 lines of HTML for every input (label, input box, validation error span), we call this helper file.
It automatically checks bindingResult for errors.
It highlights the box red if validation fails.
It preserves the user's input if the page reloads due to an error.
By default, you will see a generic message like "must not be empty".
If you want to customize this (e.g., "School Name is required"), you can define them in your messages.properties file.
Spring looks for keys in the format: Annotation.objectName.fieldName
The NamedEntity class uses @NotBlank instead of @NotEmpty.
Going forward, I will only add English translations. Adding Spanish/Korean translations is not required to pass unit tests.
label.school.name=Name
label.school.domain=Domain
NotBlank.school.name=School Name is required
NotEmpty.school.domain=Domain Name is requiredlabel.school.name=Nombre
label.school.domain=Dominio
NotBlank.school.name=El nombre de la escuela es obligatorio
NotEmpty.school.domain=El nombre del dominio es obligatoriolabel.school.name=이름
label.school.domain=도메인
NotBlank.school.name=학교 이름은 필수입니다
NotEmpty.school.domain=도메인 이름은 필수입니다Update the contents of fragments/inputField.html to display red error messages.
Restart the application. Changes to fragments often need a full restart to clear the cache.
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<body>
<th:block th:fragment="input (label, name, type)">
<div class="mb-3">
<label th:for="${name}" class="form-label" th:text="${label}"></label>
<div class="position-relative">
<div th:switch="${type}">
<input th:case="'text'"
th:class="${#fields.hasErrors(name)} ? 'form-control is-invalid' : 'form-control'"
type="text" th:field="*{__${name}__}" />
<input th:case="'date'"
th:class="${#fields.hasErrors(name)} ? 'form-control is-invalid' : 'form-control'"
type="date" th:field="*{__${name}__}" />
<input th:case="'password'"
th:class="${#fields.hasErrors(name)} ? 'form-control is-invalid' : 'form-control'"
type="password" th:field="*{__${name}__}" />
</div>
<span th:if="${#fields.hasErrors(name)}"
class="fa fa-remove position-absolute text-danger"
style="top: 10px; right: 10px;"
aria-hidden="true"></span>
<div th:if="${#fields.hasErrors(name)}" class="invalid-feedback" style="display:block">
<span class="help-inline" th:errors="*{__${name}__}"></span>
</div>
</div>
</div>
</th:block>
</body>
</html>Your Controller checks for errors (if (result.hasErrors())),
Your School entity must define what an "error" actually is.
You need to explicitly tell Java that the name and domain fields are Required with @NotEmpty annotations.
Browser: You submit an empty form.
Controller: Spring sees @Valid School school. It looks at the School class.
Validator: It sees @NotEmpty on the name field. It checks the data. It sees it is empty.
Result: It flags a field error on "name" or "domain".
Controller Logic: result.hasErrors() now returns true.
Thymeleaf: The inputField fragment sees the error and automatically renders the red styling and the error message.
The extra set of single quotation marks around the types text, date, and password are required by Thymeleaf to distinguish between a literal string and a variable name.
th:case="'text'": Checks if the variable ${type} equals the word "text". (This is what you want).
th:case="text": Checks if the variable ${type} equals the value of a variable named text. Since you don't have a variable named text in your Java model, this would evaluate to null and the case would never match.
Validate .edu domain. Is there an API?
If you try to create a new school with a domain name that already exists, you'll get a 'Duplicate entry' error.
There is no built-in @Unique annotation in the standard Jakarta Validation (Bean Validation) specification because standard validators only check the data in the object itself, not the database requirements.
However, you can create your own custom annotation to do this. This keeps your Controller clean and puts the logic where it belongs: in the Model.
Create the Interface UniqueDomain.java in a new package,
org.springframework.samples.petclinic.validation.
package org.springframework.samples.petclinic.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// We target the TYPE (Class) so we can access both the 'domain' and the 'id'
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueDomainValidator.class)
public @interface UniqueDomain {
String message() default "This domain is already registered";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}Create a class UniqueDomainValidator.java in the same package.
package org.springframework.samples.petclinic.validation;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.samples.petclinic.school.School;
import org.springframework.samples.petclinic.school.SchoolRepository;
import java.util.Optional;
@Component
public class UniqueDomainValidator implements ConstraintValidator<UniqueDomain, School> {
@Autowired
private SchoolRepository schoolRepository;
private static ApplicationContext staticContext;
@Override
public boolean isValid(School school, ConstraintValidatorContext context) {
if (schoolRepository == null && staticContext != null) {
schoolRepository = staticContext.getBean(SchoolRepository.class);
}
if (schoolRepository == null) {
return true;
}
if (school == null || school.getDomain() == null) {
return true; // Lets @NotEmpty handle null checks
}
String domainToCheck = school.getDomain();
Optional<School> existingSchool = schoolRepository.findByDomain(domainToCheck);
if (existingSchool.isPresent()) {
// Duplicate Found: If the ID of the found school is DIFFERENT from the school being validated, it's a conflict.
// (This allows you to "Update" School #1 without changing its domain)
if (!existingSchool.get().getId().equals(school.getId())) {
// This block moves the error from the "Class" level to the specific "domain" field
// so it appears next to the input box in your form.
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode("domain")
.addConstraintViolation();
return false;
}
}
return true;
}
}
Now you can use the annotation in the School model.
Note that because we wrote a Class-Level validator (to handle the ID check safely), the annotation goes above the public class School line, not on the field itself.
package org.springframework.samples.petclinic.school;
import org.springframework.samples.petclinic.validation.UniqueDomain;
// ... other imports
@Entity
@Table(name = "schools")
@UniqueDomain // <--- validate unique domain
// ... other annotations
public class School extends NamedEntity {When you submit the form, Spring runs @Valid. It sees @UniqueDomain on the class and runs UniqueDomainValidator.isValid(school). It then calls findByDomain("kirkwood.edu").
If New School: ID is null. Found existing school (ID 1).
null != 1 is True. Invalid.
If Editing School 1: ID is 1. Found existing school (ID 1).
1 != 1 is False. Valid (It found itself).
If Editing School 1 (Change to School 2's domain): ID is 1. Found existing school (ID 2).
1 != 2 is True. Invalid.
UI Feedback: The context.addPropertyNode("domain") line ensures the error message appears right under the Domain input field in your HTML, exactly like @NotEmpty errors do.
Owner.java:
Relationships: Has a One-to-Many relationship with Pet. It manages this list with methods like addPet() and getPet().
Validation: Includes constraints like @Pattern(regexp = "\\d{10}") to ensure telephone numbers are exactly 10 digits.
Pet.java:
Relationships:
Visit (a pet can have multiple doctor visits).Many-to-One with PetType (a pet belongs to one species).
Logic: It automatically sorts visits by date using @OrderBy("date ASC").
OwnerRepository.java: The primary way to access owner data.
Key Feature: It extends JpaRepository and adds a custom search method: findByLastNameStartingWith, which supports the "Find Owner" search bar feature.
PetTypeRepository.java: Provides a list of all valid animal types (findPetTypes), ordered by name, which is used to populate dropdown menus in the UI.
OwnerController.java: Manages the high-level Owner operations.
Routes: Handles listing owners (/owners), searching (/owners/find), and creating/editing owner profiles (/owners/new, /owners/{ownerId}/edit).
Note the use of @GetMapping for GET requests and @PostMapping for POST requests.
One bug I discovered is
http://localhost:8080/owners/1 (displays data for owner #1)
http://localhost:8080/owners/1/ (with slash at the end, displays an error)
I could make an Issue and Pull Request if I wanted to contribute
PetController.java: Manages the "sub-resource" of adding or editing a pet for a specific owner.
Routes: It operates under the /owners/{ownerId} path. For example, creating a new pet is handled at /owners/{ownerId}/pets/new.
VisitController.java: Manages the creation of new visits.
Routes: This is deeply nested: /owners/{ownerId}/pets/{petId}/visits/new. It ensures that before a visit is created, the system validates that the Pet and Owner actually exist.
PetValidator.java: A custom validator that ensures a Pet has a name, a birthdate, and a type. It is manually applied in the PetController.
PetTypeFormatter.java: Helps Spring MVC convert the string "Cat" from a web form into the actual PetType Java object, and vice versa.
the src/test folder contains the automated tests that ensure the application works correctly. They are organized to test different "layers" of the application, from simple logic to full database integrations.
These files test start up the full Spring Boot application context to verify that all components work together.
PetClinicIntegrationTests.java: The main integration test suite that checks standard application flows.
MySqlIntegrationTests.java and PostgresIntegrationTests.java: These are specialized tests that verify the application works correctly when connected to real MySQL or PostgreSQL databases (often using Testcontainers) instead of the default in-memory H2 database.
Located in subpackages like owner and vet, these tests use @WebMvcTest. They focus only on the URL routing and HTML generation. They "mock" (fake) the service layer so they can test the UI without needing a real database.
OwnerControllerTests.java: Verifies that URLs like /owners/new return the correct view and that form submissions work.
These test individual classes in isolation.
PetValidatorTests.java: Checks specifically that the PetValidator correctly identifies invalid inputs (like a missing name).
VetTests.java: Tests the internal logic of the Vet entity, such as serialization.
Serialization is the process of converting an object's state into a sequence of bytes, which can then be saved to a file, stored in a database, or transmitted over a network.
The reverse process, converting the byte stream back into a live object, is called deserialization.
PetTypeFormatterTests.java: Ensures the string-to-object conversion for Pet Types functions as expected.
CrashControllerTests.java: specifically tests that the "Error" page triggers correctly.
I18nPropertiesSyncTest.java: A quality assurance test that ensures your translation files (like Spanish and English) stay in sync and don't have missing keys.
EntityUtils.java: Not a test itself, but a helper class used by other tests to look up objects in collections.
This is the Maven configuration file. It tells Java exactly which third-party libraries (dependencies) your project needs to run.
Parent POM: You will see a <parent> tag for spring-boot-starter-parent. This is the "magic" that manages versions for you. You don't need to specify the version for every single library because the parent knows which versions work together.
Starters: Notice dependencies like spring-boot-starter-web, spring-boot-starter-data-jpa, and spring-boot-starter-thymeleaf. These are pre-packaged bundles. For example, starter-web automatically gives you Tomcat (server), Spring MVC (framework), and Jackson (JSON parser) in one go.
Java Version: It explicitly sets the Java version to 17 (<java.version>17</java.version>).
Located in src/main/resources/, this is where you configure the application's behavior without changing Java code.
Database Settings: The line database=h2 tells the app to use the H2 in-memory database by default. This is why you don't need to install MySQL to run the demo; the database is created in RAM when you start the app and destroyed when you stop it.
Database Initialization: It points to schema.sql (to create tables) and data.sql (to load dummy data).
Logging: You can control how much info appears in the console. For example, logging.level.org.springframework=INFO.
This is the file with the public static void main method that launches the app.
@SpringBootApplication: This single annotation actually triggers three powerful features:
Configuration: It marks the class as a source of bean definitions.
EnableAutoConfiguration: It tells Spring Boot to configure your app based on the libraries in your pom.xml
ComponentScan: It tells Spring to scan the current package (and all sub-packages) for your Controllers, Services, and Repositories.
While application.properties is good for simple key-value pairs, complex configuration is done in Java classes marked with @Configuration.
@EnableCaching: This annotation turns on Spring's caching ability.
@Bean: The method petclinicCacheConfigurationCustomizer() is marked with @Bean. This tells Spring: "Execute this method and manage the object it returns.". It is used here to set up a specific cache for "vets" so the database doesn't have to be queried every time the user refreshes the veterinarian list.
The term "Bean" is used in two distinct ways in this project. It can be confusing because they share the same name, but they serve different purposes.
In the comments of files like Vet.java and Owner.java, you will see them described as a "Simple JavaBean domain object".
A standard JavaBean is simply a Java class that follows a specific set of naming conventions so other tools can easily use it.
Private Properties: Data fields are hidden (e.g., private String address; in Owner.java).
Public Getters/Setters: The data is accessed via public methods named get... and set... (e.g., getAddress() and setAddress()).
In your project: Classes like Vet, Owner, Pet, and Visit are standard JavaBeans. They are just containers for your data.
No-Argument Constructor: It usually has a default constructor so frameworks can create an empty instance of it easily.
JavaBean is a pattern for writing data classes (like Vet or Owner).
In CacheConfiguration.java and WebConfiguration.java, you saw the @Bean annotation.
A Spring Bean is an object that is created, configured, and managed by the Spring Framework itself (the "Spring Container").
Managed Lifecycle: You do not create these objects with new (e.g., new VetController()). Instead, Spring creates them for you at startup.
Dependency Injection: Spring "injects" these beans into other beans that need them. For example, VetRepository is a Spring Bean that is automatically injected into the VetController.
Spring Bean is a specific object living in the Spring system (like VetController or the CacheManager).
By Marc Hauschildt
Web Technologies and Computer Software Development Instructor at Kirkwood Community College in Cedar Rapids, IA.