Skip to content

Session 4

Dependency injection and controllers

Updated View as Markdown

Dependency injection is how Spring wires objects together. Controllers map URLs to methods and hand data to views. The session ends with a Spring Form for student admission, the form that Sessions 5 and 6 validate and persist.

Everything here goes into the student-admission project from Session 3. Q17 and Q18 are console programs in the ioc package; Q19 and Q20 add controllers and JSP views to the web application.

Objectives

Do not copy. Read for understanding and the viva
  • Complete questions 17 to 20 of the manual: dependency injection and controllers
  • Prepare the deliverable before the lab and finish it during the session
  • Be ready to explain every step in the viva

Questions Covered

Do not copy. Read for understanding and the viva
QuestionRequirementStatus
Q17Write a class and implement it using dependency injectionComplete
Q18Write the service interface with the getRandom() method, define 3 courses in an array,…Complete
Q19Write a programme using Spring Framework to create a controller and display response…Complete
Q20Create a Form to capture Student Admission information (make usual assumptions about…Complete

Preparation

Do not copy. Read for understanding and the viva
  • Constructor injection with @Autowired is the form to show; be able to contrast it with setter injection.
  • Decide the Student Admission attributes now (name, date of birth, gender, programme dropdown, address textarea, hostel required true/false, courses checkboxes) because Sessions 5, 6 and 7 reuse them.
  • Spring form tags need the spring-webmvc dependency and the form taglib declaration on the JSP.

Question 17

Problem Statement

Write in lab record

Write a class and implement it using dependency injection.

Solution

Write in lab record

Steps

  1. Create DiApp.java in com.ignou.lab.admission.ioc. It holds one interface (FeeCalculator), one implementation (FlatFeeCalculator) and the class that depends on it (AdmissionDesk), plus main.
  2. Run it: Run As → Java Application, or mvn -q compile exec:java -Dexec.mainClass=com.ignou.lab.admission.ioc.DiApp.
  3. To see the injection fail, comment out @Component on FlatFeeCalculator and run again: UnsatisfiedDependencyException ... No qualifying bean of type FeeCalculator.

Program

ioc/DiApp.javajava
package com.ignou.lab.admission.ioc;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

// ponytail: three small classes in one file so the whole DI example is one listing

/** What AdmissionDesk depends on. It never knows which implementation it gets. */
interface FeeCalculator {
    int feeFor(String programme);
}

@Component
class FlatFeeCalculator implements FeeCalculator {
    @Override
    public int feeFor(String programme) {
        return programme.equals("MCA") ? 12000 : 8000;
    }
}

/** Constructor injection: Spring passes the FeeCalculator in; the field is final. */
@Component
class AdmissionDesk {
    private final FeeCalculator fees;

    AdmissionDesk(FeeCalculator fees) {
        this.fees = fees;
    }

    String quote(String programme) {
        return programme + " fee per semester: Rs " + fees.feeFor(programme);
    }
}

public class DiApp {
    public static void main(String[] args) {
        try (var ctx = new AnnotationConfigApplicationContext(FlatFeeCalculator.class, AdmissionDesk.class)) {
            AdmissionDesk desk = ctx.getBean(AdmissionDesk.class);
            System.out.println(desk.quote("MCA"));
            System.out.println(desk.quote("BCA"));
        }
    }
}

Output

Expected console output, checked by reading, not executed:

MCA fee per semester: Rs 12000
BCA fee per semester: Rs 8000

Explanation

  • AdmissionDesk declares what it needs (a FeeCalculator) as a constructor parameter. It never creates one. That is dependency injection: the dependency is pushed in from outside.
  • Spring sees a single constructor and calls it with the only FeeCalculator bean it knows, FlatFeeCalculator. Since Spring 4.3 a single constructor needs no @Autowired; add it when there is more than one.
  • Constructor injection makes the field final, so the object is complete the moment it exists. Setter injection (@Autowired on setFees) allows an incomplete object and is used for optional dependencies.
  • A unit test can call new AdmissionDesk(p -> 100) with a lambda; no Spring needed. That is the practical payoff of injecting an interface.

Question 18

Problem Statement

Write in lab record

Write the service interface with the getRandom() method, define 3 courses in an array, and inject using dependency injection into the teacher Interface (exercise no 4 in session 3). Test the application and verify the retrieving of random courses.

Solution

Write in lab record

Steps

  1. In the ioc package add CourseService.java and RandomCourseService.java.
  2. Replace JavaTeacher.java with the version below: a constructor takes CourseService and getFavouriteCourse() delegates to it. Teacher.java from Session 3 is unchanged.
  3. Replace TeacherApp.java so the context knows both classes and prints five calls.
  4. Run TeacherApp three times; the sequence of courses differs each run.

Program

Lab record: every tab is one file of the answer. Write all of them.

ioc/CourseService.javajava
package com.ignou.lab.admission.ioc;

public interface CourseService {
    String getRandom();
}
ioc/RandomCourseService.javajava
package com.ignou.lab.admission.ioc;

import java.util.concurrent.ThreadLocalRandom;
import org.springframework.stereotype.Service;

@Service
public class RandomCourseService implements CourseService {

    private static final String[] COURSES = {
        "MCS-218 Data Communication and Computer Networks",
        "MCS-219 Object Oriented Analysis and Design",
        "MCS-220 Web Technologies"
    };

    @Override
    public String getRandom() {
        return COURSES[ThreadLocalRandom.current().nextInt(COURSES.length)];
    }
}
ioc/JavaTeacher.javajava
package com.ignou.lab.admission.ioc;

import org.springframework.stereotype.Component;

/** Session 4 version: the favourite course now comes from an injected CourseService. */
@Component
public class JavaTeacher implements Teacher {

    private final CourseService courseService;

    public JavaTeacher(CourseService courseService) { // constructor injection
        this.courseService = courseService;
    }

    @Override
    public String getName() {
        return "Dr. Rao";
    }

    @Override
    public String getFavouriteCourse() {
        return courseService.getRandom();
    }
}
ioc/TeacherApp.javajava
package com.ignou.lab.admission.ioc;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TeacherApp {

    public static void main(String[] args) {
        try (var ctx = new AnnotationConfigApplicationContext(JavaTeacher.class, RandomCourseService.class)) {
            Teacher teacher = ctx.getBean(Teacher.class);
            for (int i = 1; i <= 5; i++) {
                System.out.println(i + ". " + teacher.getName() + " prefers " + teacher.getFavouriteCourse());
            }
        }
    }
}

Output

Expected console output (one run; yours will differ because the choice is random), checked by reading, not executed:

1. Dr. Rao prefers MCS-219 Object Oriented Analysis and Design
2. Dr. Rao prefers MCS-220 Web Technologies
3. Dr. Rao prefers MCS-220 Web Technologies
4. Dr. Rao prefers MCS-218 Data Communication and Computer Networks
5. Dr. Rao prefers MCS-219 Object Oriented Analysis and Design

Verification: over five calls only the three array entries ever appear, and at least two different ones show up in almost every run.

Explanation

  • RandomCourseService is a @Service (a @Component with a clearer name). The three courses sit in a static final String[]; ThreadLocalRandom.current().nextInt(3) picks an index.
  • JavaTeacher now depends on the CourseService interface, injected through its constructor. Session 3’s version hard-coded the answer; this one asks a collaborator.
  • The container wires the graph: it must build RandomCourseService first, then pass it to JavaTeacher’s constructor. Order is worked out from the constructor signatures, not from the order the classes are listed.
  • Because JavaTeacher is in the web application’s scanned package, the same wiring happens inside Tomcat, which Q19 uses.

Question 19

Problem Statement

Write in lab record

Write a programme using Spring Framework to create a controller and display response in view using @GetMapping() annotation.

Solution

Write in lab record

Steps

  1. Add TeacherController.java to com.ignou.lab.admission.web and teacher.jsp to WEB-INF/views.
  2. Rebuild, redeploy, open http://localhost:8080/student-admission/teacher.
  3. Press F5 several times; the course changes.

Program

Lab record: every tab is one file of the answer. Write all of them.

web/TeacherController.javajava
package com.ignou.lab.admission.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import com.ignou.lab.admission.ioc.Teacher;

@Controller
public class TeacherController {

    private final Teacher teacher; // JavaTeacher, found by the component scan

    public TeacherController(Teacher teacher) {
        this.teacher = teacher;
    }

    @GetMapping("/teacher")
    public String show(Model model) {
        model.addAttribute("teacherName", teacher.getName());
        model.addAttribute("course", teacher.getFavouriteCourse());
        return "teacher";
    }
}
WEB-INF/views/teacher.jsphtml
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Teacher</title>
</head>
<body>
  <h1>Teacher of the day</h1>
  <p><b>${teacherName}</b> will teach <b>${course}</b> this session.</p>
  <p><a href="">Refresh for another course</a></p>
</body>
</html>

Output

Expected browser result, checked by reading, not executed:

Teacher of the day
Dr. Rao will teach MCS-220 Web Technologies this session.
Refresh for another course

Explanation

  • @Controller makes the class a bean whose methods can be mapped to URLs. @GetMapping("/teacher") binds HTTP GET on /teacher to show().
  • The Model parameter is a map that the view can read. teacher.jsp reads teacherName and course with ${...} expressions.
  • The returned string "teacher" is a view name; InternalResourceViewResolver (Session 3) turns it into /WEB-INF/views/teacher.jsp and forwards the request.
  • The controller receives the Teacher bean through its constructor, exactly like TeacherApp did; a web request is just another caller of the same object graph.

Question 20

Problem Statement

Write in lab record

Create a Form to capture Student Admission information (make usual assumptions about the attributes) using Spring Form tags (must use Text, textarea, dropdown, date picker, true/false and checkbox) and write a controller using @PostMapping() to display the form information.

Solution

Write in lab record

Assumed attributes: name, email and mobile (text), date of birth (date picker), address (textarea), programme (dropdown), hostel required (true/false radio) and courses (checkboxes).

Steps

  1. Add AdmissionForm.java and AdmissionController.java to com.ignou.lab.admission.web.
  2. Add admission-form.jsp and admission-result.jsp to WEB-INF/views. The form page declares the Spring form taglib with prefix form.
  3. Rebuild, redeploy, open http://localhost:8080/student-admission/admission, fill every field, submit.
  4. View page source of the form before submitting: every form: tag became a plain HTML element with id and name equal to the path.

Program

Lab record: every tab is one file of the answer. Write all of them.

web/AdmissionForm.javajava
package com.ignou.lab.admission.web;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import org.springframework.format.annotation.DateTimeFormat;

/** Form-backing object for the Student Admission form. */
public class AdmissionForm {

    private String name;                       // form:input
    private String email;                      // form:input type="email"
    private String mobile;                     // form:input
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate dob;                     // form:input type="date" (browser date picker)
    private String address;                    // form:textarea
    private String programme;                  // form:select
    private Boolean hostelRequired;            // form:radiobutton true / false
    private List<String> courses = new ArrayList<>(); // form:checkboxes

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public String getMobile() { return mobile; }
    public void setMobile(String mobile) { this.mobile = mobile; }
    public LocalDate getDob() { return dob; }
    public void setDob(LocalDate dob) { this.dob = dob; }
    public String getAddress() { return address; }
    public void setAddress(String address) { this.address = address; }
    public String getProgramme() { return programme; }
    public void setProgramme(String programme) { this.programme = programme; }
    public Boolean getHostelRequired() { return hostelRequired; }
    public void setHostelRequired(Boolean hostelRequired) { this.hostelRequired = hostelRequired; }
    public List<String> getCourses() { return courses; }
    public void setCourses(List<String> courses) { this.courses = courses; }
}
web/AdmissionController.javajava
package com.ignou.lab.admission.web;

import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class AdmissionController {

    // ponytail: fixed lists; Session 6 reads programmes and courses from the database
    private static final List<String> PROGRAMMES = List.of("MCA", "BCA", "PGDCA");
    private static final List<String> COURSES = List.of(
            "MCS-218 Data Communication and Computer Networks",
            "MCS-219 Object Oriented Analysis and Design",
            "MCS-220 Web Technologies",
            "MCS-221 Data Warehousing and Data Mining");

    @ModelAttribute("programmes")
    public List<String> programmes() {
        return PROGRAMMES;
    }

    @ModelAttribute("courseList")
    public List<String> courseList() {
        return COURSES;
    }

    @GetMapping("/admission")
    public String showForm(Model model) {
        model.addAttribute("admission", new AdmissionForm()); // empty object the form tags bind to
        return "admission-form";
    }

    @PostMapping("/admission")
    public String submit(@ModelAttribute("admission") AdmissionForm form) {
        return "admission-result"; // "admission" is already in the model
    }
}
WEB-INF/views/admission-form.jsphtml
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Student Admission Form</title>
</head>
<body>
  <h1>Student Admission Form</h1>
  <form:form modelAttribute="admission" method="post">
    <p><form:label path="name">Full name</form:label><br>
       <form:input path="name" size="40"/></p>

    <p><form:label path="email">Email</form:label><br>
       <form:input path="email" type="email" size="40"/></p>

    <p><form:label path="mobile">Mobile</form:label><br>
       <form:input path="mobile" size="12"/></p>

    <p><form:label path="dob">Date of birth</form:label><br>
       <form:input path="dob" type="date"/></p>

    <p><form:label path="address">Address</form:label><br>
       <form:textarea path="address" rows="3" cols="40"/></p>

    <p><form:label path="programme">Programme</form:label><br>
       <form:select path="programme">
         <form:option value="" label="-- choose --"/>
         <form:options items="${programmes}"/>
       </form:select></p>

    <p>Hostel required?<br>
       <form:radiobutton path="hostelRequired" value="true" label="Yes"/>
       <form:radiobutton path="hostelRequired" value="false" label="No"/></p>

    <p>Courses this semester<br>
       <form:checkboxes path="courses" items="${courseList}" delimiter="<br>"/></p>

    <p><button type="submit">Submit application</button></p>
  </form:form>
</body>
</html>
WEB-INF/views/admission-result.jsphtml
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Application received</title>
</head>
<body>
  <h1>Application received</h1>
  <table border="1" cellpadding="4">
    <tr><th>Name</th><td><c:out value="${admission.name}"/></td></tr>
    <tr><th>Email</th><td><c:out value="${admission.email}"/></td></tr>
    <tr><th>Mobile</th><td><c:out value="${admission.mobile}"/></td></tr>
    <tr><th>Date of birth</th><td>${admission.dob}</td></tr>
    <tr><th>Address</th><td><c:out value="${admission.address}"/></td></tr>
    <tr><th>Programme</th><td><c:out value="${admission.programme}"/></td></tr>
    <tr><th>Hostel required</th><td>${admission.hostelRequired ? 'Yes' : 'No'}</td></tr>
    <tr><th>Courses</th><td>
      <c:forEach items="${admission.courses}" var="course">
        <c:out value="${course}"/><br>
      </c:forEach>
    </td></tr>
  </table>
  <p><a href="${pageContext.request.contextPath}/admission">Another application</a></p>
</body>
</html>

Output

Expected result after submitting the form, checked by reading, not executed:

Application received
Name            Asha Verma
Email           asha@example.com
Mobile          9876543210
Date of birth   2003-04-12
Address         Sector 4, Rohini, Delhi
Programme       MCA
Hostel required Yes
Courses         MCS-218 Data Communication and Computer Networks
                MCS-220 Web Technologies
Another application

The generated HTML for the radio buttons is input type="radio" name="hostelRequired" value="true" and value="false"; the date field is input type="date" name="dob".

Explanation

  • AdmissionForm is the form-backing object. showForm puts an empty one in the model under the name admission; form:form modelAttribute="admission" binds every form: tag to a property of it by path.
  • Tag to element: form:input (text; type="email" and type="date" pass through as HTML5 input types, so the browser shows its own date picker), form:textarea, form:select with form:options, two form:radiobutton with values true and false bound to a Boolean, form:checkboxes over a list bound to List<String>.
  • @DateTimeFormat(iso = DATE) tells Spring’s conversion service to parse 2003-04-12 into a LocalDate; without it the post fails with a type mismatch.
  • @PostMapping("/admission") receives the same URL by POST. @ModelAttribute("admission") makes Spring create an AdmissionForm, copy every request parameter into the matching property (data binding) and add it to the model, so admission-result.jsp reads ${admission.name}.
  • @ModelAttribute on the programmes() and courseList() methods runs before every handler in the controller, so both GET and the POST re-render see the option lists.

Viva Questions

Do not copy. Read for understanding and the viva
  • Q: What is dependency injection? A: An object receives the objects it needs from outside (constructor or setter) instead of creating them; the container does the passing.
  • Q: Constructor or setter injection, which and why? A: Constructor: mandatory dependencies, immutable fields, object valid at creation. Setter: optional dependencies or circular references.
  • Q: What is the difference between @Component, @Service, @Repository and @Controller? A: All are components; the names document the layer. @Repository also translates persistence exceptions, @Controller enables request mapping.
  • Q: What happens if two beans implement Teacher? A: NoUniqueBeanDefinitionException unless one is @Primary or the injection point uses @Qualifier.
  • Q: What does @ModelAttribute do on a method parameter? A: Creates or looks up the object, binds request parameters to its properties and adds it to the model.
  • Q: Why does form:form need modelAttribute? A: The form tags read initial values from that object and derive name attributes from path, so binding on POST is by the same names.
  • Q: How does a Boolean get a true/false value from radio buttons? A: Both radios share the name hostelRequired; the chosen one sends true or false, which Spring converts to Boolean.

Common Mistakes

Do not copy. Read for understanding and the viva
  • Injecting a concrete class (JavaTeacher) instead of the interface, which defeats the point of Q18.
  • Forgetting the form taglib line; the JSP then prints form:input literally.
  • Naming the model attribute student in one method and admission in the other; the POST binding then creates a new empty object.
  • Leaving out @DateTimeFormat on the LocalDate field and getting Failed to convert property value on submit.
  • Checkboxes bound to a String instead of a List<String>; only the last ticked box survives.

Session Summary

Write in lab record
  • Question 17: DiApp with AdmissionDesk receiving a FeeCalculator through constructor injection
  • Question 18: CourseService.getRandom() over a three-course array, injected into JavaTeacher, verified with five console calls
  • Question 19: TeacherController with @GetMapping("/teacher") rendering teacher.jsp
  • Question 20: Student Admission form with Spring form tags (text, textarea, select, date, true/false radios, checkboxes) and @PostMapping showing the posted data
Navigation

Type to search…

↑↓ navigate↵ selectEsc close