GH-1330: Explain AOP annotations with copilot

This commit is contained in:
vudayani
2024-08-29 20:00:37 +05:30
committed by Martin Lippert
parent fb072bfa8c
commit 64e7084514
15 changed files with 387 additions and 62 deletions

View File

@@ -10,6 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java;
import java.util.Map;
/**
* Constants containing various fully-qualified annotation names.
*
@@ -85,6 +87,16 @@ public class Annotations {
public static final String SCHEDULED = "org.springframework.scheduling.annotation.Scheduled";
public static final Map<String, String> AOP_ANNOTATIONS = Map.of(
"org.aspectj.lang.annotation.Pointcut", "Pointcut",
"org.aspectj.lang.annotation.Before", "Before",
"org.aspectj.lang.annotation.Around", "Around",
"org.aspectj.lang.annotation.After", "After",
"org.aspectj.lang.annotation.AfterReturning", "AfterReturning",
"org.aspectj.lang.annotation.AfterThrowing", "AfterThrowing",
"org.aspectj.lang.annotation.DeclareParents", "DeclareParents"
);
}

View File

@@ -40,7 +40,7 @@ import org.springframework.ide.vscode.boot.java.handlers.BootJavaWorkspaceSymbol
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
import org.springframework.ide.vscode.boot.java.handlers.HighlightProvider;
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
import org.springframework.ide.vscode.boot.java.handlers.QueryCodeLensProvider;
import org.springframework.ide.vscode.boot.java.handlers.CopilotCodeLensProvider;
import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.livehover.ActiveProfilesProvider;
@@ -319,7 +319,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
protected BootJavaCodeLensEngine createCodeLensEngine(SpringSymbolIndex index, JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) {
Collection<CodeLensProvider> codeLensProvider = new ArrayList<>();
codeLensProvider.add(new WebfluxHandlerCodeLensProvider(index));
codeLensProvider.add(new QueryCodeLensProvider(projectFinder, server, spelSemanticTokens));
codeLensProvider.add(new CopilotCodeLensProvider(projectFinder, server, spelSemanticTokens));
return new BootJavaCodeLensEngine(this, codeLensProvider);
}

View File

@@ -13,7 +13,9 @@ package org.springframework.ide.vscode.boot.java.handlers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -25,13 +27,17 @@ import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.CodeLens;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.spel.AnnotationParamSpelExtractor;
import org.springframework.ide.vscode.boot.java.spel.AnnotationParamSpelExtractor.Snippet;
import org.springframework.ide.vscode.boot.java.spel.SpelSemanticTokens;
@@ -49,9 +55,9 @@ import com.google.gson.JsonPrimitive;
/**
* @author Udayani V
*/
public class QueryCodeLensProvider implements CodeLensProvider {
public class CopilotCodeLensProvider implements CodeLensProvider {
protected static Logger logger = LoggerFactory.getLogger(QueryCodeLensProvider.class);
protected static Logger logger = LoggerFactory.getLogger(CopilotCodeLensProvider.class);
public static final String CMD_ENABLE_COPILOT_FEATURES = "sts/enable/copilot/features";
@@ -66,13 +72,13 @@ public class QueryCodeLensProvider implements CodeLensProvider {
private SpelSemanticTokens spelSemanticTokens;
private static boolean showCodeLenses;
public QueryCodeLensProvider(JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) {
public CopilotCodeLensProvider(JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) {
this.projectFinder = projectFinder;
this.spelSemanticTokens = spelSemanticTokens;
server.onCommand(CMD_ENABLE_COPILOT_FEATURES, params -> {
if (params.getArguments().get(0) instanceof JsonPrimitive) {
QueryCodeLensProvider.showCodeLenses = ((JsonPrimitive) params.getArguments().get(0)).getAsBoolean();
CopilotCodeLensProvider.showCodeLenses = ((JsonPrimitive) params.getArguments().get(0)).getAsBoolean();
}
return CompletableFuture.completedFuture(showCodeLenses);
});
@@ -84,6 +90,9 @@ public class QueryCodeLensProvider implements CodeLensProvider {
if (!showCodeLenses) {
return;
}
Map<String, String> pointcutMap = findPointcuts(cu);
cu.accept(new ASTVisitor() {
@Override
@@ -91,14 +100,15 @@ public class QueryCodeLensProvider implements CodeLensProvider {
Arrays.stream(spelExtractors).map(e -> e.getSpelRegion(node)).filter(o -> o.isPresent())
.map(o -> o.get()).forEach(snippet -> {
String additionalContext = parseSpelAndFetchContext(cu, snippet.text());
provideCodeLensForSpelExpression(cancelToken, node, document, snippet,
additionalContext, resultAccumulator);
provideCodeLensForSpelExpression(cancelToken, node, document, snippet, additionalContext, resultAccumulator);
});
if (isQueryAnnotation(node)) {
String queryPrompt = determineQueryPrompt(document);
provideCodeLensForQuery(cancelToken, node, document, node.getValue(), queryPrompt,
resultAccumulator);
QueryType queryType = determineQueryType(document);
provideCodeLensForExpression(cancelToken, node, document, queryType, "", resultAccumulator);
} else if (isAopAnnotation(node)) {
String additionalPointcutContext = extractPointcutReference(node.getValue(), pointcutMap);
provideCodeLensForExpression(cancelToken, node, document, QueryType.AOP, additionalPointcutContext, resultAccumulator);
}
return super.visit(node);
@@ -106,31 +116,40 @@ public class QueryCodeLensProvider implements CodeLensProvider {
@Override
public boolean visit(NormalAnnotation node) {
Arrays.stream(spelExtractors).map(e -> e.getSpelRegion(node)).filter(o -> o.isPresent())
.map(o -> o.get()).forEach(snippet -> {
String additionalContext = parseSpelAndFetchContext(cu, snippet.text());
provideCodeLensForSpelExpression(cancelToken, node, document, snippet, additionalContext,
resultAccumulator);
provideCodeLensForSpelExpression(cancelToken, node, document, snippet, additionalContext, resultAccumulator);
});
if (isQueryAnnotation(node)) {
String queryPrompt = determineQueryPrompt(document);
for (Object value : node.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
if ("value".equals(pair.getName().getIdentifier())) {
provideCodeLensForQuery(cancelToken, node, document, pair.getValue(), queryPrompt,
resultAccumulator);
break;
}
}
QueryType queryType = determineQueryType(document);
provideCodeLensForExpression(cancelToken, node, document, queryType, "", resultAccumulator);
} else if (isAopAnnotation(node)) {
Expression value = getMemberValue(node);
String additionalPointcutContext = null;
if (value != null) {
additionalPointcutContext = extractPointcutReference(value, pointcutMap);
}
provideCodeLensForExpression(cancelToken, node, document, QueryType.AOP, additionalPointcutContext, resultAccumulator);
}
return super.visit(node);
}
private Expression getMemberValue(NormalAnnotation node) {
for (Object value : node.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
if ("pointcut".equals(pair.getName().getIdentifier())) {
return pair.getValue();
}
}
}
return null;
}
});
}
@@ -163,20 +182,26 @@ public class QueryCodeLensProvider implements CodeLensProvider {
}
}
protected void provideCodeLensForQuery(CancelChecker cancelToken, Annotation node, TextDocument document,
Expression valueExp, String query, List<CodeLens> resultAccumulator) {
protected void provideCodeLensForExpression(CancelChecker cancelToken, Annotation node, TextDocument document,
QueryType queryType, String additionalContext, List<CodeLens> resultAccumulator) {
cancelToken.checkCanceled();
if (valueExp != null) {
if (node != null) {
try {
String context = additionalContext != null && !additionalContext.isEmpty() ? String.format(
"""
This is the pointcut definition referenced in the above annotation. \n\n %s \n\nProvide a brief summary of the pointcut's role within the annotation.
Avoid detailed implementation steps and avoid repeating information covered earlier.
""",additionalContext) : "";
CodeLens codeLens = new CodeLens();
codeLens.setRange(document.toRange(valueExp.getStartPosition(), valueExp.getLength()));
codeLens.setRange(document.toRange(node.getStartPosition(), node.getLength()));
Command cmd = new Command();
cmd.setTitle(QueryType.DEFAULT.getTitle());
cmd.setTitle(queryType.getTitle());
cmd.setCommand(CMD);
cmd.setArguments(ImmutableList.of(query + valueExp.toString()));
cmd.setArguments(ImmutableList.of(queryType.getPrompt() + node.toString() + "\n\n" +context));
codeLens.setCommand(cmd);
resultAccumulator.add(codeLens);
@@ -190,15 +215,15 @@ public class QueryCodeLensProvider implements CodeLensProvider {
return FQN_QUERY.equals(a.getTypeName().getFullyQualifiedName())
|| QUERY.equals(a.getTypeName().getFullyQualifiedName());
}
private String determineQueryPrompt(TextDocument document) {
private QueryType determineQueryType(TextDocument document) {
Optional<IJavaProject> optProject = projectFinder.find(document.getId());
if (optProject.isPresent()) {
IJavaProject jp = optProject.get();
return SpringProjectUtil.hasDependencyStartingWith(jp, "hibernate-core", null) ? QueryType.HQL.getPrompt()
: QueryType.JPQL.getPrompt();
return SpringProjectUtil.hasDependencyStartingWith(jp, "hibernate-core", null) ? QueryType.HQL
: QueryType.JPQL;
}
return QueryType.DEFAULT.getPrompt();
return QueryType.DEFAULT;
}
private String parseSpelAndFetchContext(CompilationUnit cu, String spelExpression) {
@@ -238,4 +263,49 @@ public class QueryCodeLensProvider implements CodeLensProvider {
return methodContext;
}
private boolean isAopAnnotation(Annotation a) {
String annotationFQN = a.getTypeName().getFullyQualifiedName();
return Annotations.AOP_ANNOTATIONS.containsKey(annotationFQN)
|| Annotations.AOP_ANNOTATIONS.containsValue(annotationFQN);
}
private Map<String, String> findPointcuts(CompilationUnit cu) {
Map<String, String> pointcutMap = new HashMap<>();
cu.accept(new ASTVisitor() {
@Override
public boolean visit(MethodDeclaration node) {
for (Object modifierObj : node.modifiers()) {
if (modifierObj instanceof Annotation) {
Annotation annotation = (Annotation) modifierObj;
if ("Pointcut".equals(annotation.getTypeName().getFullyQualifiedName())) {
String methodName = node.getName().getIdentifier();
pointcutMap.put(methodName, node.toString());
}
}
}
return super.visit(node);
}
});
return pointcutMap;
}
private String extractPointcutReference(org.eclipse.jdt.core.dom.Expression expression, Map<String, String> pointcutMap) {
if (expression instanceof MethodInvocation) {
return ((MethodInvocation) expression).getName().getIdentifier();
} else if (expression instanceof SimpleName) {
return ((SimpleName) expression).getIdentifier();
} else if (expression instanceof StringLiteral) {
String literalValue = ((StringLiteral) expression).getLiteralValue();
StringBuilder pointcuts = new StringBuilder();
for (Map.Entry<String, String> entry : pointcutMap.entrySet()) {
if (literalValue.contains(entry.getKey())) {
pointcuts.append(entry.getValue());
}
}
return pointcuts.toString();
}
return null;
}
}

View File

@@ -1,10 +1,11 @@
package org.springframework.ide.vscode.boot.java.handlers;
public enum QueryType {
SPEL("Explain SpEL Expression using Copilot", "Explain the following SpEL Expression with a clear summary first, followed by a breakdown of the expression with details: \n\n"),
JPQL("Explain Query using Copilot", "Explain the following JPQL query with a clear summary first, followed by a detailed explanation. If the query contains any SpEL expressions, explain those parts as well: \n\n"),
HQL("Explain Query using Copilot", "Explain the following HQL query with a clear summary first, followed by a detailed explanation. If the query contains any SpEL expressions, explain those parts as well: \n\n"),
DEFAULT("Explain Query using Copilot", "Explain the following query with a clear summary first, followed by a detailed explanation: \n\n");
SPEL("Explain SpEL Expression with Copilot", "Explain the following SpEL Expression with a clear summary first, followed by a breakdown of the expression with details: \n\n"),
JPQL("Explain Query with Copilot", "Explain the following JPQL query with a clear summary first, followed by a detailed explanation. If the query contains any SpEL expressions, explain those parts as well: \n\n"),
HQL("Explain Query with Copilot", "Explain the following HQL query with a clear summary first, followed by a detailed explanation. If the query contains any SpEL expressions, explain those parts as well: \n\n"),
AOP("Explain AOP annotation with Copilot", "Explain the following AOP annotation with a clear summary first, followed by a detailed contextual explanation of annotation and its purpose: \n\n"),
DEFAULT("Explain Query with Copilot", "Explain the following query with a clear summary first, followed by a detailed explanation: \n\n");
private final String title;
private final String prompt;

View File

@@ -38,7 +38,7 @@ import org.springframework.context.annotation.Import;
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
import org.springframework.ide.vscode.boot.java.handlers.QueryCodeLensProvider;
import org.springframework.ide.vscode.boot.java.handlers.CopilotCodeLensProvider;
import org.springframework.ide.vscode.boot.java.handlers.QueryType;
import org.springframework.ide.vscode.boot.java.spel.SpelSemanticTokens;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
@@ -59,7 +59,7 @@ import com.google.gson.JsonPrimitive;
@ExtendWith(SpringExtension.class)
@BootLanguageServerTest
@Import(SymbolProviderTestConf.class)
public class QueryCodeLensProviderTest {
public class CopilotCodeLensProviderTest {
@Autowired
private BootLanguageServerHarness harness;
@@ -73,17 +73,17 @@ public class QueryCodeLensProviderTest {
private SpelSemanticTokens spelSemanticTokens;
private ArgumentCaptor<ExecuteCommandHandler> commandHandlerCaptor;
private QueryCodeLensProvider queryCodeLensProvider;
private CopilotCodeLensProvider queryCodeLensProvider;
private File directory;
@BeforeEach
public void setup() throws Exception {
harness.intialize(null);
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spel-query-codelense/").toURI());
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spel-query-aop-codelenses/").toURI());
String projectDir = directory.toURI().toString();
server = mock(SimpleLanguageServer.class);
commandHandlerCaptor = ArgumentCaptor.forClass(ExecuteCommandHandler.class);
queryCodeLensProvider = new QueryCodeLensProvider(projectFinder, server, spelSemanticTokens);
queryCodeLensProvider = new CopilotCodeLensProvider(projectFinder, server, spelSemanticTokens);
// trigger project creation
projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
@@ -91,7 +91,7 @@ public class QueryCodeLensProviderTest {
CompletableFuture<Void> initProject = indexer.waitOperation();
initProject.get(5, TimeUnit.SECONDS);
verify(server).onCommand(eq(QueryCodeLensProvider.CMD_ENABLE_COPILOT_FEATURES), commandHandlerCaptor.capture());
verify(server).onCommand(eq(CopilotCodeLensProvider.CMD_ENABLE_COPILOT_FEATURES), commandHandlerCaptor.capture());
}
@Test
@@ -107,9 +107,9 @@ public class QueryCodeLensProviderTest {
assertEquals(3, codeLenses.size());
assertTrue(containsCodeLens(codeLenses.get(0), QueryType.DEFAULT.getTitle(), 9, 8, 9, 108));
assertTrue(containsCodeLens(codeLenses.get(1), QueryType.DEFAULT.getTitle(), 13, 8, 13, 39));
assertTrue(containsCodeLens(codeLenses.get(2), QueryType.DEFAULT.getTitle(), 17, 14, 17, 92));
assertTrue(containsCodeLens(codeLenses.get(0), QueryType.DEFAULT.getTitle(), 9, 1, 9, 109));
assertTrue(containsCodeLens(codeLenses.get(1), QueryType.DEFAULT.getTitle(), 13, 1, 13, 40));
assertTrue(containsCodeLens(codeLenses.get(2), QueryType.DEFAULT.getTitle(), 17, 1, 17, 93));
}
@Test
@@ -194,6 +194,85 @@ public static String concat(String str1,String str2){
assertEquals(expectedPrompt, actualPrompt);
}
@Test
public void testShowCodeLensesTrueForAOP() throws Exception {
setCommandParamsHandler(true);
String docUri = directory.toPath().resolve("src/main/java/org/test/MyAspect.java").toUri().toString();
TextDocumentInfo doc = harness.getOrReadFile(new File(new URI(docUri)), LanguageId.JAVA.getId());
TextDocumentInfo openedDoc = harness.openDocument(doc);
List<? extends CodeLens> codeLenses = harness.getCodeLenses(openedDoc);
assertEquals(7, codeLenses.size());
assertTrue(containsCodeLens(codeLenses.get(0), QueryType.AOP.getTitle(), 9, 1, 9, 53));
assertTrue(containsCodeLens(codeLenses.get(1), QueryType.AOP.getTitle(), 14, 1, 14, 24));
assertTrue(containsCodeLens(codeLenses.get(2), QueryType.AOP.getTitle(), 19, 1, 19, 51));
assertTrue(containsCodeLens(codeLenses.get(3), QueryType.AOP.getTitle(), 27, 1, 27, 50));
assertTrue(containsCodeLens(codeLenses.get(4), QueryType.AOP.getTitle(), 32, 1, 32, 92));
assertTrue(containsCodeLens(codeLenses.get(5), QueryType.AOP.getTitle(), 37, 1, 37, 86));
assertTrue(containsCodeLens(codeLenses.get(6), QueryType.AOP.getTitle(), 42, 1, 42, 65));
}
@Test
public void testShowCodeLensesTrueForAopPointcutExamples() throws Exception {
setCommandParamsHandler(true);
String docUri = directory.toPath().resolve("src/main/java/org/test/PointcutExamples.java").toUri().toString();
TextDocumentInfo doc = harness.getOrReadFile(new File(new URI(docUri)), LanguageId.JAVA.getId());
TextDocumentInfo openedDoc = harness.openDocument(doc);
String expectedPrompt = """
Explain the following AOP annotation with a clear summary first, followed by a detailed contextual explanation of annotation and its purpose: \n
@Pointcut("cflow(execution(* com.example..*.*(..)))")
""";
String expectedPromptWithContext = """
Explain the following AOP annotation with a clear summary first, followed by a detailed contextual explanation of annotation and its purpose: \n
@AfterReturning(pointcut="targetService()",returning="result")
This is the pointcut definition referenced in the above annotation. \n
@Pointcut("target(com.example.service.MyService)") public void targetService(){
}
\n
Provide a brief summary of the pointcut's role within the annotation.
Avoid detailed implementation steps and avoid repeating information covered earlier.
""";
String expectedPromptWithMultiPointcutRef = """
Explain the following AOP annotation with a clear summary first, followed by a detailed contextual explanation of annotation and its purpose: \n
@Pointcut("serviceLayer() || repositoryLayer()")
This is the pointcut definition referenced in the above annotation. \n
@Pointcut("within(com.example.repository..*)") public void repositoryLayer(){
}
@Pointcut("execution(* com.example.service.*.*(..))") public void serviceLayer(){
}
\n
Provide a brief summary of the pointcut's role within the annotation.
Avoid detailed implementation steps and avoid repeating information covered earlier.
""";
List<? extends CodeLens> codeLenses = harness.getCodeLenses(openedDoc);
assertEquals(8, codeLenses.size());
assertTrue(containsCodeLens(codeLenses.get(0), QueryType.AOP.getTitle(), 4, 1, 4, 54));
assertTrue(containsCodeLens(codeLenses.get(3), QueryType.AOP.getTitle(), 15, 1, 15, 64));
String actualPrompt = codeLenses.get(0).getCommand().getArguments().get(0).toString();
String actualPromptWithContext = codeLenses.get(3).getCommand().getArguments().get(0).toString();
String actualPromptWithMultiPointcutRef = codeLenses.get(7).getCommand().getArguments().get(0).toString();
assertEquals(expectedPrompt, actualPrompt);
assertEquals(expectedPromptWithContext, actualPromptWithContext);
assertEquals(expectedPromptWithMultiPointcutRef, actualPromptWithMultiPointcutRef);
}
@Test
public void testShowCodeLensesFalseForQuery() throws Exception {
@@ -222,6 +301,20 @@ public static String concat(String str1,String str2){
assertEquals(0, codeLenses.size());
}
@Test
public void testShowCodeLensesFalseForAOP() throws Exception {
setCommandParamsHandler(false);
String docUri = directory.toPath().resolve("src/main/java/org/test/MyAspect.java").toUri().toString();
TextDocumentInfo doc = harness.getOrReadFile(new File(new URI(docUri)), LanguageId.JAVA.getId());
TextDocumentInfo openedDoc = harness.openDocument(doc);
List<? extends CodeLens> codeLenses = harness.getCodeLenses(openedDoc);
assertEquals(0, codeLenses.size());
}
private void setCommandParamsHandler(boolean value) throws InterruptedException, ExecutionException {
ExecuteCommandHandler handler = commandHandlerCaptor.getValue();
ExecuteCommandParams params = new ExecuteCommandParams();

View File

@@ -35,6 +35,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Spring Context for SpEL -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,56 @@
package org.test;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.example.aopdemo..*(..))")
public void myPointcut() {
// Pointcut definition
}
@Before("myPointcut()")
public void beforeAdvice() {
System.out.println("Before advice executed");
}
@Around("execution(* com.example.aopdemo..*(..))")
public Object aroundAdvice(org.aspectj.lang.ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around advice: before method execution");
Object result = joinPoint.proceed();
System.out.println("Around advice: after method execution");
return result;
}
@After("execution(* com.example.aopdemo..*(..))")
public void afterAdvice() {
System.out.println("After advice executed");
}
@AfterReturning(pointcut = "execution(* com.example.aopdemo..*(..))", returning = "result")
public void afterReturningAdvice(Object result) {
System.out.println("After returning advice executed, returned: " + result);
}
@AfterThrowing(pointcut = "execution(* com.example.aopdemo..*(..))", throwing = "ex")
public void afterThrowingAdvice(Exception ex) {
System.out.println("After throwing advice executed, exception: " + ex.getMessage());
}
@DeclareParents(value = "org.test..*", defaultImpl = Test.class)
public static MyInterface mixin;
}
interface MyInterface {
void someMethod();
}
class Test implements MyInterface {
@Override
public void someMethod() {
System.out.println("Default implementation");
}
}

View File

@@ -123,16 +123,4 @@ public class Owner extends Person {
return null;
}
@Override
public String toString() {
return new ToStringCreator(this).append("id", this.getId())
.append("new", this.isNew())
.append("lastName", this.getLastName())
.append("firstName", this.getFirstName())
.append("address", this.address)
.append("city", this.city)
.append("telephone", this.telephone)
.toString();
}
}

View File

@@ -0,0 +1,62 @@
package org.test;
import java.time.LocalDate;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.samples.petclinic.model.NamedEntity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
@Entity
@Table(name = "pets")
public class Pet extends NamedEntity {
@Column(name = "birth_date")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate;
@ManyToOne
@JoinColumn(name = "type_id")
private PetType type;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "pet_id")
@OrderBy("visit_date ASC")
private Set<Visit> visits = new LinkedHashSet<>();
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public LocalDate getBirthDate() {
return this.birthDate;
}
public PetType getType() {
return this.type;
}
public void setType(PetType type) {
this.type = type;
}
public Collection<Visit> getVisits() {
return this.visits;
}
public void addVisit(Visit visit) {
getVisits().add(visit);
}
}

View File

@@ -0,0 +1,38 @@
package org.test;
public class PointcutExamples {
@Pointcut("cflow(execution(* com.example..*.*(..)))")
public void myPointcutFlow() {}
@Pointcut("target(com.example.service.MyService)")
public void targetService() {}
@Before("myPointcutFlow()")
public void beforeAdviceFlow() {
System.out.println("Before advice triggered by control flow.");
}
@AfterReturning(pointcut="targetService()", returning="result")
public void afterReturningAdvice(Object result) {
System.out.println("After returning advice: " + result);
}
@Around("targetService()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around advice: before method execution");
Object result = joinPoint.proceed();
System.out.println("Around advice: after method execution");
return result;
}
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Pointcut("within(com.example.repository..*)")
public void repositoryLayer() {}
@Pointcut("serviceLayer() || repositoryLayer()")
public void applicationLayer() {}
}