1. Modelo de agência
O cenário aplicável do modo proxy é que às vezes queremos aprimorar os métodos de algumas classes de entidade, mas não é conveniente modificar as definições de método correspondentes nas classes de entidade. Neste momento, podemos escrever a parte aprimorada no proxy através do modo.
1) proxy estático
Suponha que queremos vender telefones celulares, para que possamos escrever a classe de entidade da seguinte maneira:
Person.java
public class Person {
public void sellPhone(){
System.out.println("卖手机");
}
}
teste:
public class TestStaticProxy {
public static void main(String[] args) {
Person person = new Person();
person.sellPhone();
}
}
Agora descobrimos que precisamos encontrar a próxima casa antes de vender telefones celulares, mas definimos o método de venda de telefones celulares. No momento, podemos aprimorá-lo por meio de proxy estático:
1) Use especificações de definição de interface
public interface ISellPhone {
public void sellPhone();
}
2) Método de definição
public class Person implements ISellPhone {
public void sellPhone(){
System.out.println("卖手机");
}
}
3) Aprimorar métodos por meio de agentes
public class SellPhoneProxy implements ISellPhone {
private Person person = new Person();
public void sellPhone() {
System.out.println("寻找买家");
person.sellPhone();
}
}
4) Nós apenas usamos o proxy diretamente
public class TestStaticProxy {
public static void main(String[] args) {
ISellPhone iSellPhone = new SellPhoneProxy();
iSellPhone.sellPhone();
}
}
2) proxy dinâmico JDK
A desvantagem dos agentes estáticos é que para quase todos os negócios, eles devem ser feitos adicionando um agente, mesmo que seus métodos de aprimoramento sejam quase os mesmos; também podemos usar agentes dinâmicos para aprimorar diretamente o método sem escrever muitos agentes de classe .
Adicionamos uma empresa de pós-venda para telefones celulares:
public interface ISellPhone {
public void sellPhone();
public void serviceAfterSelling();
}
Person.java
public class Person implements ISellPhone {
public void sellPhone(){
System.out.println("卖手机");
}
public void serviceAfterSelling() {
System.out.println("售后服务");
}
}
Use a interface de proxy dinâmica do JDK:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class SellPhoneProxy implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用前");
Object result = method.invoke(new Person(),args);
System.out.println("调用后");
return result;
}
}
Vendo o nome do pacote, também devemos saber que o proxy dinâmico é implementado por meio de mecanismo de reflexão. A propriedade Object é usada para armazenar o objeto sendo proxy.
teste:
import java.lang.reflect.Proxy;
public class TestDynamicProxy {
public static void main(String[] args) {
Person person = new Person();
ISellPhone iSellPhone = (ISellPhone) Proxy.newProxyInstance(ISellPhone.class.getClassLoader(),new Class[]{ISellPhone.class},new SellPhoneProxy());
iSellPhone.serviceAfterSelling();
}
}
[Nota]: Seja um proxy estático ou um proxy dinâmico, a classe de proxy e a classe de proxy precisam ter uma interface comum.
3) Agente dinâmico CGLib
Claro, você também pode usar o método de herança, deixar a classe proxy herdar a classe proxy e aprimorar o método correspondente para proxy (cglib) sobrescrevendo o método.
A. Importar dependências
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
B. Use o proxy dinâmico cglib
import java.lang.reflect.Method;
public class Client {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class);
enhancer.setCallback(new InvocationHandler() {
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("执行前");
Object invoke = method.invoke(new Person(),objects);
System.out.println("执行后");
return invoke;
}
});
Person personAgent = (Person) enhancer.create();
personAgent.sellPhone();
System.out.println("============");
personAgent.serviceAfterSelling();
}
}
3. Resultados de execução
2. Programação orientada a aspectos AOP
A programação orientada a aspectos AOP (Aspect Oriented Programming) é usada principalmente para resolver alguns problemas de nível de sistema no processo de desenvolvimento do programa, como logs, transações, permissões, etc.
O AOP do Spring é na verdade um aprimoramento do proxy do método. Após a análise acima, já sabemos que o proxy baseado em interface usa o JDK; se não houver interface, o proxy é usado pelo cglib.
Vamos dar uma olhada na programação orientada a objetos OOP (Object Oriented Programming) primeiro. Todas as coisas são consideradas como objetos. Java é uma linguagem OOP. A extensão das classes é realizada por herança. O sistema construído com Java mostra uma relação vertical.
No entanto, a herança também tem deficiências. Embora a herança possa completar o aprimoramento do método da classe pai, cada aprimoramento da classe requer uma herança, o que levará a muitas classes em todo o sistema, o que não é fácil de manter, especialmente para todos tipos como logs. Quando o método e o método de aprimoramento são quase os mesmos, se for implementado por meio de herança, a carga de trabalho é muito grande.
Em Sring, é definida uma tecnologia de "corte transversal", que é aprimorada pela inserção de uma seção transversal no método sem herança. Todo o sistema apresenta uma relação horizontal, e a camada inferior é realizada através de um agente, que possui uma cruz -section. O efeito é o seguinte:
A. Terminologia relacionada ao AOP:
1. Notificação, processamento avançado (conselho)
O código ou lógica escrita para implementar melhorias funcionais é a segurança, transação, log, etc. mencionados acima.
2. Ponto de conexão (JoinPoint)
O Spring permite a inserção de notificações, incluindo frente, verso, surround (antes e depois) de cada método, e quando uma exceção é lançada, o Spring suporta apenas o ponto de conexão do método.
3. PointCut
Ou seja, nós realmente definimos aqueles pontos de conexão a serem usados, ou seja, aqueles pontos de conexão que realmente cortam a notificação.
4. Aspecto (Aspecto)
O aspecto é a combinação de notificação e ponto de entrada. A notificação é responsável por explicar o que fazer e quando fazer, e o ponto de entrada mostra onde fazer, que juntos constituem o aspecto.
5. Introdução
Introdução nos permite introduzir aspectos na classe de destino.
6. Alvo
A classe alvo mencionada na introdução, ou seja, o objeto a ser notificado, possui uma lógica de negócio real, não precisa se preocupar com o que é cortado, mas apenas com sua própria lógica de negócio.
7. Tecelagem
O processo de aplicação do aspecto ao objeto de destino (recortando a notificação para o ponto de contato) e de construção de um novo objeto proxy de acordo.
B. Sinta o uso de AOP (anotação):
1. Adicionar arquivos de cabeçalho
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
2. Importar dependências tecidas
<!--引入织入相关的依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
3. Adicione dois métodos a serem aprimorados
AdminService.java
package com.zt.Service;
import org.springframework.stereotype.Service;
@Service
public class AdminService {
public Integer getAdmin(){
System.out.println("-----getAdmin-----");
return 100;
}
}
UserService.java
package com.zt.Service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void getUser(){
System.out.println("-----getUser-----");
}
}
4. Escreva a classe de aspecto
package com.zt.Aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogAdvice {
@Before("execution(* com.zt.Service.*.*(..))")
public void before(){
System.out.println("-----方法执行前 before -----");
}
@After("execution(* com.zt.Service.*.*(..))")
public void after(){
System.out.println("-----方法执行后 after -----");
}
@AfterReturning("execution(* com.zt.Service.*.*(..))")
public void afterReturning(){
System.out.println("-----方法执行返回后 afterReturning -----");
}
@Around("execution(* com.zt.Service.*.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("-----环绕前-----");
System.out.println("方法名:" + joinPoint.getSignature());
Object proceed = joinPoint.proceed();
System.out.println("-----环绕后-----");
System.out.println(proceed);
}
@AfterThrowing("execution(* com.zt.Service.*.*(..))")
public void afterThrow(){
System.out.println("-----有异常-----");
}
}
[Nota]: A expressão de execução é usada para especificar quais métodos são aprimorados.
Claro, também podemos especificar qual método melhorar: execução (void com.zt.Service.UserService.getUser (..))
5. Como usamos o método de classe de aspecto, precisamos ativar a varredura de pacote e deixar o contêiner Spring gerenciar o Bean usado para configuração
<context:component-scan base-package="com.zt"/>
6. Ative o suporte de anotação para proxy automático AOP
<aop:aspectj-autoproxy/>
7. Use classes de configuração e teste
package com.zt.Config;
import com.zt.Service.AdminService;
import com.zt.Service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class JavaConfig {
@Autowired
private UserService userService;
@Autowired
private AdminService adminService;
@Test
public void TestAop(){
userService.getUser();
System.out.println("-------------------------------------------");
adminService.getAdmin();
}
}
[Nota]: Spring pode apenas aprimorar o método Bean no contêiner, e o objeto instanciado fora do contêiner não será aprimorado.
C. Uso de AOP (método de arquivo de configuração)
1. Defina a classe de aspecto
package com.zt.Aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Component
public class XMLAdvice {
public void before(){
System.out.println("-----方法执行前 before -----");
}
public void after(){
System.out.println("-----方法执行后 after -----");
}
public void afterReturning(){
System.out.println("-----方法执行返回后 afterReturning -----");
}
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("-----环绕前-----");
System.out.println("方法名:" + joinPoint.getSignature());
Object proceed = joinPoint.proceed();
System.out.println("-----环绕后-----");
System.out.println("方法执行结果的返回值:" + proceed);
}
public void afterThrow(){
System.out.println("-----有异常-----");
}
}
2. No arquivo de configuração, use pointcuts e consulte os aspectos para tecer
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.zt.Service.*.*(..))"/>
<aop:aspect ref="XMLAdvice">
<aop:before pointcut-ref="pointcut" method="before"/>
<aop:after pointcut-ref="pointcut" method="after"/>
</aop:aspect>
</aop:config>
<aop: pointcut>: usado para definir o pointcut.
<aop: aspect>: Usado para introduzir classes de aspecto.
<aop: before>: define o modo de execução como antes da execução. Para introduzir um pointcut pode usar ref para se referir a um pointcut existente (pointcut-ref), ou diretamente usar uma expressão de execução para definir um pointcut. método é usado para especificar de qual método no aspecto a notificação vem, e outros métodos de execução são semelhantes.
<aop:aspect ref="XMLAdvice">
<aop:before pointcut="execution(* com.zt.Service.*.*(..))" method="before"/>
<aop:after pointcut="execution(* com.zt.Service.*.*(..))" method="after"/>
</aop:aspect>
D. Uso de AOP (usar notificação diretamente, implementando algumas interfaces específicas)
LogBefore.java
package com.zt.Aop;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class LogBefore implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----before------" + method.getName() + "-----");
}
}
LogAfter.java
package com.zt.Aop;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class LogAfter implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----after-----" + method.getName() + "------");
}
}
Referência no arquivo de configuração:
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.zt.Service.*.*(..))"/>
<aop:advisor advice-ref="logBefore" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="logAfter" pointcut-ref="pointcut"/>
</aop:config>
[Nota]: Use a anotação @Component para fazer o Spring instanciar e gerenciar automaticamente este bean, e o nome padrão é minúsculo, e os outros permanecem inalterados (nomenclatura camel case). Obviamente, para facilitar a leitura, também podemos especificar o ID do bean injetado:
LogBefore.java
package com.zt.Aop;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component("Before")
public class LogBefore implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----before------" + method.getName() + "-----");
}
}
LogAfter.java
package com.zt.Aop;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component("After")
public class LogAfter implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----after-----" + method.getName() + "------");
}
}
Referência no arquivo de configuração:
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.zt.Service.*.*(..))"/>
<aop:advisor advice-ref="Before" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="After" pointcut-ref="pointcut"/>
</aop:config>