Programação Orientada a Aspectos, Spring AOP

A programação orientada a aspectos (AOP) é ​​um paradigma de programação que é um desenvolvimento adicional da programação procedural e orientada a objetos (OOP). A idéia do AOP é destacar a chamada funcionalidade de ponta a ponta. E, como tudo está em ordem, mostrarei aqui como fazê-lo no estilo de anotação Java - Spring @AspectJ (também existe um estilo xml baseado em esquema, a funcionalidade é semelhante).

Destacando a funcionalidade de ponta a ponta


Para

imagem

e depois

imagem

I.e. existe uma funcionalidade que afeta vários módulos, mas não está diretamente relacionada ao código comercial, e seria bom colocá-lo em um local separado, conforme mostrado na figura acima.

Ponto de união



imagem

Ponto de junção - o próximo conceito de AOP, esses são os pontos de observação, adesão ao código onde a introdução da funcionalidade é planejada.

Pointcut


imagem

Um pointcut é uma fatia, uma consulta para pontos de anexo, pode ser um ou mais pontos. As regras para consultar pontos são muito diversas, na figura acima, uma solicitação de anotação em um método e um método específico. As regras podem ser combinadas com &&, ||,!

Conselhos


imagem

Conselho - um conjunto de instruções executadas nos pontos de corte (Pointcut). As instruções podem ser executadas em um evento de vários tipos:

  • Antes - antes de uma chamada de método
  • Depois - após uma chamada de método
  • Após retornar - após retornar um valor de uma função
  • Depois de jogar - em caso de exceção
  • Depois de finalmente - se o bloco finalmente for executado
  • Around - você pode fazer o pré., Post., Processando antes da chamada do método, e também geralmente ignorar a chamada do método.

em um Pointcut, você pode "travar" vários conselhos de diferentes tipos.

Aspecto


imagem

Aspecto - um módulo que contém descrições Pointcut e Conselhos.

Agora vou dar um exemplo e, finalmente, tudo se encaixará (ou quase todos). Todos sabemos sobre o código de registro que permeia muitos módulos, não relacionados ao código comercial, mas, no entanto, é impossível sem ele. E, portanto, separo essa funcionalidade do código comercial.
Exemplo - registro de código

Serviço de destino

@Service public class MyService { public void method1(List<String> list) { list.add("method1"); System.out.println("MyService method1 list.size=" + list.size()); } @AspectAnnotation public void method2() { System.out.println("MyService method2"); } public boolean check() { System.out.println("MyService check"); return true; } } 

Um aspecto com uma descrição de Pointcut e Conselhos.

 @Aspect @Component public class MyAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { } @Before("callAtMyServicePublic()") public void beforeCallAtMethod1(JoinPoint jp) { String args = Arrays.stream(jp.getArgs()) .map(a -> a.toString()) .collect(Collectors.joining(",")); logger.info("before " + jp.toString() + ", args=[" + args + "]"); } @After("callAtMyServicePublic()") public void afterCallAt(JoinPoint jp) { logger.info("after " + jp.toString()); } } 

E o código de teste de chamada

 @RunWith(SpringRunner.class) @SpringBootTest public class DemoAspectsApplicationTests { @Autowired private MyService service; @Test public void testLoggable() { List<String> list = new ArrayList(); list.add("test"); service.method1(list); service.method2(); Assert.assertTrue(service.check()); } } 

Explicações No serviço de destino, não há menção ao log, no código de chamada ainda mais, todo o log é concentrado em um módulo separado
@Aspect
class MyAspect ...


Em pointcut

  @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { } 

Solicitei todos os métodos públicos do MyService com qualquer tipo de retorno * e o número de argumentos (..)

No aviso Antes e Depois, consulte o Pointcut (callAtMyServicePublic) , escrevi instruções para escrever no log. O JoinPoint não é um parâmetro obrigatório, que fornece informações adicionais, mas, se usado, deve ser o primeiro.
Tudo é espaçado em diferentes módulos! Código de chamada, destino, registro.

Resultado no console

imagem

As regras do pointcut podem variar
Alguns exemplos de Pointcut and Advice:

Solicitação de anotação em um método.

 @Pointcut("@annotation(AspectAnnotation)") public void callAtMyServiceAnnotation() { } 

Conselhos para ele

  @Before("callAtMyServiceAnnotation()") public void beforeCallAt() { } 

Solicitação de um método específico indicando os parâmetros do método de destino

 @Pointcut("execution(* com.example.demoAspects.MyService.method1(..)) && args(list,..))") public void callAtMyServiceMethod1(List<String> list) { } 

Conselhos para ele

  @Before("callAtMyServiceMethod1(list)") public void beforeCallAtMethod1(List<String> list) { } 

Pointcut para resultado de retorno

  @Pointcut("execution(* com.example.demoAspects.MyService.check())") public void callAtMyServiceAfterReturning() { } 

Conselhos para ele

  @AfterReturning(pointcut="callAtMyServiceAfterReturning()", returning="retVal") public void afterReturningCallAt(boolean retVal) { } 

Um exemplo de verificação de direitos para o Aviso do tipo Around, através da anotação

 @Retention(RUNTIME) @Target(METHOD) public @interface SecurityAnnotation { } // @Aspect @Component public class MyAspect { @Pointcut("@annotation(SecurityAnnotation) && args(user,..)") public void callAtMyServiceSecurityAnnotation(User user) { } @Around("callAtMyServiceSecurityAnnotation(user)") public Object aroundCallAt(ProceedingJoinPoint pjp, User user) { Object retVal = null; if (securityService.checkRight(user)) { retVal = pjp.proceed(); } return retVal; } 

Os métodos que precisam ser verificados antes da chamada podem ser anotados com "SecurityAnnotation"; em Aspect, temos uma fatia deles, e todos serão interceptados antes da chamada e a verificação de direitos.

Código de destino:

 @Service public class MyService { @SecurityAnnotation public Balance getAccountBalance(User user) { // ... } @SecurityAnnotation public List<Transaction> getAccountTransactions(User user, Date date) { // ... } } 

Código do chamador:

 balance = myService.getAccountBalance(user); if (balance == null) { accessDenied(user); } else { displayBalance(balance); } 

I.e. no código de chamada e no destino, não há verificação de direitos, apenas o próprio código comercial.
Um exemplo de criação de perfil do mesmo serviço usando um aviso do tipo Around

 @Aspect @Component public class MyAspect { @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { } @Around("callAtMyServicePublic()") public Object aroundCallAt(ProceedingJoinPoint call) throws Throwable { StopWatch clock = new StopWatch(call.toString()); try { clock.start(call.toShortString()); return call.proceed(); } finally { clock.stop(); System.out.println(clock.prettyPrint()); } } } 

Se executarmos o código de chamada com chamadas para os métodos MyService, obteremos o tempo da chamada para cada método. Assim, sem alterar o código de chamada e o destino, adicionei novos recursos: registro, criação de perfil e segurança.
Exemplo de uso em formulários de interface do usuário

Há um código que, ao definir oculta / mostra os campos no formulário:

 public class EditForm extends Form { @Override public void init(Form form) { formHelper.updateVisibility(form, settingsService.isVisible(COMP_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_LAST_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_BIRTH_DATE)); // ... } 

você também pode remover updateVisibility em um Conselho do tipo Around

 @Aspect public class MyAspect { @Pointcut("execution(* com.example.demoAspects.EditForm.init() && args(form,..))") public void callAtInit(Form form) { } // ... @Around("callAtInit(form)") public Object aroundCallAt(ProceedingJoinPoint pjp, Form form) { formHelper.updateVisibility(form, settingsService.isVisible(COMP_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_LAST_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_BIRTH_DATE)); Object retVal = pjp.proceed(); return retVal; } 

etc.

Estrutura do projeto

imagem

arquivo pom
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demoAspects</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demoAspects</name> <description>Demo project for Spring Boot Aspects</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 


Materiais

Programação Orientada a Aspectos com Spring

Source: https://habr.com/ru/post/pt428548/


All Articles