AOP介绍

时间:2019-05-31 12:50来源:计算机教程
  三、AOP中的"成员"介绍 连接点(JoinPoint)   在AOP表示为"在哪里做",它用来定义在程序的什么地方能够通过AOP加入额外的逻辑。 切入点(Pointcut)   在AOP表示为"在哪里做的集合"。通过

 

三、AOP中的"成员"介绍

连接点(JoinPoint)
  在AOP表示为"在哪里做",它用来定义在程序的什么地方能够通过AOP加入额外的逻辑。

切入点(Pointcut)
  在AOP表示为"在哪里做的集合"。通过创建切入点,我们可以精确地控制程序中什么组件接到什么通知,而一个典型的切入点就是对某一个类的所有方法调用的集合。

通知、增加(Advice)
  在AOP表示为"做什么"。增强即为在连接点上执行的行为,包括前置增强、后置增强、环绕增强。

方面/切面(Aspect)
  在AOP表示为"在哪里做和做什么的集合"。切面是通知和切点的集合,通知和切点共同定义了切面的全部功能——它是什么,在何时何处完成其功能。

顾问(Advisor)
  在Spring中,一个advisor就是一个aspect的完整的模块化标识。一般地,一个advisor包括通知和切入点,是通知和切入点的配置器。

目标对象(Target Object)
  在AOP表示为"对谁做"。目标对象是需要被织入横切关注点的对象,也就是切入点选择的对象、需要被增强的对象。由于Spring AOP通过代理模式实现,从而这个对象永远是被代理对象。

AOP代理(AOP Proxy)
  AOP框架使用代理模式创建的对象,从而实现在连接点处插入增加。Spring中,可以通过JDK动态代理或者CGLIB代理实现AOP代理,通过拦截器模型应用切面

织入(Weaving)
  织入是一个过程,是将切面运用到目标对象从而创建出AOP代理对象的过程。织入是在编译时完成的,它通常是作为编译过程中的一个额外的步骤。

引入(introduction)
  在AOP表示为"做什么(新增什么)"。引入也称为内部类型声明,为已有的类添加额外新的字段或方法。

 

一、AOP是什么

AOP,即面向切面编程。AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制、异常处理等,封装起来,便于减少系统重复的代码,降低模块之间的耦合度。
  AOP注重的是许多解决问题的方法中的共同点。

图片 1

AOP

 

二、AOP的实现

AOP的实现可以分为两种:1.静态织入 2.动态代理

(1)静态AOP
  静态织入就是在编译期,切面直接以字节码的形式编译到目标字节码文件中。这种方式对系统的性能没有影响,但是灵活性不够。

(2)动态AOP

为了实现源码组成无关性,AOP往往通过预编译方式(如AspectJ)和运行期动态代理模式(如Spring AOP)实现。

spring动态AOP有两种方式:

  • JDK动态代理:通过反射和动态编译实现
  • cglib动态代理:通过修改字节码来实现代理

这里我们主要讨论JDK动态代理的方式。
  JDK动态代理的两个核心就是InvokationHandler和Proxy,下面我用一个简单的例子来说明JDK动态代理的实现。
  由于JDK动态代理只能创建指定接口的动态代理,所以下面先提供一个Dog接口:

interface Dog {  
    // info()方法声明  
    public void info();  

    // run()方法声明  
    public void run();  
}  

然后为接口Dog创建一个实现方法:

class GunDog implements Dog {  
    // info方法实现,仅仅打印一个字符串  
    @Override  
    public void info() {  
        System.out.println("我是一只猎狗");  
    }  

    // run方法实现,仅仅打印一个字符串  
    @Override  
    public void run() {  
        System.out.println("我奔跑迅速");  
    }  
}  

在DogUtil中创建两个通用的方法(公共代码):

class DogUtil {  
    // 第一个拦截器方法  
    public void method1() {  
        System.out.println("-----------模拟第一个通用方法-----------");  
    }  

    // 第二个拦截器方法  
    public void method2() {  
        System.out.println("-----------模拟第二个通用方法-----------");  
    }  
}  

假设info()和run()代表两个要调用公共代码的方法,但是不能以硬编码的方式来进行调用,应该怎么办呢?可以借助Proxy和InvocationHandler来实现:当程序调用info()和run()方法时,系统将会"自动"将method1()和method2()两个通用方法插入到info()和run()方法。

class MyInvokationHandlerPro implements InvocationHandler {  
    // 需要被代理的对象  
    private Object target;  

    public void setTarget(Object target) {  
        this.target = target;  
    }  

    // 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法  
    public Object invoke(Object proxy, Method method, Object[] args)  
            throws Exception {  
        DogUtil du = new DogUtil();  
        // 执行DogUtil对象中method1方法  
        du.method1();  
        // 以target作为主调来执行method方法  
        Object result = method.invoke(target, args);  
        // 执行DogUtil对象中method2方法  
        du.method2();  
        return result;  
    }  
}  

上面程序中的类是InvokationHandler的一个实现类,该实现类的invoke方法将会作为代理的方法实现,其中invoke方法包含了一行关键的代码,这行代码以target作为主调通过反射来执行method方法,这就实现了target对象的原有方法。

有了代理处理类以及需要被代理的对象target,我们还需要一段代码为指定的target生成动态代理:

class MyProxyFactory {  
    // 为指定target生成动态代理对象  
    public static Object getProxy(Object target) throws Exception {  
        // 创建一个MyInvokationHandler对象  
        MyInvokationHandlerPro handler = new MyInvokationHandlerPro();  
        // 为MyInvokationHandler设置target对象  
        handler.setTarget(target);  
        // 创建,并返回一个动态代理  
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
                target.getClass().getInterfaces(), handler);  
    }  
}  

上面的类是一个动态代理工厂类,它提供了一个getProxy方法,为target对象生成了一个动态代理对象。这个动态代理实现了与target相同的接口,所以动态代理对象可以当成target对象来使用。通俗一点说,就像为明星忙碌的经纪人,他们就是活生生的代理,帮助明星打理事务。
  当程序调用动态代理对象的指定方法的时候,实际上就是执行MyInvokationHandlerPro 的invoke方法。
  下面提供主程序来测试这种动态代理的效果:

public class TestProxy {  
    public static void main(String[] args) throws Exception {  
        // 创建一个原始的GunDog对象,作为target  
        Dog target = new GunDog();  
        // 以指定的target来创建动态代理  
        Dog dog = (Dog) MyProxyFactory.getProxy(target);  
        dog.info();  
        dog.run();  
    }  
}  

程序的执行结果为:

-----------模拟第一个通用方法-----------
我是一只猎狗
-----------模拟第二个通用方法-----------
-----------模拟第一个通用方法-----------
我奔跑迅速
-----------模拟第二个通用方法-----------

上面程序中的dog对象实际上就是动态代理的对象,只是该动态代理对象也实现Dog接口,所以也可以当成Dog对象使用。
  这种动态代理在AOP被称为AOP代理。AOP代理可以替代目标对象,AOP代理包含了目标对象的全部方法。但是AOP代理中的方法与目标对象的方法存在差异:AOP代理里面的方法可以在执行目标方法之前,插入一些通用处理。

  1. AOP(Aspect-Oriented Programming, style="color: #ff0000;">面向切面编程): 是一种新的方法论,

    是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充.。

  2. AOP 的主要编程对象是 style="color: #ff0000;">切面(aspect),

    而切面模块化横切关注点.。

  3. 在应用 AOP 编程时, 仍然需要定义通用的系统功能,

    但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里。

四、AOP开发步骤

图片 2

AOP基本运行流程

1.功能横切:找出横切关注点
2.分离关注点:各自独立地实现这些横切关注点所需要完成的功能。
3.根据切入点匹配Target中的方法
4.根据AOP的配置寻找对应的Advice,然后进行重组/织入/结合。

  代理:

 JDK动态代理

 

JDK内置的Proxy动态代理可以在运行时动态生成字节码,而没必要针对每个类编写代理类。中间主要使用到了一个接口InvocationHandler与Proxy.newProxyInstance静态方法。

 使用内置的Proxy(JDK动态代理)实现动态代理有一个问题:被代理的类必须实现接口,未实现接口则没办法完成动态代理。

如果项目中有些类没有实现接口,则不应该为了实现动态代理而刻意去抽出一些没有实例意义的接口,通过cglib可以解决该问题。

 

 

    1.  创建maven工程并解决jdk版本及web.xml问题

    2.  导入jar包

  <properties>
      <spring-version>4.2.4.RELEASE</spring-version>
  </properties>

   <dependencies>
       <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-context</artifactId>
           <version>${spring-version}</version>
       </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-core</artifactId>
           <version>${spring-version}</version>
       </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-beans</artifactId>
           <version>${spring-version}</version>
       </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-expression</artifactId>
           <version>${spring-version}</version>
       </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-web</artifactId>
           <version>${spring-version}</version>
       </dependency>
       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>javax.servlet-api</artifactId>
           <version>3.1.0</version>
           <scope>provided</scope>
       </dependency>    
       <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring-version}</version>
        <scope>test</scope>
    </dependency>    
    <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>1.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.6.8</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring-version}</version>
    </dependency>    
   </dependencies>
   <build>
          <plugins>
          <!-- 设置jdk的编译版本 -->
            <plugin>  
                        <groupId>org.apache.maven.plugins</groupId>  
                        <artifactId>maven-compiler-plugin</artifactId>  
                        <version>3.1</version>  
                        <configuration>  
                            <source>1.8</source>  
                            <target>1.8</target> 
                            <encoding>utf-8</encoding> 
                        </configuration>  
                    </plugin>  
        </plugins>
    </build>            

 

    3.  编写切面类(封装增强逻辑)

1 //切面:定义了增强的业务逻辑(权限验证)
2 public class SecurityAspect {
3     //权限校验的系统逻辑
4     public void checkPrivilege(){
5         System.out.println("我是权限校验的方法,我需要在方法执行前进行执行");
6     }
7 }

 

    4.  创建代理对象

 1 public class ProxyFactory implements InvocationHandler{
 2     //目标类
 3     private Object target;
 4         
 5     //传递目标对象
 6     public ProxyFactory(Object target) {
 7         super();
 8         this.target = target;
 9     }
10 
11     public Object getProxy(){
12         /**
13          * loader:类加载器
14          * interfaces:目标实现类接口(jdk动态代理必须有接口)
15          * h:实现了InvocationHandle接口的类
16          */
17         return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
18                 target.getClass().getInterfaces(), this);
19     }
20 
21     @Override
22     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
23         //添加校验权限的逻辑
24         SecurityAspect securityAspect = new SecurityAspect();        
25         //添加检验权限
26         securityAspect.checkPrivilege();        
27         //反射调用业务逻辑方法(目标类,参数)
28         Object result = method.invoke(target, args);
29         return result;
30     }
31 }

 

    5.  测试

1 public static void main(String[] args) {
2         //测试动态代理的执行
3         UserService target = new UserServiceImpl();        
4         //产生代理对象
5         UserService proxy = (UserService) new ProxyFactory(target).getProxy();        
6         //调用代理对象的业务方法
7         proxy.add();
8     }

 

 

  • 切面(aspect):横切逻辑被模块化的特殊对象。即它是一个类 :如LogAspect
  • 通知(advice):切面中必须完成的工作。即它是类中的一个方法:如writeLog()
  • 目标类(target):被通知增强的对象
  • 代理类(proxy):向目标类应用通知增强之后产生的对象
  • 切入点(pointcut):切面中通知执行的“地点”的定义
  • 连接点(JoinPoint): 与切入点匹配的执行点:如目标类中的所有方法getUserId()

代理设计模式的原理: 使用一个代理对象将原始对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理对象. 代理对象决定是否以及何时将方法调用转到原始对象上。

AOP简介

    •  日志记录的代码和真正的业务逻辑代码进行代码分离 
    •  通用的系统功能(日志记录、权限校验)进行了高度的模块化 
    • * 业务逻辑的功能变的更简洁,仅仅包含业务逻辑的代码*
    • * AOP可以将系统功能(日志记录)与业务逻辑功能搅和到一起执行*

 

 

      •     静态代理:
 1 //静态代理
 2 public class StaticProxyUserService implements UserService {
 3     //原始对象
 4     private UserService userService;
 5     
 6     public StaticProxyUserService(UserService userService) {
 7         this.userService = userService;
 8     }
 9     @Override
10     public User getById(String userId) {
11         System.out.println("执行权限校验,日志记录.......");
12         return userService.getById(userId);
13     }
14     @Override
15     public boolean add(User user) {
16         System.out.println("执行权限校验,日志记录.......");
17         return userService.add(user);
18     }
19 
20     @Override
21     public boolean delete(String userId) {
22         System.out.println("执行权限校验,日志记录.......");
23         return userService.delete(userId);
24     }
25     @Override
26     public boolean update(User user) {
27         System.out.println("执行权限校验,日志记录.......");
28         return userService.update(user);
29     }
30 }

 

 

 

 


动态代理可以实现静态代理相同的功能,唯一的区别这些Proxy的创建都是自动的并且在系统运行时生成的。这样就不需要对每一个原始对象来创建一个代理了。

图片 3

编辑:计算机教程 本文来源:AOP介绍

关键词: