【Spring从入门到出家】4 - 静态代理、动态代理再到AOP

7 静态代理

代理比较好理解,类似于生活中的房屋中介、经销商、代理商
假设没有代理,顾客就可以直接从工厂里面购买东西了
在这里插入图片描述很美好,没有中间商赚差价
但现实很骨感,工厂在售卖的时候,需要负责接待顾客、展示产品、磋商洽谈等等这些工作,当只有一个顾客的话还好,如果有成千上万个顾客,那工厂一定吃不消了,这个时候自然而然想要把这些重复的工作交给别人来干。
在这里插入图片描述

  1. Salable接口,可售卖接口
public interface Salable {
    void sale();
}
  1. Product类,
public class Product implements Salable{
    private long money;

    public Product(long money){
        this.money = money;
    }

    public long getMoney() {
        return money;
    }

    public void setMoney(long money) {
        this.money = money;
    }

    public void sale(){
        System.out.println("买东西,价格为"+money+"$");
    }
}
  1. 代理类Proxy
public class Proxy implements Salable{
    Product product;

    public Proxy(Product product){
        this.product = product;
    }

    public void sale() {
        System.out.println("欢迎光临!");
        System.out.println("原价为:"+product.getMoney());
        System.out.println("服务价位:1000$");
        product.setMoney(product.getMoney()+1000L);
        product.sale();
        System.out.println("欢迎下次光临!");
    }
}
  1. 顾客类Client
public class Client {
    @Test
    public void purchase(){
        Proxy proxy = new Proxy(new Product(2000L));
        proxy.sale();
    }
}

结果为:

欢迎光临!
原价为:2000
服务价位:1000$
买东西,价格为3000$
欢迎下次光临!

以上整个过程就是代理的过程了,这样的代理叫做“静态代理”,静态是相对于动态而言的,静态代理不能实现对不同顾客呈现不同的表现,而动态代理却可以。

8 动态代理

java的动态代理本质还是依靠反射来实现的,它能够动态生成代理类。
动态代理分为两大类:

  • 基于接口——JDK提供实现
  • 基于继承——cglib
  • (字节码实现——javasist)

8.1 基于接口的动态代理实现

  1. 改变一下我们的Salable接口
public interface Salable {
    void sale();
    void secondHandSale();
}
  1. 修改实现类Product
public class Product implements Salable{
    private long money;

    public Product(long money){
        this.money = money;
    }

    public long getMoney() {
        return money;
    }

    public void setMoney(long money) {
        this.money = money;
    }

    public void sale(){
        System.out.println("卖东西,价格为"+money+"$");
    }

    public void secondHandSale() {
        System.out.println("卖东西,价格为"+(money*0.8)+"$");
    }
}
  1. 编写InvocationHandler的实现类ProxyInvocationHandler,它的作用是动态生成Salable的代理类
public class ProxyInvocationHandler implements InvocationHandler {

    private Salable salable;

    public void setSalable(Salable salable) {
        this.salable = salable;
    }

    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                salable.getClass().getInterfaces(),
                this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equalsIgnoreCase("secondHandSale")){
            System.out.println("二手商品打八折");
        }
        Object result = method.invoke(salable,args);
        return result;
    }
}
public class Client {
    @Test
    public void purchase(){
        Product product = new Product(2000L);
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setSalable(product);
        Salable proxy = (Salable) pih.getProxy();
        proxy.sale();
        System.out.println();
        proxy.secondHandSale();
    }
}

结果输出为:

卖东西,价格为2000$

二手商品打八折
卖东西,价格为1600.0$

8.2 基于子类的动态代理实现

JDK只能提供对接口的动态代理的实现,接下来我们使用cglib来实现对类的直接动态代理

  1. 导入依赖
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
  1. 修改Product类,不再继承任何接口
public class Product {
    private long money;

    public Product() {
    }

    public Product(long money){
        this.money = money;
    }

    public long getMoney() {
        return money;
    }

    public void setMoney(long money) {
        this.money = money;
    }

    public void sale(){
        System.out.println("卖东西,价格为"+money+"$");
    }

    public void secondHandSale(){
        System.out.println("卖东西,价格为"+(money*0.8)+"$");

    }
}
  1. 创建Client类
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class Client {
    public static void main(String[] args) {
        //和JDK提供的动态代理的Proxy类一样,只不过Enhancer能够同时支持接口和普通类
        Enhancer enhancer = new Enhancer();
        //设置要代理的类,将其作为父类
        enhancer.setSuperclass(Product.class);
        //设置回调方法——可实现对方法的增强
        enhancer.setCallback(new MethodInterceptor(){
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

                if (method.getName().equalsIgnoreCase("secondHandSale")){
                    System.out.println("二手商品打八折");
                }

                Object result = methodProxy.invokeSuper(o, objects);
                return result;
            }
        });
        
        Product product = (Product) enhancer.create();
        product.setMoney(2000L);
        product.sale();
        System.out.println();
        product.secondHandSale();
    }

}

  1. 结果可得:
卖东西,价格为2000$

二手商品打八折
卖东西,价格为1600.0$

9 Spring中AOP的实现

9.1 AOP概述

  • AOP的全称是Aspect Oriented Programming,即面向切面编程,在运行期间,不改变源码的情况下动态增强代码,这也就是切面意思的由来。 在这里插入图片描述
  • Spring中的AOP实现底层是动态代理
  • AOP的优点有很多,主要是能够减少重复性代码,或者说,能够在需要的时候实现对代码的增强。
  • AOP的运用场景:
    • 事务处理
    • 性能统计
    • 日志记录
    • 异常处理
    • 等等

9.2 AOP相关术语

  • Joinpoint:连接点,在Spring中,这些点指的是方法,因为Spring只支持方法的连接

  • Pointcut:切入点,指对哪些连接进行拦截,哪些不拦截

    连接点不一定是切入点

  • Advice:通知/增强,即拦截到Joinpoint之后所要做的事情

    通知的类型有:前置通知、后置通知、异常通知、最终通知、环绕通知

  • Introduction:引介,特殊的通知,在不修改类代码的前提下在运行期动态得为类添加一些方法或Field

  • Target:目标对象,被代理的目标对象

  • Weaving:织入,是把增强应用到目标对象来创建新的代理对象的过程

    Spring采用动态代理织入,AspectJ采用编译器织入和类转载期织入

  • Proxy:代理,一个类被AOP织入增强后,所产生的一个结果代理类

  • Aspect:切面,即切入点和通知的结合

9.3 基于XML的AOP

XML各个通知

  • 前置通知before:切入点方法执行之前执行
  • 后置通知after-returning:切入点方法执行之后执行
  • 异常通知after-throwing:切入点方法出现异常时执行
  • 最终通知after:不管有没有出现异常,该方法都会执行
  • 环绕通知around:前置、后置、异常、最终通知的集合
    Spring中的AOP主要通过AspectJ框架来实现的
  1. 那么我们首先需要导入AspectJ框架的依赖
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
  1. 在service层建立一个测试类和方法
public class TransferAccount {
    public void transferAccount(){
        System.out.println("转账$$$$");
    }
}
  1. 建立一个通知方法
public class Logger {
    public void logger(){
        System.out.println("记录!!!!!!!!");
    }
}
  1. 建立applicationContext.xml文件,注意导入约束
<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/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="TransferAccount" class="com.cap.service.TransferAccount" />
    <bean id="Logger" class="com.cap.log.Logger" />

    <aop:config>
        <aop:aspect ref="Logger">
            <aop:pointcut id="logTransferAccount" expression="execution(* com.cap.service.*.*(..))"/>
            <aop:before method="logger" pointcut-ref="logTransferAccount"/>
        </aop:aspect>
    </aop:config>

</beans>
  1. 测试方法
public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        TransferAccount transferAccount = context.getBean(TransferAccount.class);
        transferAccount.transferAccount();
    }
}
  1. 结果得到
记录!!!!!!!!
转账$$$$

可以看到我们前置通知的方法已经织入了
使用后置通知

 <aop:after-returning method="logger" pointcut-ref="logTransferAccount" />

结果得到

转账$$$$
记录!!!!!!!!

使用后置通知

<aop:around method="logger" pointcut-ref="logTransferAccount" />

结果得到:

记录!!!!!!!!

9.4 环绕通知的另一种写法

当配置了环绕通知后,切入点方法没有执行,而通知方法执行了
Spring框架为我们提供了一个接口:ProceedingJoinPoint。
该接口有一个方法proceed(),此方法相当于明确调用切入点方法。
该接口可以作为环绕通知的方法参数,在程序执行时,
Spring框架会为我们提供接口的实现类供我们使用。
在Logger类中添加环绕通知的方法

    public Object aroundLogger(ProceedingJoinPoint pjp){
        Object result = null;
        Object[] args = pjp.getArgs();
        try {
            System.out.println("前置通知:记录!!!!!!!!");
            result = pjp.proceed();
            System.out.println("后置通知:记录!!!!!!!!");
        } catch (Throwable throwable) {
            System.out.println("异常通知:记录!!!!!!!!");
        } finally {
            System.out.println("最终通知:记录!!!!!!!!");
        }
        return result;
    }

修改xml

<aop:around method="aroundLogger" pointcut-ref="logTransferAccount" />

运行测试方法,得到

前置通知:记录!!!!!!!!
转账$$$$
后置通知:记录!!!!!!!!
最终通知:记录!!!!!!!!

9.5 切入点表达式的写法

切入点表达式的写法

切入点表达式的写法
关键字:execution
表达式: 访问修饰符 返回值 包名.包名…包名.类名.方法名(参数列表)
实例:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()

  1. 访问修饰符可以省略
    void com.itheima.service.impl.AccountServiceImpl.saveAccount()
  2. 返回值可以使用通配符,表示任意返回值
    • * com.itheima.service.impl.AccountServiceImpl.saveAccount()
  3. 包名可以使用通配符,表示任意包,有几个包就写几个*.
    • * *.*.*.*.AccountServiceImpl.saveAccount()
  4. 包名可以使用..表示当前包及其子包
    • * *..AccountServiceImpl.saveAccount()
  5. 类名和方法名也可以使用*通配符
    • * *..*.*()
  6. 参数列表
    可以直接写数据类型
    基本类型——直接写名称(int)
    引用类型——包名.类名 (java.lang.String)
    可以使用通配符*表示任意类型,注意这只对有参方法其作用
    可以使用…表示任意参数(无参也可以)
  7. 全通配的方法
    • * *..*.*(..)
      实际开发中切入点表达式的通常写法:切到业务层实现类下的所有方法
    • * com.itheima.service.impl.*.*(..)

9.6 基于注解的AOP

修改元数据配置文件

<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/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="TransferAccount" class="com.cap.service.TransferAccount" />
    <bean id="Logger" class="com.cap.log.Logger" />
    <aop:aspectj-autoproxy />

</beans>
  1. 对Logger改用注解
    注意需要使用@Aspect表明这是一个切面
@Aspect
public class Logger {
    @Before("execution(* com.cap.service.*.*(..))")
    public void logger(){
        System.out.println("记录!!!!!!!!");
    }

通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了

<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class=“true”/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值