This commit is contained in:
Dantrue 2025-05-31 15:33:05 +00:00 committed by GitHub
commit 9156eea85c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 211 additions and 6 deletions

View file

@ -18,7 +18,7 @@
<properties>
<!-- Generic properties -->
<java.version>17</java.version>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Important for reproducible builds. Update using e.g. ./mvnw versions:set
@ -71,6 +71,10 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<!-- Workaround for AOT issue (https://github.com/spring-projects/spring-framework/pull/33949) -->
<groupId>io.projectreactor</groupId>

View file

@ -0,0 +1,44 @@
package org.springframework.samples.petclinic;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.function.BiFunction;
import java.util.function.Supplier;
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@FunctionalInterface
private interface MethodLogger {
void log(String className, String methodName, long executionTime);
}
private final MethodLogger defaultLogger = (className, methodName, executionTime) ->
logger.info("Method {}.{} completed in {} ms", className, methodName, executionTime);
@Around("execution(* org.springframework.samples.petclinic..*.*(..))")
public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
Supplier<String> methodInfo = () -> String.format("%s.%s", className, methodName);
logger.info("Starting method: {}", methodInfo.get());
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
return result;
} finally {
long executionTime = System.currentTimeMillis() - startTime;
defaultLogger.log(className, methodName, executionTime);
}
}
}

View file

@ -0,0 +1,19 @@
package org.springframework.samples.petclinic.cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setCacheNames(java.util.Arrays.asList("dataCache"));
return cacheManager;
}
}

View file

@ -0,0 +1,36 @@
package org.springframework.samples.petclinic.cache;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class CacheableService {
private final Map<String, String> dataStore = new HashMap<>();
public CacheableService() {
// Инициализация тестовых данных
dataStore.put("key1", "value1");
dataStore.put("key2", "value2");
dataStore.put("key3", "value3");
}
@Cacheable(value = "dataCache", key = "#key")
public String getData(String key) {
// Имитация задержки при получении данных
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return dataStore.get(key);
}
public void updateData(String key, String value) {
dataStore.put(key, value);
}
}

View file

@ -15,15 +15,14 @@
*/
package org.springframework.samples.petclinic.owner;
import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.samples.petclinic.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.samples.petclinic.model.BaseEntity;
import java.time.LocalDate;
/**
* Simple JavaBean domain object representing a visit.

View file

@ -0,0 +1,48 @@
package org.springframework.samples.petclinic;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.mockito.Mockito.*;
@ExtendWith({SpringExtension.class, MockitoExtension.class})
@SpringJUnitConfig
public class LoggingAspectTest {
@Mock
private Logger logger;
@InjectMocks
private LoggingAspect loggingAspect;
@Test
public void testLogMethodExecution() throws Throwable {
// Создаем тестовый метод для проверки
TestService testService = new TestService();
// Вызываем метод, который должен быть перехвачен аспектом
testService.testMethod();
// Проверяем, что логирование было вызвано
verify(logger, atLeastOnce()).info(contains("Starting method:"));
verify(logger, atLeastOnce()).info(contains("Method TestService.testMethod completed in"));
}
// Вспомогательный класс для тестирования
private static class TestService {
public void testMethod() {
// Простой метод для тестирования
try {
Thread.sleep(100); // Имитируем некоторую работу
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}

View file

@ -0,0 +1,55 @@
package org.springframework.samples.petclinic.cache;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cache.CacheManager;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
public class CacheableServiceTest {
@Autowired
private CacheableService cacheableService;
@Autowired
private CacheManager cacheManager;
@Test
public void testCacheHit() {
// Первый вызов - кэш промах
long startTime1 = System.currentTimeMillis();
String result1 = cacheableService.getData("key1");
long duration1 = System.currentTimeMillis() - startTime1;
// Второй вызов - должен быть кэш хит
long startTime2 = System.currentTimeMillis();
String result2 = cacheableService.getData("key1");
long duration2 = System.currentTimeMillis() - startTime2;
// Проверяем результаты
assertEquals("value1", result1);
assertEquals("value1", result2);
// Второй вызов должен быть быстрее из-за кэширования
assertTrue(duration2 < duration1);
}
@Test
public void testCacheMiss() {
// Проверяем несуществующий ключ
String result = cacheableService.getData("nonExistentKey");
assertNull(result);
}
@Test
public void testCacheUpdate() {
// Обновляем значение
cacheableService.updateData("key1", "newValue");
// Получаем обновленное значение
String result = cacheableService.getData("key1");
assertEquals("newValue", result);
}
}