【Spring——AOP编程】

一、代理设计模式

1、为什么需要代理设计模式?

我们思考一个问题,在传统的分层开发过程中,我们最重要的层次是Service层,那么Service层一般都写什么代码呢?

Service层  = 核心代码 + 额外功能。

那么此时我们考虑一个问题,额外功能书写在Service层到底好不好,我们需要从两个角度去探讨:

①、Service层调用者的角度:需要在Service层当中书写额外功能。

②、软件设计者:Service层不需要额外功能。(保证类的单一性职责)

那这个额外功能怎么办?从两个角度去看,各有各的理由,此时我们就可以换个思路去解决,可不可以借用一个第三方,让第三方衔接在上层调用者和设计者之间去做这个额外功能,上层调用者依然享用额外功能、设计者不需要书写额外功能。这就是我们所讨论的代理设计模式。

2、什么是代理设计模式?

2.1、概念

所谓代理设计模式,就是通过代理类,为原始类增加额外功能。

2.2、名词解释

①、原始类:指的是业务类,(核心功能——》业务运算,DAO层调用),也叫目标类。

②、原始方法:指的是原始类当中的方法,就是原始方法,也叫目标方法。

③、额外功能:指的是附加功能,非核心功能,比如日志、事务、性能测试等功能。

2.3、代理开发三要素

代理类 = 额外功能 + 目标类 + 和目标类实现相同的接口

2.4、代理设计模式分类

代理设计模式分为:静态代理设计模式 、 动态代理设计模式

二、静态代理设计模式

1、静态代理设计模式编码实现

①、准备阶段

User类

public class User {
    private String name;
    private String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

UserService接口

public interface UserService {
    public void login(String name,String password);

    public void register(User user);
}

 原始类

public class UserServiceImpl implements UserService{

    @Override
    public void login(String name, String password) {
        System.out.println("name = " + name + " password = " + password);
    }

    @Override
    public void register(User user) {
        System.out.println(user.toString());
    }
}

 ②、代理类开发

public class UserServiceProxy implements UserService{

    UserService userService = new UserServiceImpl();

    @Override
    public void login(String name, String password) {
        System.out.println("执行日志功能");
        userService.login(name,password);
    }

    @Override
    public void register(User user) {
        System.out.println("执行日志功能");
        userService.register(user);
    }
}

 ③、调用

public class test {
    public static void main(String[] args) {
        UserService userService = new UserServiceProxy();
        userService.login("大靓仔","123456");
        userService.register(new User("大美女","654321"));
    }
}

 ④、结果验证

【Spring——AOP编程】_第1张图片

2、静态代理设计模式思路实现

 我们知道了的代理设计模式的三要素,原始类+额外功能+代理类和原始类实现相同的接口。

那么我们接上来对这三个要素逐一分析。

①、原始类:毋庸置疑,就是实现接口的类,此类当中实现了核心功能的编写。

②、代理类:代理类当中,把原始类的实例对象当做本类的成员变量进行定义(通过new关键字),和原始类实现相同的接口,重写相同的方法,在代理类当中的方法书写额外功能,此外,还需要调用原始类的实例对象的相应方法去执行。

③、为什么要实现相同的接口?

这个是很重要的问题,举个例子:

假设A包租婆有N套北京的房产,那咱们的小富婆天天去电线杆子上贴小广告,说,“啊,某某某高档小区,有N室N厅房屋出租,联系电话:15660026750”,好,这个小富婆要干的第一件事,第二件事,某同学刚来北京北漂,嘿,电线杆上看到了小富婆贴的房屋出租广告,就给小富婆打电话了,说“喂,我是大靓仔,听说你这有房出租,可以看看嘛”,然后说完小富婆就带着大靓仔去看房了,看房又是一件事,看完了满意再签出租合同,一系列的操作下来可算是出租了一套,这把小富婆累的呀,小富婆的核心事件只有一个,那就是签合同收钱,其余的都是在为这件事做准备,那小富婆合计着太累了,消瘦了,不高兴了,好,那我拿出百分之十给中介去干其余的事,我只负责核心的一件事,那就是签合同收钱。好了,贴小广告、带客户看房、推销等等一系列问题都不用小富婆去干了。

这个例子当中,小富婆就是我们所说的原始类,中介就是代理类,而客户就是上层调用者。

三者的关系不言而喻,这个时候,就回到我们最初的问题,为什么要实现相同的接口?为的就是迷惑上层调用者(客户),让上层调用者感知不到代理类(中介)的存在,上层调用者只是为了达成租房这一件事,其中租房的各种核心流程(原始类)和不核心流程(代理类)我都必须走一遍(安心),直到签合同给钱这一步为止,都是中介在搞,上层调用者关心是谁搞得嘛?并不关心,我只关心我这个流程是否都走过。这样,就完成了额外功能+核心功能的处理,上层调用者(客户)只是知道了我和房东签订了租房合约,这个过程看似是房东全程处理的,实际上中介负责了非核心功能的处理。

这就是为什么要实现相同的接口(中介和小富婆在上层调用者看来,都是房东),为的就是迷惑上层调用者!

3、静态代理设计模式开发存在的缺点

这个缺点显而易见,存在太多代码冗余,我们上面的代码只有两个方法,为每个代理类当中的方法都添加日志功能,就得在每个方法当中编写日志代码。

还有,这里只有一个原始类,后续的项目啊开发过程中,成百上千的原始类,那岂不是要在原来的基础上编写2倍的类?

后续的项目维护过程中,如果我对这个日志功能不满意了,我是不是要一个一个方法去改他们的日志功能?

显然,这是不可能的,那聪明的程序猿就用到了动态代理设计模式,解决了代码冗余、项目不易管理、额外功能难维护的问题。

三、动态代理开发

动态代理和静态代理的目标是一致的,这点我们毋庸置疑,那么具体实现区别在哪里?我们接着往下看:

1、环境搭建

引入jar包


 org.springframework
 spring-aop
 5.1.14.RELEASE
 
 org.aspectj
 aspectjrt
 1.8.8
 
 org.aspectj
 aspectjweaver
 1.8.3

 2、开发步骤

依然是为UserServiceImpl创建实例对象(原始对象),并为原始对象增加一些额外功能。

2.1、准备阶段

接口

public interface UserService {
    public void login(String name,String password);

    public void register(User user);
}

原始类

public class UserServiceImpl implements UserService {

    @Override
    public void login(String name, String password) {
        System.out.println("name = " + name + " password = " + password);
    }

    @Override
    public void register(User user) {
        System.out.println(user.toString());
    }
}

 交由Spring工厂进行创建

 2.2、代理类开发

 额外功能的编写

public class Before implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("实现MethodBeforeAdvice接口之后实现的日志功能");
    }
}

在Spring工厂当中创建实现额外功能的实例对象

 定义切入点

这块我们就有了一个疑问,什么是切入点?所谓切入点,就是将该额外功能的加入位置,加给哪个方法。

    
        
    

这里的id值可以随意起名,expression当中的内容是切入点表达式。

组装

 组装是组装什么?将切入点和额外功能组装,组成切面,表示这个额外功能都要加给哪些方法。

 advice-ref表示使用哪个额外功能;

pointcut-ref表示使用哪个切入点。

 这个标签同样是在配置文件的标签内部编写。

调用

public class test {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("大靓仔","123456");
        userService.register(new User("大美女","654321"));
    }
}

 验证结果

【Spring——AOP编程】_第2张图片

可以看到login方法和register方法都在原始方法的基础上增加了额外功能。

 3、细节分析

3.1、Spring工厂创建的对象是什么对象?

获取Spring工厂创建的对象时,创建的是代理对象,而非原始对象。

3.2、如何获取代理对象?

上面我们说到的代理模式三要素,第三要素就解决了我们面临的问题,代理对象和原始对象时实现了相同的接口的,因此,我们可以通过声明接口类型来获取代理对象。

3.3、动态代理的好处有哪些?

①、解决了代码冗余

一个日志额外功能,我们只需要写一次代码,定义切入点之后,想让哪个方法有这个额外功能就让哪个方法拥有。

②、解决了代码难维护的问题

如果日后我们不想使用这个额外功能了,想要替换一种日志功能,可以直接写一个新的日志代理类即可,切入点不变,只改变额外功能的实现类即可。

③、减少了类的个数

在静态代理开发过程中,我们需要为每一个原始类创建一个代理类,进而去实现额外功能,此处显然是不需要的,N个原始类,只需要N+1个类即可完成目标。

四、动态代理的底层技术是什么??

我们首先考虑一个问题,在我们以往的开发过程中,如何创建对象?第一步,编写.java源文件;第二步,编译器编译.java文件,使之成为.class文件;第三步,JVM给该.class文件分配一个类加载器,类加载器加载.class文件,在JVM内部创建Class对象;第四步,使用new关键字或者其他方式创建对象。那么问题来了,动态代理对象有.java源文件嘛?显然上面我们并没有编写动态代理类的源文件,更不用说.class文件了,那么JVM内部的Class对象从何而来????

牛逼之处就在于“动态字节码技术”!!!

1、什么是动态字节码技术??

概念:通过第三个动态字节码框架,在JVM中创建对应类的字节码,进⽽创建对象,当虚拟机结束,动态字节码跟着消失。

2、动态字节码技术有何作用?

动态代理不需要定义类⽂件,都是JVM运⾏过程中动态创建的,所以不会造成静态代理,类⽂件数量过多,影响项⽬管理的问题。

五、动态代理实现方式详解

1、额外功能使用详解

1.1、MethodBeforeAdvice接口

我们上面的动态代理实现过程,是通过实现MethodBeforeAdvice接口,重写其中的Before方法来书写额外功能的。

Before方法的参数都分别代表什么?

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        额外功能
    }

 Method:表示此方法内的额外功能需要增加给哪个方法

Object【】:额外功能所要增加给的原始方法的参数列表

Object:额外功能增加给的原始对象

我们要调用一个方法,我们需要知道方法所属对象(Object)、方法名(Method)、方法参数(如果存在重载,就需要知道参数列表——》Object【】)

考虑一个问题,Before方法只会在原始方法执行之前执行,那如果我们的需求是要在原始方法执行之后执行额外功能呢?这就是Before方法完成不了的功能了。这就需要我们的MethodInterceptor接口来实现了~

1.2、MethodInterceptor(方法拦截器)

使用MethodInterceptor如何增加额外功能?我们先来看看具体的开发步骤:

  • 创建原始对象(目标对象),和普通创建方式相同
  • 额外功能编写(实现MethodInterceptor接口)实现MethodInterceptor接口之后重写接口当中提供的invoke方法,在invoke方法内部编写额外功能。和MethodBeforeAdvice接口不同的是,invoke方法中编写的额外功能可以在原始方法的前、后、前后、抛出异常时执行,更加灵活。

具体实现:

①、在原始方法执行之前执行:

@Override

public Object invoke(MethodInvocation invocation) throws

Throwable {

System.out.println("-----额外功能 log----");

Object ret = invocation.proceed();

return ret;

}

}

②、在原始方法执行之后执行: 

@Override

public Object invoke(MethodInvocation invocation) throws Throwable

{

Object ret = invocation.proceed();

System.out.println("-----额外功能运⾏在原始⽅法执⾏之后----");

return ret;

}

 ③、在原始方法执行前后执行:

@Override

public Object invoke(MethodInvocation invocation) throws Throwable

{

System.out.println("-----额外功能运⾏在原始⽅法执⾏之前----");

Object ret = invocation.proceed();

System.out.println("-----额外功能运⾏在原始⽅法执⾏之后----");

return ret;

}

 ④、在原始方法抛出异常时执行:

@Override

public Object invoke(MethodInvocation invocation) throws Throwable

{

Object ret = null;

try {

ret = invocation.proceed();

} catch (Throwable throwable) {

System.out.println("-----原始⽅法抛出异常 执⾏的额外功能 ---- ");

throwable.printStackTrace();

}

return ret;

}

invoke方法参数MethodInvocation (Method):额外功能所增加给的那个原始⽅法

Invocation.proceed()方法表示执行原始方法,在合适的位置编写额外功能即可。

注意点:invoke方法的返回值可以返回原始方法的返回值,也可以自定义返回值,这是MethodBeforeAdvice接口当中Before方法所做不到的。

2、动态代理实现方式的区别

2.1、方法参数

Before方法:Method、Object【】、Object

invoke方法:MethodInvocation

2.2、返回值

Before方法:void

invoke方法:Object

2.3、是否可以影响返回值

invoke方法的返回值可以是原始方法的返回值,也可以程序猿自己定义的返回值。这是Before方法做不到的。

六、AOP编程

1、什么是AOP编程

AOP(Aspect Oriented Programing)编程 = 面向切面编程,回顾我们以往学过的,面向对象编程(典型代表Java)、面向过程编程(典型代表C)。

所谓面向切面编程,是在面向对象编程的基础上演变而来的,以切面为基本单位的程序开发,通过切面间的彼此协同、相互调用,完成程序的构建。

切面 = 切入点 + 额外功能。

在我们以往的开发过程中,可能会存在很多个Service类,每个类实现各自不同的功能,类中的每个方法又有各自的功能,面向切面编程,就是对这个类共性的一种抽取,比如UserService类当中的login方法需要添加日志功能,ArticleService类当中的add方法也需要日志功能,把每个类当中的某个方法抽取出来,和其他类当中实现相同额外功能的方法之间形成切面,这就是面向切面编程的核心主旨。

如图所示:

【Spring——AOP编程】_第3张图片

2、AOP编程底层实现原理

AOP编程的底层实现主要存在两种方法,一种是JDK为我们提供的,一种是Spring为我们提供的。

2.1、JDK(JDK提供)

①、开发步骤

public class test1 {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();//创建原始对象
        //创建handler对象
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("执行日志功能——》");
               Object ret =  method.invoke(userService,args);
                System.out.println("执行功能测试时间功能——》");
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                System.out.println("执行时间 = " + dateFormat.format(new Date()));
                System.out.println();
                return ret;
            }
        };
        UserService userService1 = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(),handler);
        userService1.login("朱意芃","123456");
        userService1.register(new User("小豆","123456"));
    }
}

 ②、执行结果

【Spring——AOP编程】_第4张图片

 ③、细节分析

I、Proxy.newProxyInstance类说明

使用Proxy类调用其newProxyInstance方法创建代理对象。

II、参数详解

newProxyInstance方法有三个参数,分别是ClassLoader、interfaces、InvacationHandler

ClassLoader(类加载器):

为什么要是用类加载器?这是我们首先要思考的问题,对于一个类,我们知道,要想创建一个类的实体对象,那么首先必须要有这个类的Class对象,那么这个Class对象从何而来?我们一般写的代码xxx.java经过编译之后生成.class文件,.class文件内部实际上就是一些字节码,JVM内部会为这个.class文件分配一个classLoader(类加载器)将这个.class文件加载到JVM内存当中变成这个类的Class对象,因为是采用动态字节码技术,因此就没有xxx.java源文件,更不会有.class文件,因此,JVM内部就无法为这个字节码生成一个Class对象,所以我们就需要一个类加载器将这个动态字节码技术生成的字节码加载到JVM当中生成一个Class对象,因此就需要借一个类加载器来实现(classLoader)。

借用谁的类加载器?谁的都可以了,类加载器的本质工作是通过.class文件变成Class对象,并不对这个.class文件具有其他影响,因此,我们借用其他类的加载器并不影响到其余功能。

Interfaces(和原始类相同的接口):

我们说创建代理对象需要三个要素,这个interfaces参数代表的是实现哪个接口。因为要实现相同的接口(第三个要素),所以就需要指明这个代理对象实现的是哪个接口。此处我们为UserServiceImpl创建代理对象,可以通过userServiceImpl.getClass()方法获得这个类的全部信息,进而通过getInterfaces()方法获取它所实现的接口。

InvocationHandler(额外功能 + 原始方法):

这个参数所表示的含义就是额外功能和原始对象的结合。此处采用内部类的方法区实现这个接口,因为是接口,所以不能实例化,只能通过内部类的方式来创建实现这个接口的类的实例对象。

为什么要实现这个接口?我们说到的代理对象的三要素当中,第一要素和第二要素我们目前并没有解决,因此,就需要通过这个接口的实现类来解决这两个因素,实现InvocationHandler接口之后,重写他的invoke方法。invoke方法的功能就是书写额外功能。Invoke方法同样具有三个参数,分别代表——》Proxy=代理对象(此处我们忽略不讨论)、Method=额外功能所要增加给哪个原始方法、args【】数组=方法的参数。

在invoke方法当中书写额外功能的代码,同时执行原始方法,问题来了,如何让原始方法执行呢?我们知道,要执行一个方法,要知道这个方法属于哪个对象、方法名、方法参数列表,此处方法名和参数列表有了,那么方法所属对象是谁?

显而易见是userService,调用method的invoke方法,传递参数即可。这样就可以执行额外功能+原始方法了。

Method.invoke(userService,args) == userService.xxx(参数列表)

注意:MethodInterceptor接口和此处的InvocationHandler接口的重写方法极其相似,区别就在于invoke方法的参数不同,从而导致了原始方法的运行方式不同。这两个接口相似的原因是由于Methodinterceptor是Spring为我们提供的接口,而InvocationHandler接口是JDK为我们提供的,Spring为我们对原始方法的执行进行了封装,直接调用invocation.proceed()的方法即可让原始方法执行。

2.2、Cglib

①、开发步骤

public class test {
    public static void main(String[] args) {
        UserService userService = new UserService();

        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(test.class.getClassLoader());
        enhancer.setSuperclass(userService.getClass());

        MethodInterceptor interceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("执行Cglib的日志功能");
                Object ret =  method.invoke(userService,args);
                System.out.println("测试执行时间");
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-HH-mm HH:mm:ss");
                String date = simpleDateFormat.format(new Date());
                System.out.println("Cglib动态代理执行时间 : " + date);
                System.out.println();
                return ret;
            }
        };

        enhancer.setCallback(interceptor);

        UserService userServiceProxy = (UserService) enhancer.create();
        userServiceProxy.login("朱意芃","123456");
        userServiceProxy.register(new User("小豆","654321"));
    }
}

②、执行结果

 【Spring——AOP编程】_第5张图片

③、细节分析 

我们说动态代理需要三要素,额外功能 + 原始对象 + 和原始对象实现相同的接口。

此时我们考虑一个问题,如果该原始对象并没有声明哪个接口,那么第三要素的意义何在?

我们在之前的开发步骤当中,为什么要代理对象和原始对象实现相同的接口呢?为的就是迷惑上层调用者,让调用者感知不到我们是代理对象,那么如果原始对象并没有实现任何接口该怎么办呢?这就是Cglib需要解决的问题,我们可以通过继承来实现这个问题,继承原始对象的类,从而重写其中的方法,在重写方法当中增加额外功能,这样通过父类来定义代理对象即可。

I、Enhancer类说明

Enhancer,中文翻译为“增强”,什么是增强?我们中文的理解,是在原有基础上的一种附加。附加什么?当然是额外功能咯。Enhancer类是Spring为我们提供的。

II、参数详解

  • setClassLoader(设置类加载器),这个原理和JDK动态代理的原理相同
  • SetSuperClass(定义父类),表明我们的代理对象的父类是哪个,后续的获取代理对象也是可以通过这个父类来接收。
  • SetCallBack(定义额外功能+原始方法的运行),这个方法需要传递参数,参数和JDK动态代理当中的invocationHandler的效果基本类似,区别在于,创建的内部类实现的接口为MethodIntercpter,重写方法intercept,在intercept方法内部写额外功能+原始对象的运行。

 III、intercept方法参数详解

Method:原始方法

Object【】:原始方法参数

2.3、JDK动态代理和Cglib动态代理区别

①、处理场景不同

JDK应对的场景是原始类实现了某个接口,创建的代理对象通过声明接口类型来获取;而Cglib应对的场景是原始类没有实现接口,将原始类当做父类,基于继承关系的获取,通过声明父类(原始类)来获取创建的代理对象(继承自原始类,实现了父类的方法,在原有方法的基础上增加额外功能)。

②、机制不同

JDK是基于实现接口实现的;Cglib是基于继承实现的。

③、参数不同

第二个参数:JDK是interfaces,表示代理对象所要实现的接口,而Cglib是SuperClass,表示其父类是谁(原始对象)。

第三个参数:JDK是InvocationHandler,这个接口是JDK提供的。而Cglib是MethodInterceptor,此处的MethodInterceptor和上面讲到的MethodInterceptor不是同一个接口。

3、Spring工厂如何对原始对象进行加工

我们上面说完了如何创建动态代理对象,那么Spring工厂内部如何对原始对象进行加工呢?

我们首先来回顾一个知识,后置处理Bean,后置处理Bean是对Bean对象的进一步加工,重写Before方法和After方法,分别在Bean初始化操作之前执行和Bean初始化操作之后执行,一般我们的额外功能是在After方法当中执行,具体开发如下:

public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("在After方法当中处理Bean,执行额外功能");
                Object ret = method.invoke(bean,args);
                return ret;
            }
        };

        return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),handler);
    }
}

配置文件中配置:

    

    

获取调用:

public class test {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        com.zyp.Bean.UserService userService = (com.zyp.Bean.UserService) ctx.getBean("userService");

        userService.login("大靓仔","123456");
        userService.register(new User("大美女","654321"));
    }
}

 验证结果:

【Spring——AOP编程】_第6张图片

过程分析:当我们实现BeanPostProcessor接口之后,重写当中的After方法,在After方法当中去创建动态代理对象,创建完成后将这个动态代理对象作为After方法的返回值返回,此时Spring工厂当中根据这个Id获取到的对象,是经过After方法加工之后生成的代理对象,而非原始对象,这样,我们去Spring工厂根据ID获取对象时,就拿到的是代理对象,通过声明接口类型去获取这个代理对象。

 4、基于注解的AOP开发编程

4.1、开发步骤

①、原始对象

②、额外功能

③、切入点

④、组装切面

@Aspect
public class MyAspect {

    @Around("execution(* login(..))")
    public Object arround(ProceedingJoinPoint joinPoint){
        System.out.println("基于注解的AOP开发实现额外功能");
        Object ret = null;
        try {
            ret = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return ret;
    }
}

 4.2、分析:

①、使用@Aspect注解标记这是一个切面类

②、方法名可以是任意方法名,返回值必须是Object,方法内部编写额外功能+原始方法的运行

③、ProceedingJointPoint参数类比实现MethodInterceptor接口时的invoke方法参数,两者效果上是等效的。调用jointPoint.proceed()即可运行原始方法。

④、@Arround注解是标注这个一个切面,后面括号中的内容表示切入点表达式

⑤、一个切面类当中可以有很多切面,可以实现不同的功能。

针对⑤,我们考虑一个问题,假设我们此时有很多切面,某些切入点是相同的,我们是不是需要反复的去写切入点呢?显然,这样的做法可以是可以,就是太麻烦了。因此就有了另外一种方式——》切入点复用!

4.3、切入点复用

@Aspect
public class MyAspect {

    @Pointcut("execution(* login(..))")
    public void myPointCut(){}

    @Around(value = "myPointCut()")
    public Object arround(ProceedingJoinPoint joinPoint){
        System.out.println("基于注解的AOP开发实现额外功能");
        Object ret = null;
        try {
            ret = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return ret;
    }

    @Around(value = "myPointCut()")
    public Object arround1(ProceedingJoinPoint joinPoint){
        System.out.println("切入点复用实现额外功能");
        Object ret = null;
        try {
            ret = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return ret;
    }
}

 此时我们看到,我们新增了一个额外功能和一个方法,使用了@Pointcut注解,这个注解就可以让我们的切入点复用,在@Pointcut注解后面写上切入点表达式,其方法的返回值必须是void,方法名不做限制,方法内部不能有具体的实现。

这样,我们实现额外功能的方法要想使用这个切入点时,只需要在括号当中指定value属性的值,一般为切入点方法名即可。

4.4、验证结果

    

    
    
public class test2 {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("大靓仔","123456");
        userService.register(new User("大美女","654321"));
    }
}

 【Spring——AOP编程】_第7张图片

 配置文件分析: 

 ①、使用标签标注告知Spring我们使用注解的形式进行AOP编程开发。

②、proxy-target-class是什么意思呢?这个我们上面说到了AOP编程的底层原始是动态字节码技术,基于这种技术衍生出来两种开发方式,一种是JDK动态代理,一种是Cglib动态代理,这个属性的默认值为false,表名我们底层采用JDK动态代理,设置为true表示我们采用Cglib动态代理。下来我们来印证这个观点。

【Spring——AOP编程】_第8张图片

改为True之后我们再来看看

 七、AOP编程开发过程当中的坑

我们以一段代码为例来看这个坑:

代码准备:

        原始对象:

public class UserService2 implements UserService{
    @Override
    public void login(String name, String password) {
        System.out.println("name = " + name + " password = " + password);
        this.register(new User(name,password));
    }

    @Override
    public void register(User user) {
        System.out.println(user.toString());
    }
}

在login方法当中调用register方法! 

        代理对象:

public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("在After方法当中处理Bean,执行额外功能");
                Object ret = method.invoke(bean,args);
                return ret;
            }
        };

        return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),handler);
    }
}

        配置文件:

    

    

结果验证:

结果分析:这是嘛情况?只有login方法执行了额外功能,register方法的额外功能呢?这是咋回事?我们来捋一捋,我们在Spring工厂创建了原始对象,然后通过BeanPostProcessor接口当中的After方法进行加工,在After方法当中进行代理对象的创建,我们的handler拿到的是什么?拿到的是原始对象,此时对象还未被加工成代理对象,在invoke方法当中才是附加了额外功能,那这个附加功能加给了谁?显然是login,login方法执行时即便的调用register方法,那也是原始对象的register方法,那要怎么调用代理对象的register方法呢?下面来看操作:

public class UserService2 implements UserService, ApplicationContextAware {

    private ApplicationContext ctx;
    @Override
    public void login(String name, String password) {
        System.out.println("name = " + name + " password = " + password);
        UserService userService = (UserService) ctx.getBean("userService2");
        userService.register(new User(name,password));
    }

    @Override
    public void register(User user) {
        System.out.println(user.toString());
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }
}

 这段代码和上面的代码对比,显然我们发现多实现了一个接口,这个接口是干什么的?获取Spring工厂的,在这个工厂当中获取工厂创建的代理对象,进而调用代理对象的register方法。结果如图:

【Spring——AOP编程】_第9张图片

 因为Spring工厂是重量级资源,因此我们尽可能的避免工厂的创建,实现ApplicationContextAware接口,就可以获取工厂,将工厂当做成员变量赋值给ctx,进而通过ctx去获取工厂当中创建的对象。

你可能感兴趣的:(面试八股文,spring,java,后端)