Spring之AOP

目录

一、背景

二、方案

2.1 写死代码

2.2 静态代理

2.2.1 实现

2.2.2 优点

2.2.3 缺点

2.3 动态代理

2.3.1 JDK动态代理

2.3.1.1 实现

2.3.1.2 优点

2.3.1.3 缺点

2.3.2 CGLib动态代理

2.4 Spring AOP:前置增强、后置增强、环绕增强(编程式)

2.4.1 前置增强Before Advice

2.4.2 后置增强After Advice

2.4.3 环绕增强Around Advice

2.5 Spring AOP:前置增强、后置增强、环绕增强(声明式)

2.6 Spring AOP:切面

2.6.1 手动代理

2.6.2 自动代理

2.6.2.1 扫描Bean名称

2.6.2.2 扫描切面配置

2.7 Spring + AspectJ(基于注解)

2.7.1 通过 AspectJ execution 表达式拦截方法

2.7.2 通过 AspectJ @annotation 表达式拦截方法

2.8 Spring + AspectJ(基于配置)


一、背景

在我们的业务系统中,有时候需要用业务系统中的”某些代码”去执行一些公共的动作,比如写日志、数据库连接管理、事务管理。那么这样就需要我们在很多方法中添加重复代码,这样使得代码重复率过高,也不好维护。那么我们想到的是要求每个Action都继承框架提供好的BaseAction,然后实现excute方法来实现。不过,这样的方式缺乏灵活性,那就是我们每个Action都需要写一个类来完成,对于复杂的系统,就需要写太多这样的类,因此,我们期望有一种动态的方式来代理操作,于是动态代理机制就出现了,AOP技术其实也是字节码增强技术,JVM提供的动态代理追根到底也是字节码增强技术。

二、方案

2.1 写死代码

public interface Greeting {
    void sayHello(String name);
}

public class GreetingImpl implements Greeting {
    @Override
    public void sayHello(String name) {
        before();
        System.out.println("Hello! " + name);
        after();
    }

    private void before() {
        System.out.println("Before");
    }
    private void after() {
        System.out.println("After");
    }
}		

2.2 静态代理

2.2.1 实现

首先创建一个接口(JDK代理都是面向接口的)Greeting,然后创建具体实现类GreetingImpl来实现这个接口,在创建一个代理类GreetingProxy同样实现这个接口,不同之处在于,具体实现类的方法中需要将接口中定义的方法的业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层。

单独为 GreetingImpl 这个类写一个代理类:

public class GreetingProxy implements Greeting {
    private GreetingImpl greetingImpl;

    public GreetingProxy(GreetingImpl greetingImpl) {
        this.greetingImpl = greetingImpl;
    }

    @Override
    public void sayHello(String name) {
        before();
        greetingImpl.sayHello(name);
        after();
    }

   private void before() {
        System.out.println("Before");
    }
    private void after() {
        System.out.println("After");
    }
}

客户端调用:

public class Client {
    public static void main(String[] args) {
        Greeting greetingProxy = new GreetingProxy(new GreetingImpl());
        greetingProxy.sayHello("Jack");
    }
}

2.2.2 优点

静态代理是在编译时就将接口、实现类、代理类一股脑儿全部手动完成。

2.2.3 缺点

但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码。

2.3 动态代理

创建代理类将具体类隐藏解耦,不同与静态代理之处在于代理类的创建时机不同,动态代理需要在运行时因需实时创建。并且实现代理类的减少。

2.3.1 JDK动态代理

2.3.1.1 实现

public class JDKDynamicProxy implements InvocationHandler {
 
    private Object target;
 
    public JDKDynamicProxy(Object target) {
        this.target = target;
    }

    @SuppressWarnings("unchecked")
    public  T getProxy() {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }
 
    private void before() {
        System.out.println("Before");
    }
    private void after() {
        System.out.println("After");
    }
}

客户端调用

public class Client {
    public static void main(String[] args) {
        Greeting greeting = new JDKDynamicProxy(new GreetingImpl()).getProxy();
        greeting.sayHello("Jack");
    }
}

2.3.1.2 优点

所有的代理类都合并到动态代理类中

2.3.1.3 缺点

JDK 给我们提供的动态代理只能代理接口,而不能代理没有接口的类。

2.3.2 CGLib动态代理

public class CGLibDynamicProxy implements MethodInterceptor {
 
    private static CGLibDynamicProxy instance = new CGLibDynamicProxy();
 
    private CGLibDynamicProxy() {
    } 
    public static CGLibDynamicProxy getInstance() {
        return instance;
    }
 
    @SuppressWarnings("unchecked")
    public  T getProxy(Class cls) {
        return (T) Enhancer.create(cls, this);
    }
 
    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        before();
        Object result = proxy.invokeSuper(target, args);
        after();
        return result;
    }
 
    private void before() {
        System.out.println("Before");
    }
    private void after() {
        System.out.println("After");
    }
}

使用了Singlton模式,客户端调用:

public class Client {
    public static void main(String[] args) {
        Greeting greeting = CGLibDynamicProxy.getInstance().getProxy(GreetingImpl.class);
        greeting.sayHello("Jack");
    }
}

2.4 Spring AOP:前置增强、后置增强、环绕增强(编程式)

上面例子中提到的 before() 方法,在 Spring AOP 里就叫 Before Advice(前置增强)。有些人将 Advice 直译为"通知",更直白的是对原有代码功能的一种"增强"。再说,CGLib 中也有一个 Enhancer 类,它就是一个增强类。

此外,像 after() 这样的方法就叫 After Advice(后置增强),因为它放在后面来增强代码的功能。

如果能把 before() 与 after() 合并在一起,那就叫 Around Advice(环绕增强),就像汉堡一样,中间夹一根火腿。

实现这些所谓的"增强类",让他们横切到代码中,而不是将这些写死在代码中。

2.4.1 前置增强Before Advice

public class GreetingBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("Before");
    }
}

这个类实现了 org.springframework.aop.MethodBeforeAdvice 接口,我们将需要增强的代码放入其中。

2.4.2 后置增强After Advice

public class GreetingAfterAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object result, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("After");
    }
}

类似地,这个类实现了 org.springframework.aop.AfterReturningAdvice 接口。

客户端调用:

public class Client {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();     // 创建代理工厂
        proxyFactory.setTarget(new GreetingImpl());         // 注入目标类对象
        proxyFactory.addAdvice(new GreetingBeforeAdvice()); // 添加前置增强
        proxyFactory.addAdvice(new GreetingAfterAdvice());  // 添加后置增强 
 
        Greeting greeting = (Greeting) proxyFactory.getProxy(); // 从代理工厂中获取代理
        greeting.sayHello("Jack");                              // 调用代理的方法
    }
}

当然,我们完全可以只定义一个增强类,让它同时实现 MethodBeforeAdvice 与 AfterReturningAdvice 这两个接口,如下:

public class GreetingBeforeAndAfterAdvice implements MethodBeforeAdvice, AfterReturningAdvice {
 
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("Before");
    }
 
    @Override
    public void afterReturning(Object result, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("After");
    }
}

2.4.3 环绕增强Around Advice

这个东西可以把"前置增强"与"后置增强"的功能给合并起来,无需让我们同时实现以上两个接口。

public class GreetingAroundAdvice implements MethodInterceptor {
 
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        before();
        Object result = invocation.proceed();
        after();
        return result;
    }
 
    private void before() {
        System.out.println("Before");
    }
    private void after() {
        System.out.println("After");
    }
}

环绕增强类需要实现 org.aopalliance.intercept.MethodInterceptor 接口。注意,这个接口不是 Spring 提供的,它是 AOP 联盟(一个很牛逼的联盟)写的,Spring 只是借用了它。

在客户端中同样也需要将该增强类的对象添加到代理工厂中:

proxyFactory.addAdvice(new GreetingAroundAdvice());

2.5 Spring AOP:前置增强、后置增强、环绕增强(声明式)

用 Spring 配置文件的方式来定义 Bean 对象,把代码中的 new 操作全部解脱出来。


 

    
    
 

    
    
         
               
                         
            
                greetingAroundAdvice
            
        
    

使用 ProxyFactoryBean 就可以取代前面的 ProxyFactory,其实它们俩就一回事儿。

@Component
public class GreetingImpl implements Greeting {
    ...
}
				
@Component
public class GreetingAroundAdvice implements MethodInterceptor {
    ...
}
public class Client {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml"); // 获取 Spring Context
        Greeting greeting = (Greeting) context.getBean("greetingProxy");                        // 从 Context 中根据 id 获取 Bean 对象(其实就是一个代理)
        greeting.sayHello("Jack");                                                              // 调用代理的方法
    }
}

2.6 Spring AOP:切面

2.6.1 手动代理

之前谈到的 AOP 框架其实可以将它理解为一个拦截器框架,但这个拦截器似乎非常武断。比如说,如果它拦截了一个类,那么它就拦截了这个类中所有的方法。类似地,当我们在使用动态代理的时候,其实也遇到了这个问题。需要在代码中对所拦截的方法名加以判断,才能过滤出我们需要拦截的方法,想想这种做法确实不太优雅。在大量的真实项目中,似乎我们只需要拦截特定的方法就行了,没必要拦截所有的方法。于是,借助了 AOP 的一个很重要的工具,Advisor(切面),来解决这个问题。它也是 AOP 中的核心!是我们关注的重点!也就是说,我们可以通过切面,将增强类拦截匹配条件组合在一起,然后将这个切面配置到 ProxyFactory 中,从而生成代理。这里提到这个"拦截匹配条件"在 AOP 中就叫做 Pointcut(切点),其实说白了就是一个基于表达式的拦截条件罢了。

归纳一下,Advisor(切面)封装了 Advice(增强)与 Pointcut(切点 )。

在 GreetingImpl 类中故意增加了两个方法,都以"good"开头。下面要做的就是拦截这两个新增的方法,而对 sayHello() 方法不作拦截。

@Component
public class GreetingImpl implements Greeting {
 
    @Override
    public void sayHello(String name) {
        System.out.println("Hello! " + name);
    }
 
    public void goodMorning(String name) {
        System.out.println("Good Morning! " + name);
    }
 
    public void goodNight(String name) {
        System.out.println("Good Night! " + name);
    }
}

     

    
    
                    
         
    
 
    
    
                        
         
                    
    

以上代理对象的配置中的 interceptorNames,它不再是一个增强,而是一个切面,因为已经将增强封装到该切面中了。此外,切面还定义了一个切点(正则表达式),其目的是为了只将满足切点匹配条件的方法进行拦截。

需要强调的是,这里的切点表达式是基于正则表达式的。示例中的"aop.demo.GreetingImpl.good.*"表达式后面的".*"表示匹配所有字符,翻译过来就是"匹配 aop.demo.GreetingImpl 类中以 good 开头的方法"。

2.6.2 自动代理

2.6.2.1 扫描Bean名称

Spring AOP 提供了一个可根据 Bean 名称来自动生成代理的工具,它就是 BeanNameAutoProxyCreator。是这样配置的:



    ...
 
    
                               
         
                                 
    

以上这个例子只能匹配目标类,而不能进一步匹配其中指定的方法,要匹配方法,就要考虑使用切面与切点了。Spring AOP 基于切面也提供了一个自动代理生成器:DefaultAdvisorAutoProxyCreator。(扫描切面配置)

2.6.2.2 扫描切面配置


 
    ...
 
    
        
        
    
 

    
        
    

这里无需再配置代理了,因为代理将会由 DefaultAdvisorAutoProxyCreator 自动生成。也就是说,这个类可以扫描所有的切面类,并为其自动生成代理。

在 Spring 配置文件中,仍然会存在大量的切面配置。然而在有很多情况下 Spring AOP 所提供的切面类真的不太够用了,比如:想拦截指定注解的方法,我们就必须扩展 DefaultPointcutAdvisor 类,自定义一个切面类,然后在 Spring 配置文件中进行切面配置。

后来,集成了 AspectJ,同时也保留了以上提到的切面与代理配置方式。将 Spring 与 AspectJ 集成与直接使用 AspectJ 是不同的,我们不需要定义 AspectJ 类(它是扩展了 Java 语法的一种新的语言,还需要特定的编译器),只需要使用 AspectJ 切点表达式即可(它是比正则表达式更加友好的表现形式)。

2.7 Spring + AspectJ(基于注解)

2.7.1 通过 AspectJ execution 表达式拦截方法

@Aspect
@Component
public class GreetingAspect {
 
    @Around("execution(* aop.demo.GreetingImpl.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        before();
        Object result = pjp.proceed();
        after();
        return result;
    }
 
    private void before() {
        System.out.println("Before");
    }
    private void after() {
        System.out.println("After");
    }
}

类上面标注的 @Aspect 注解,这表明该类是一个 Aspect(其实就是 Advisor)。该类无需实现任何的接口,只需定义一个方法(方法叫什么名字都无所谓),只需在方法上标注 @Around 注解,在注解中使用了 AspectJ 切点表达式。方法的参数中包括一个 ProceedingJoinPoint 对象,它在 AOP 中称为 Joinpoint(连接点),可以通过该对象获取方法的任何信息,例如:方法名、参数等。

execution(* aop.demo.GreetingImpl.*(..))

execution():表示拦截方法,括号中可定义需要匹配的规则。

第一个"*":表示方法的返回值是任意的。

第二个"*":表示匹配该类中所有的方法。

(..):表示方法的参数是任意的。

配置文件:


 
    
 
    

需要注意的是 proxy-target-class="true" 属性,它的默认值是 false,默认只能代理接口(使用 JDK 动态代理),当为 true 时,才能代理目标类(使用 CGLib 动态代理)。

2.7.2 通过 AspectJ @annotation 表达式拦截方法

拦截指定的注解的方法

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tag {
}

以上定义了一个 @Tag 注解,此注解可标注在方法上,在运行时生效。

@Aspect
@Component
public class GreetingAspect {
 
    @Around("@annotation(aop.demo.Tag)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        ...
    }
    ...
}

使用了 @annotation() 表达式,只需在括号内定义需要拦截的注解名称即可。

直接将 @Tag 注解定义在您想要拦截的方法上,就这么简单:

@Component
public class GreetingImpl implements Greeting {
 
    @Tag
    @Override
    public void sayHello(String name) {
        System.out.println("Hello! " + name);
    }
}

2.8 Spring + AspectJ(基于配置)


 
    
 
    
 
    
        
            
        
    

使用 元素来进行 AOP 配置,在其子元素中配置切面,包括增强类型、目标方法、切点等信息。

 

 

 

参考:

静态代理、动态代理:https://www.cnblogs.com/V1haoge/p/5860749.html

https://www.cnblogs.com/javastudy123/p/4423969.html

https://blog.csdn.net/kexinxin1/article/details/93013718

你可能感兴趣的:(Spring之AOP)