第7天学习打卡(Spring:代理模式[AOP底层机制]、AOP面向切面编程)

9.代理模式(AOP底层机制)

为什么要学习代理模式,因为AOP的底层机制就是动态代理!
代理模式:

  • 静态代理
  • 动态代理

image-20210715001651746

9.1静态代理

1.静态代理角色分析

  • 抽象角色:一般使用接口或者抽象类实现
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色;代理真实角色后,一般会做一些附属的操作
  • 客户:使用代理角色来进行一些操作

代码实现

Rent.java 即抽象角色

//抽象角色:租房
public interface Rent {
    public void rent();
}

Host.java即真实角色

//真实角色:房东,房东要出租房子
public class Host implements Rent {
    public void rent(){
        System.out.println("房东要出租房子");
    }
}

Proxy.java即代理角色

//代理角色:中介
public class Proxy implements Rent{

    public Host host;

    public Proxy(){
    }
    public Proxy(Host host) {
        this.host = host;
    }

    //租房
    public void rent(){
        seeHouse();
        //核心业务:房东出租房
        host.rent();
        fare();
    }

    //附加业务:看房
    public void seeHouse(){
        System.out.println("带房客看房");
    }

    //附加业务:收中介费
    public void fare(){
        System.out.println("收中介费");
    }
}

Client.java即客户

public class Client {
    public static void main(String[] args) {
        //真实角色,出租房源
        Host host=new Host();
        //代理角色,代理真实角色
        Proxy proxy = new Proxy(host);

        //客户,找中介租房
        proxy.rent();

    }
}

分析:

在这个过程中,客户直接接触的就是中介,就如同现实生活中的样子,看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活。

2.优缺点

优点:

  • 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情
  • 公共的业务由代理来完成,实现了业务的分工
  • 公共业务发生扩展时变得更加集中和方便

缺点:

  • 当类多了,每个类对应一个代理类;工作量就变大了,开发效率降低

我们想要静态代理的好处,又不想静态代理的缺点,所以,就有了动态代理。

3.进一步理解

同学们练习完毕后,我们再来举一个例子,巩固大家的学习!

练习步骤:

  1. 创建一个抽象角色,比如咱们平时做的用户业务,抽象起来就是增删改查!

    //抽象角色:增删改查业务
    public interface UserService {
        void add();
        void delete();
        void update();
        void query();
    }
    
  2. 我们需要一个真实对象来完成这些增删改查操作

    //真实角色:完成增删改查操作的人
    public class UserServiceImpl implements UserService{
        @Override
        public void add() {
            System.out.println("增加了一个用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除了一个用户");
        }
    
        @Override
        public void update() {
            System.out.println("修改了一个用户");
        }
    
        @Override
        public void query() {
            System.out.println("查询了一个用户");
        }
    }
    
  3. 需求来了,现在我们需要增加一个日志功能,怎么实现?

    • 思路1:在实现类上增加代码(麻烦)
    • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好了
  4. 设置一个代理类来处理日志,代理角色

    //代理角色,在这里面增加日志的实现
    public class UserServiceProxy implements UserService{
        private UserServiceImpl userService;
    
        public UserServiceProxy(UserServiceImpl userService) {
            this.userService = userService;
        }
    
        @Override
        public void add() {
            log("add");
            userService.add();
        }
    
        @Override
        public void delete() {
            log("delete");
            userService.delete();
        }
    
        @Override
        public void update() {
            log("update");
            userService.update();
        }
    
        @Override
        public void query() {
            log("query");
            userService.query();
        }
    
        public void log(String method){
            System.out.println("执行了"+method+"方法");
        }
    }
    
  5. 客户测试访问类

    public class Client2 {
        public static void main(String[] args) {
            //真实业务
            UserServiceImpl userService = new UserServiceImpl();
            //代理业务
            UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
    
            //客户访问代理,实现了真实业务的同时实现日志功能
            userServiceProxy.add();
        }
    }
    

重点理解代理模式的思想:

我们不改变原来代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想

聊聊AOP:纵向开发、横向开发

image-20210715010335710

9.2动态代理

  • 动态代理的角色和静态代理的一样
  • 动态代理的代理类是动态生成的,静态代理的代理类是我们提前写好的
  • 动态代理分为两类:一类是基于接口的动态代理,一类是基于类的动态代理
    • 基于接口的动态代理——JDK动态代理
    • 基于类的动态代理——cglib
    • 现在用的比较多的是javasist来生成动态代理
    • 我们这里使用JDK的原生代码来实现,其余道理都是一样的

AOP的原理就是java的动态代理机制

1. 在java的动态代理机制中,有两个重要的类或接口

一个是InvocationHandler(Interface),另一个是Proxy(Class),这一个类和接口是我们实现动态代理所必须用到的。

首先我们我们先来看看java的API帮助文档是怎么样对这两个类进行描述的:

InvocationHandler接口

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
  • 每个动态代理类都必须要实现InvocationHandler这个接口,InvocationHandler并不是动态代理类,动态代理类是动态生成的

  • 并且每个代理类的实例都管理了一个handler(代理类对象通过InvocationHandler接口关联动态生成的动态代理类)

  • 当我们通过代理对象调用一个方法的时候,这个方法的调用就被转发为由InvacationHandler这个接口的invoke方法来调用。

我们来看看InvocationHandler这个接口的唯一一个方法==invoke方法==:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数

Proxy类

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods. 

Proxy这个类的作用就是用来创建一个代理对象的类。

它提供了许多方法,但是我们用的最多的就是==newProxyInstance这个方法:==

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentExceptionReturns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.】

其接收三个参数,我们来看看这三个参数所代表的含义:
    
loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

这个方法的作用就是得到一个动态的代理对象

2.通过一个实例来看看动态代理是什么样的

抽象方法:首先定义了一个Subject接口,为其声明两个办法

//抽象接口
public interface Subject {
    public void rent();
    public void hello(String str);
}

真实对象:定义了一个类实现了Subject接口,RealSubject类

//真实对象
public class RealSubject implements Subject{
    @Override
    public void rent() {
        System.out.println("I want to rent my house");
    }

    @Override
    public void hello(String str) {
        System.out.println("hello:"+str);
    }
}

动态代理类:我们定义一个动态代理类,前面说到,每一个动态代理类都必须实现InvocationHandler这个接口

//1.动态代理类需要实现InvocationHandler接口
public class DynamicProxy implements InvocationHandler {
    //我们要代理的真实对象
    public Object subject;
    //构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object subject) {
        this.subject = subject;
    }

    //2.动态代理类通过InvocationHandler接口的invoke方法,调用真实对象的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");

        //【关键】调用了真实对象的方法名
        System.out.println("Method:"+method);
        method.invoke(subject,args);

        //在代理真实对象后我们可以添加一些自己的操作
        System.out.println("after rent house");

        return null;
    }

}

Client类

public class Client3 {
    public static void main(String[] args) {
        //1.要代理的真实对象
        Subject realSubject=new RealSubject();

        //2.初始化生成动态代理的所必需的InvocationHandler接口,生成动态代理类
        //我们要代理哪个真实对象,就将该对象传进去(一个Subject接口可以实现多个真实对象:如租房抽象接口可以有无数房东真实对象),最后是通过该真实对象来调用其方法的
        InvocationHandler dynamicProxy = new DynamicProxy(realSubject);

        /*3.
        * 通过Proxy的newProxyInstance方法,传入动态代理类handler,生成实际代理对象;我们来看看其三个参数
        * 第一个参数dynamicProxy.getClass().getClassLoader(),我们只在这里使用dynamicProxy这个类的ClassLoader对象来加载我们的代理对象
        * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实现的接口,表示我要代理的是该真实对象,这样我就能调用这个接口中的方法了
        * 第三个参数dynamicProxy,我们将这个代理对象关联到了上方的InvocationHandler这个对象上
         */
        Subject subject = (Subject) Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), dynamicProxy);


        //4.利用代理对象,调用真实对象的方法和拓展方法
        System.out.println(subject.getClass().getName());
        /*
        com.sun.proxy.$Proxy0
         */
        subject.rent();
        /*
        before rent house
        Method:public abstract void com.kuang.service.Subject.rent()
        I want to rent my house
        after rent house
         */
        subject.hello("world");
        /*
        before rent house
        Method:public abstract void com.kuang.service.Subject.hello(java.lang.String)
        hello:world
        after rent house
         */
    }
}

3.控制台的输出与分析

System.out.println(subject.getClass().getName());
/*
com.sun.proxy.$Proxy0
*/
  • 我们可能会认为返回的代理对象会是Subject类型的对象,或者是InvocationHandler类型对象,可是都不是;

  • 首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?

    原因就在于newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现这组接口。这个时候我们可以将这个代理对象类型强转这组接口中的任意一个接口类型。

    同时我们一定要记住,通过Proxy.newProxyInstance 创建的代理对象 是在JVM运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行时动态生成的一个对象,并且命名方式都是这样,以$开头,proxy为中,最后一个数字表示对象的标号。

  • 假如有多个realSubject1,realSubject2,realSubject3,都实现了Subject接口

    DynamicProxy此时相当于一个工厂

    生产什么还需要Proxy中传入特定真实对象,从而生成相应的代理对象

subject.rent();
/*
before rent house
Method:public abstract void com.kuang.service.Subject.rent()
I want to rent my house
after rent house
*/
  • 这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象 关联到的handler中的invoke方法中去执行。
  • 而我们的这个抽象代理dynamicProxy对象又接收了一个RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用dynamicProxy中的invoke方法去执行
  • 我们可以看到,在真正通过代理对象来调用真实对象的方法时,我们可以在该方法前后添加自己的一些操作。
  • 同时我们看到我们的这个method对象:Method:public abstract void com.kuang.service.Subject.rent() ,正好是我们的Subject接口中的方法,这也就证明了当我通过代理对象来调用方法的时候,其实际上就是委托其关联到的dynamicProxy对象的invoke方法中调用,并不是自己来真实调用,而是通过代理的方式来调用。
subject.hello("world");
/*
before rent house
Method:public abstract void com.kuang.service.Subject.hello(java.lang.String)
hello:world
after rent house
*/

图解

image-20210715025915412

这就是我们的java动态代理机制。

10.AOP

  • AOP是一种思想,是一种横向编程的思想,在不影响原来业务类的情况下,实现业务类的增强

  • 代理模式也是为了:“在不影响原来业务类的强狂下,实现业务类的增强”。所以AOP完全就是基于动态代理的Spring实现。

  • 代理模式本质就是装饰者模式。

10.1什么是AOP?

  • 代理模式本质就是装饰者模式,在不影响原来业务类的情况下,实现业务类的增强
  • AOP就是代理模式在Spring中的实现

AOP(Aspect Oriented Programming)

  • 面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的 同一维护的一种技术。

  • AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式变成的一种衍生范型。

  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

image-20210715130617419

10.2AOP在Spring中的作用

提供声明式事务:允许用户自定义切面

AOP本质是动态代理用Spring实现,名词术语:

  • 横切关注点:对业务类增强的部分,即我们需要关注的部分,如日志、安全、缓存、事务等等…

  • 切面(ASPECT): 增强部分抽象的类

  • 通知(Advice): 增强部分抽象的类中的方法,切面要完成的工作

  • 目标(Target): 抽象接口,用来引用真实对象(动态代理中的InvocationHandler接口,动态代理类代理的是一个接口)

  • 代理(Proxy): 向目标对象应用通知后,创建的代理对象(动态代理中的Proxy类,生成动态代理对象)

  • 切入点(pointCut):切面通知 执行的地点的定义。

  • 连接点(JoinPoint):当采用around横切时,与切入点匹配的执行点。

image-20210715131657466

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice

image-20210715131741277

即AOP 在不改变原有代码的情况下,去增加新的功能。

10.3使用Spring实现AOP三种方式

Spring中使用AOP,需要导入一个依赖包:

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>

方式1.通过Spring中的API实现

  • 增强类实现特定通知接口,即声明横切逻辑

  • 此时beans.xml中无需显式声明切面,只需声明切入点和执行环绕

首先我们编写业务接口和实现类,在com.kuang.service包下

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void search();
}
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加用户");
    }

    @Override
    public void delete() {
        System.out.println("删除用户");
    }

    @Override
    public void update() {
        System.out.println("更新用户");
    }

    @Override
    public void search() {
        System.out.println("查询用户");
    }
}

然后去写我们的增强类,我们编写两个增强类,一个前值增强,一个后置增强,在com.kuang.log包下

//前置增强类
public class Log implements MethodBeforeAdvice {
    //method: 要执行的目标对象的方法
    //args: 被调用的方法的参数
    //target:目标对象(目标对象在代理对象中的接口)

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行了");
    }
}
public class AfterLog implements AfterReturningAdvice {
    @Override
    //returnvalue 返回值
    //method 被调用的方法
    //args 被调用的方法的对象的参数
    //target 被调用的目标对象
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+target.getClass().getName()+"的"+method.getName()+"方法,"+"返回值:"+returnValue);
    }
}

最后去Spring的配置文件beans.xml中注册,并实现aop切入实现,注意导入aop约束

<?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/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-->
    <bean id="userService" class="com.kuang.service.UserServiceImpl"></bean>
    <bean id="log" class="com.kuang.log.Log"></bean>
    <bean id="afterLog" class="com.kuang.log.AfterLog"></bean>

    <!--aop的配置-->
    <aop:config>

        <!--切入点 expression:表达式匹配要执行的方法-->
        <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>

        <!--执行环绕:advice-ref执行方法  pointcut-ref切入点-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

测试:

注意getBean返回的对象需要强转成抽象接口类型,因为增强类代理类实现的也是抽象接口类型

public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

AOP的重要性:很重要,一定要理解其中的思路,主要是思想的理解。

Spring的AOP就是:将公共的业务(如日志、安全)等领域业务结合起来,当执行领域业务时,将会把公共业务加起来,实现公共业务代码的重复利用,领域业务更纯粹;程序猿专注领域业务,其本质还是动态代理。

方式2.自定义增强类来实现AOP(推荐使用)

目标业务类不变,依旧是UserServiceImpl

1.第一步:写我们自己的一个原生切入类,在com.kuang.diy包下

//切入类,增强类
public class DiyPointcut {
    //增强方法
    public void before(){
        System.out.println("===========方法执行前============");
    }

    public void after(){
        System.out.println("===========方法执行后============");
    }
}

2.去Spring中配置

image-20210715143151264

采用自定义增强类,需要显式声明切面

采用Spring中API实现接口,在定义增强类时就实现了通知接口,所以在beans.xml中无需显式声明切面

3.测试代码相同

方式3.使用注解实现

1.编写一个注解实现的增强类,切面和通知 都使用注解在增强类值标注

@Aspect
public class AnnotationPointcut {
    @Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("===========方法执行前============");
    }
    @After("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("===========方法执行后============");
    }

    @Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        //执行原有业务目标方法
        Object proceed = jp.proceed();
        System.out.println("环绕后");

        System.out.println("签名:"+jp.getSignature());
        System.out.println(proceed);

    }
}

2.配置beans.xml,注册bean,并增加支持注解的配置

<!--注册bean-->
<bean id="userService" class="com.kuang.service.UserServiceImpl"></bean>
<bean id="diy" class="com.kuang.diy.AnnotationPointcut"/>

<!--aop的配置,设置支持注解-->
<aop:aspectj-autoproxy/>

<aop:aspectj-autoproxy/>说明:

  • 通过aop命名空间声明,为Spring容器中那些配置@Aspect切面的bean创建代理,植入切面
  • 当然Spring在内部依旧采用AnnotationAwareAspectAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被隐藏。
  • <aop:aspectj-autoproxy/>有一个<aop:aspectj-autoproxy poxy-target-class="true"/>属性,默认为false,表示使用jdk动态 代理织入增强;为true时表示使用 CGLib动态代理技术植入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接 口,则spring将自动使用CGLib动态代理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值