Advanced Java, J2EE : Day 5
Aspect Oriented Programming
Terminology of AOP
→ Aspect : A concern which need not to be main logic, but can be used along with main logic. These codes lead to code tangling and code scattering. Ex: Logging, Profile, Transaction, Security
→ PointCut : places where aspect can be weaved. Can be weaved to any method or exception
→ JoinPoint : Selected PointCut
→ Advice : How we apply aspect to JoinPoint
→ Before
→ After
→ Around
→ AfterReturning : return value unlike @After
→ AfterThrowing : Throws exceptionn
@After("execution(* com.cisco.prj.service.OrderService.*(..))")
-
Code
package com.cisco.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; @Aspect @Configuration public class LogAspect { Logger logger = LoggerFactory.getLogger(LogAspect.class); @Before("execution( * com.cisco.prj.service.OrderService.*(..))") public void performLog(JoinPoint jp) { logger.info("Called :" + jp); Object[] args = jp.getArgs(); for(Object obj : args) { logger.info("arguments : " + obj); } } @After("execution( * com.cisco.prj.service.OrderService.*(..))") public void performAfterLog(JoinPoint jp) { logger.info("************************"); } @AfterThrowing(value="execution( * com.cisco.prj.service.OrderService.*(..))", throwing = "ex") public void exceptionLogger(JoinPoint jp, Throwable ex) { logger.info("Exception occured :" + ex.getMessage() + "for " + jp.getStaticPart()); } @AfterReturning(value="execution( * com.cisco.prj.service.OrderService.*(..))", returning = "obj") public void captureReturn(JoinPoint jp, Object obj) { logger.info("Returned " + obj + "from : => " + jp.getStaticPart()); } @Around("execution( * com.cisco.prj.service.OrderService.*(..))") public Object timeTaken(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); Object ret = pjp.proceed(); long end = System.currentTimeMillis(); logger.info(pjp.getSignature().getName() + " took " + (end - start) + " ms"); return ret; } }
AOP Used for Event Listener
-
Code : Using
ApplicationEventPublisher
we publish event, which then can be listened by CacheListener, RedisListener, RabbitMqListener. Similarly to listen, we haveApplicationListener
//Create an Event package com.cisco.aop; import org.springframework.context.ApplicationEvent; public class EntityCreatedEvent extends ApplicationEvent { public EntityCreatedEvent(Object source) { super(source); } } //EventPublishingAspect package com.cisco.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; @Component @Aspect public class EventPublishingAspect { @Autowired private ApplicationEventPublisher eventPublisher; @AfterReturning(value ="execution( * org.springframework.data.repository.Repository+.save*(..))", returning = "entity") public void publishEntityCreationEvent(JoinPoint jp, Object entity) { eventPublisher.publishEvent(new EntityCreatedEvent(entity)); } } //Event Listener
Validation
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<scope>test</scope>
</dependency>
@NotBlank(message="Name can't be blank")
@Min(value = 10, message="Price ${validatedValue} should be more than {value}")
Global Exception Handler
-
Code
package com.cisco.prj.api; import java.time.LocalDate; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @ControllerAdvice public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(NotFoundException.class) public ResponseEntity<Object> handleNotFoundException(NotFoundException ex, WebRequest req){ Map<String, Object> body = new LinkedHashMap<>(); body.put("message", ex.getLocalizedMessage()); return new ResponseEntity<>(body,HttpStatus.BAD_REQUEST); } @Override public ResponseEntity<Object> handleMethodArgumentNotValid( MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { Map<String, Object> body = new LinkedHashMap<>(); body.put("timestamp", LocalDate.now()); body.put("status", status.value()); List<String> errors = ex.getBindingResult() .getFieldErrors() .stream() .map(e -> e.getDefaultMessage()) .collect(Collectors.toList()); body.put("errors", errors); return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); } }
Testing
Unit Testing Framework
→ Unit Testing Framework : JUnit
, TestNg
→ Mockito
for Mocking the the injected DAOs
→ Hamcrest
: Rich Assertion Library
→ JsonPath
@WebMvcTest(ProductController.class)
: Load only specified controller in testbed
MockMvc
can be used to mock API Calls
MockBean
can be used to mock DI
when().thenReturn()
//Mocking function call
-
Code
package com.cisco.prj.api; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import com.cisco.prj.entity.Product; import com.cisco.prj.service.OrderService; import com.fasterxml.jackson.databind.ObjectMapper; @WebMvcTest(ProductController.class) public class ProductControllerTest { @MockBean private OrderService service; @Autowired private MockMvc mockMvc; @Test public void getProductsTest() throws Exception { List<Product> products = Arrays.asList(new Product(1, "a", 500.00, 100), new Product(2, "b", 1500.00, 100)); // mocking when(service.getProducts()).thenReturn(products); // @formatter:off mockMvc.perform(get("/api/products")) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasSize(2))) .andExpect(jsonPath("$[0].id", is(1))) .andExpect(jsonPath("$[0].name", is("a"))) .andExpect(jsonPath("$[1].id", is(2))) .andExpect(jsonPath("$[1].name", is("b"))); // @formatter:on verify(service, times(1)).getProducts(); } @Test public void addProductTest() throws Exception { Product p = new Product(0, "b", 1500.00, 100); Product p2 = new Product(1, "b", 1500.00, 100); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(p); // get JSON for Product p // mocking if Product type is passed to service he should return a PK 10 when(service.addProduct(Mockito.any(Product.class))).thenReturn(p2); mockMvc.perform(post("/api/products") .content(json) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()); verify(service, times(1)).addProduct(Mockito.any(Product.class)); } @Test public void addProductExceptionTest() throws Exception { Product p = new Product(0, "", -1500.0, 100); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(p); // get JSON for Product p mockMvc.perform(post("/api/products") .content(json).contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.errors", hasSize(2))) .andExpect(jsonPath("$.errors", hasItem("Give Name :-("))) .andExpect(jsonPath("$.errors", hasItem("Price -1500.0 should be more than 10"))); verifyNoInteractions(service); } }
Spring Security
→ Authentication
→ Authorization
→ Exception Handling
Context Listener
loads Spring Security configuration
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<scope>test</scope>
</dependency>
DelegatingFilterProxy
Runs in Servlet Container, Similar to DispatcherServlet
Context
ApplicationContext
→ Place where Beans are Managed : Spring Container
ServletContext
→ Servlet/JSP/Filter/Listener → Managed by Servlet Engine [TomCat, Jetty]
SecurityContext
→ Authentication related configs are stored.