spring之Aop面向切面

来源:互联网 发布:算法之美 京东 编辑:程序博客网 时间:2024/06/08 10:14

面向切面编程中的一些概念

1.1 代理模式

代理模式的英文叫做Proxy或Surrogate,中文都可译为”代理“,所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

A. 抽象主题角色

声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以是使用代理主题

B. 代理主题(Proxy)角色:

代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象;代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主题控制对真实主题的引用,负责在需要的时候创建真实主题对象(和删除真实主题对象);代理角色通常在将客户端调用传递给真实的主题之前或之后,都要执行某个操作,而不是单纯地将调用传递给真实主题对象。

C. 真实主题角色

定义了代理角色所代表地真实对象

1.1.1 JDK动态代理

JDK的动态代理必须具备四个条件:

          目标接口

          目标类

          拦截器

          代理类

总结:1、因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。

2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。

          3、利用JDKProxy方式必须有接口的存在。

          4invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型。

1.1.2 CGLIB做代理

1、 CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。

2、 用CGlib生成代理类是目标类的子类。

3、 用CGlib生成 代理类不需要接口

4、 用CGLib生成的代理类重写了父类的各个方法。

5、 拦截器中的intercept方法内容正好就是代理类中的方法体

spring有两种代理方式:

1. 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。

      优点:因为有接口,所以使系统更加松耦合

      缺点:为每一个目标类创建接口

2. 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。

      优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。

      缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。

1.1.3 Spring的动态代理

1、 拦截器必须实现MethodInterceptor接口

2、 在spring中的配置

总结:不管采用JDK动态代理生成代理类还是采用CGLIB生成动态代理类。目标类中的所有方法都被拦截下来。而在哪个方法里做比如权限的判断、安全性的检查等一系列工做必须在拦截器中作相应的判断。但是这样的编程形式给程序的编写带来了一定的麻烦。

1、 在拦截器中控制哪些方法将被做权限判断、安全性检查等是一件比较困难的事情。

A. 采取这样的配置目标类只能是一个,所以如果用这种方法做权限控制,得写很多代理,这样给代码的书写造成了困难。

B. 每一个类中的每一个方法如果都有不同的权限(实际的系统往往都是这样的),在拦截器中的判断代码书写会很困难。

2、 这样的代码也会导致硬编码,也就是说我们必须在拦截器中写一些权限判断等事情,会导致拦截器中代码量的增大,造成维护的麻烦。

1.2 AOP编程

1.2.1概念:

A. Aspect(切面)

比如说事务、权限等,与业务逻辑没有关系的部分 

B. joinpoint(连接点)

目标类的目标方法。(由客户端在调用的时候决定)

C. Pointcut(切入点)

所谓切入点是指我们要对那些拦截的方法的定义.

 被纳入spring aop中的目标类的方法。

D. Advice(通知)

所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

E. Target(目标对象):

代理的目标对象 

F. Weaving(织入)

是指把切面应用到目标对象来创建新的代理对象的过程.切面在指定的连接点织入到目标对象 

JDKProxy代理

SpringAop

目标对象

目标对象

拦截器类

切面

拦截器类中的方法

通知

被拦截到的目标类中方法的集合

切入点

在客户端调用的方法(目标类目标方法)

连接点

代理类

AOP代理

代理类的代理方法生成的过程

织入

通知根据拦截目标类中的目标方法的位置不一样可以分为:前置通知、后置通知、最终通知、环绕通知、异常通知

1.2.2 AOP实现的两种模式

1.2.2.1 xml形式

A. 前置通知

spring配置文件中声明切面

spring配置文件中声明目标类

定义切面、切入点、通知

    注:见6.2.3.4

说明:

1、在切面类中,没有必要实现接口,但方法名称要与<aop:before method=”checkSecurity” 中的checkSecurity一样。

2checkSecurity方法中通过JoinPoint参数可以获得目标类的目标方法名称、参数值等信息。

B. 后置通知

1、 没有特殊说明的地方和前置通知是一样的。

2、 在spring配置文件中

3、 在拦截器中的方法要和checkSecurity方法一样,有两个参数

              JoinPoint   point    可以获得目标方法和参数值

              Object      val    这里的名字要和returning=”val”中保持一致,指的是方法的返回值。

4、 returning=”val”时,通知里可以有返回参数,这个参数只能决定通知里能不能拿到方法的返回值,和客户端没有关系。

5、  在执行目标类的目标方法中遇到异常,则不执行后置通知。

C. 异常通知

1、 没有特殊说明的地方和前置通知一样

2、 在spring配置文件中

其中throwing指定了传递异常的参数名称

3、 在异常通知中(拦截器)中,必须是checkSecurity方法。方法中有两个参数

                JoinPoint    point   可以获得方法的名称、参数

               Throwable   ex    利用ex.getMessage()可以获得异常信息

D. 最终通知

1、 没有特殊说明,和前置通知的配置一样。

2、 在spring配置文件里:

说明:在最终通知中不受异常的影响。也就是说不论目标方法执行的过程中是否抛出异常,最终通知都将执行。

E. 环绕通知

1、 没有特殊说明和前置通知的配置保持一致。

2、 在spring文件中

3、 在环绕通知中,方法名称为checkSecurity。参数 类型 为ProceedingJoinPoint。

ProceedingJoinPointproceed方法相当于invoke方法,调用目标类的目标方法。ProceedingJoinPoint继承了JoinPoint

4、  能在方法执行的前后加入额外的代码。

说明:

1.2.2.2Aop注解形式

A. 前置通知

注意:@Aspectj是按照类型匹配的。

B. 后置通知

C. 异常通知

D. 最终通知

E. 环绕通知


spring中使用aop的实例和配置文件

1、proxyBeanFactory:代理类创建bean工厂,之前的beanFactory就是一个容器,创建bean,管理beanproxyBeanFactory就是一个创建代理对象的工厂,和其他javabean一样也有一些属性控制他的行为,下面列出常用的一些属性。

2proxybeanFactory配置文件常用的属性:现在站在代理类的角度看的话可能更清晰


(1)target: 代理目标(被代理者),这个属性指定了你这个代理对象要代理的目标对象即被代理对象(委托者)

<beanid =""  class="">

<propertyname="target">

<ref bean="被代理的对象的id">

</property>

</bean>

//这样的话我们知道有这个被代理对象暴露出来了,我们可以直接定义在内部

<beanid="" class="">

<propertyname="target">

<beanclass="" /> 

</property>

</bean>

// 这样就不会被外部使用了,因为隐藏在了内部

 

(2)、proxyInterface: 这个属性指定了,从工厂中创建的bean要实现的接口

<beanid="" class="">

<propertyname="proxyInterface">

<value>impinterface</value>

</property>

</bean>

// 这样proxyBeanFactory就知道了创建的bean对象要实现impinterface 这个接口

(3)、interceptorName: interceptor(拦截机,妨碍着) 定义了一个应用到目标对象上的advisor或列表

// 提供多个接口来实现,使用list

<beanid="" class="">

<property  name="interceptorName">

<list>

<value>advisor1</value>

<value>advisor2</value>

<value>serviceTarget</value>

 // 指定了被代理对象,但是还是使用 target属性来指定

</list>

</property>

</bean>

 

总结: 通过上面这三个属性的配置,很清晰的看到就是一个代理模式的实现,其中的target就是指定了被代理的对象,proxyInterface就是代理类要实现的接口,而interceptorName就是一个通知,或已被安全监测等拦截器,通过这三个属性的配置就可以实现代理,也就是 spring中说的aop


实例:实现前置通知,后置通知,以及环绕通知的实现

(1)、interface 代理类要实现的接口(proxyInterface)

package com.inspur.imp;

/**
 *@author WHD
 *2015-2-3
 */
public interface IStudent {
public  void  addStu(String name);
public void  addStuName(String name);
public void delStu();
}

(2)、目标类(target)

package com.inspur.imp;

/**
 *@author WHD
 *2015-2-3
 */
public class IStudentImp  implements IStudent{

    @Override
    public void addStu(String name) {
        System.out.println("委托类执行 开始!");
        // TODO Auto-generated method stub
        if("".equals(name)){
            System.out.println("名称为空");
        }
        if("whd".equals(name)){
            System.out.println(" name  is"+name);
        }
        System.out.println("名称"+name);
    }

    @Override
    public void addStuName(String name) {
        // TODO Auto-generated method stub
        System.out.println("add  name:"+name);
    }

    @Override
    public void delStu() {
        // TODO Auto-generated method stub
        System.out.println("delete stu");
    }
   
}

(3)、切面(切面中只定义了前置通知)

package com.inspur.imp;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

/**
 *@author WHD
 *2015-2-3
 */

// 和业务无关的这个类就是切面

public  class MethodBeforeImp  implements MethodBeforeAdvice {
    @Override

//  这个方法就是通知

    public void before(Method arg0, Object[] arg1, Object arg2)
            throws Throwable {
        // 获取目标类的名称,方法名称以及方法中的参数
        // 方法名
         String name1=arg0.getName();
         //参数值
         String name2=arg1[0].toString();
         //类名
         Object obj=arg2.getClass();
         String name3=obj.toString();
         if("whd".equals(name2)){
             System.out.println("参数值"+name2);
         }
        System.out.println("before method"+"  方法名称:"+name1+"  参数值:"+name2+"  目标类:"+name3);
    }
}

(4)、切面(切面中只定义了后置通知)

package com.inspur.imp;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

/**
 *@author WHD
 *2015-2-3
 */

// 和业务无关的这个类就是切面

public class AfterAdvice   implements AfterReturningAdvice {
    @Override

//  这个方法就是通知

    public void afterReturning(Object arg0, Method arg1, Object[] arg2,
            Object arg3) throws Throwable {
        // TODO Auto-generated method stub
        //参数值
        String  name=arg2[0].toString();
        System.out.println("后置通知"+name);
    }
}


(5)、环绕通知

package com.inspur.imp;

import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 *@author WHD
 *2015-2-3
 */

//和业务无关的这个类是切面

public class CompareInterceptor implements MethodInterceptor {

    @Override

//  这个方法就是通知

    public Object invoke(MethodInvocation arg0) throws Throwable {
        // TODO Auto-generated method stub
        Object  result=null;
        String obj=arg0.getArguments().toString();
        if("whd".equals(obj)){
            result=arg0.proceed();
            
            System.out.println("环绕方法");
        }else{
            System.out.println("环绕方法");
        }
        
        return result;
        
    }

}

(6)、配置文件

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context-3.2.xsd
                http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

<!--    目标对象  -->  

     <bean id="studentImp"  class="com.inspur.imp.IStudentImp">
     </bean>

<!--  切面-前置通知(拦截器)   -->

     <bean id="beforeAdvice"  class="com.inspur.imp.MethodBeforeImp"/>

<!--  切面-后置通知(拦截器)   -->

     <bean  id="afterAdvice"   class="com.inspur.imp.AfterAdvice"/>

<!--  切面-环绕通知(拦截器)  -->

     <bean id="compareInterceptor"  class="com.inspur.imp.CompareInterceptor" />
<!--  代理类   -->
     <bean id="proxystudent"  class="org.springframework.aop.framework.ProxyFactoryBean">

<!--  代理类要实现的接口 -->

     <property name="proxyInterfaces">
     <value>com.inspur.imp.IStudent</value>
     </property>

<!--切面(拦截器) -->

     <property name="interceptorNames">
     <list>
      <value>beforeAdvice</value>  
      <value>afterAdvice</value>  
       <value>compareInterceptor</value>   
     </list>   
     </property>

<!-- 目标类 -->

    <property name="target">
    <ref bean="studentImp"/>
     </property>
    </bean>
</beans>


(7)、测试类

/**
 *
 */
package com.test;

import java.util.List;
import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.inspur.imp.IStudent;
import junit.framework.TestCase;
/**
 *@author WHD
 *2014-10-4
 */
public class TestDisk extends TestCase{
    public  void  test(){
        ApplicationContext  act= new ClassPathXmlApplicationContext("ApplicationContext.xml");    
        IStudent stu= (IStudent)act.getBean("proxystudent");
        stu.addStu("whd");
    }
}


实例:对指定的方法实现前置通知,后置通知,其实在实际中我们也是只对某一类进行方法进行拦截的,也就是静态切入点

(1)、抽象接口

package com.inspur.dao;
/**
 *@author WHD
 *2015-2-4
 */
public interface shopping {
      public String buySomething(String type);
      public String buyAnything(String type);
      public String sellSomething(String type);
      public String sellAnything(String type);
}

(2)、目标类

package com.inspur.dao;

/**
 *@author WHD
 *2015-2-4
 */
public class Shoppingimp implements shopping{

    @Override
    public String buySomething(String type) {
        // TODO Auto-generated method stub
        System.out.println("bysomethind"+ type);
        return null;
    }
    @Override
    public String buyAnything(String type) {
        // TODO Auto-generated method stub
        System.out.println("buyAnything"+ type);
        return null;
    }
    @Override
    public String sellSomething(String type) {
        // TODO Auto-generated method stub
        System.out.println("sellSomething"+ type);
        return null;
    }
    @Override
    public String sellAnything(String type) {
        // TODO Auto-generated method stub
        System.out.println("sellAnything"+ type);
        return null;
    }
}

(3)、切面(切面只定义了前置通知)

package com.inspur.dao;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
/**
 *@author WHD
 *2015-2-4
 */

//这个和业务无关的类就是一个切面

public class MethodBefore  implements MethodBeforeAdvice  {
    @Override

// 切面中的方法,就是一个通知---前置通知

   public void before(Method arg0, Object[] arg1, Object arg2)
            throws Throwable {
        // TODO Auto-generated method stub
        String type=arg1[0].toString();
        System.out.println("前置拦截器"+type);
    }
}


(4)、配置文件

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context-3.2.xsd
                http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
   
    <!--目标类-->
    <bean id="shoppingImpl"  class="com.inspur.dao.Shoppingimp">
  
    </bean>
    <!-- 切面(拦截器)--前置通知-->
     <bean id="shoppingAdvise"  class="com.inspur.dao.MethodBefore">
     </bean>
     <!--  静态切入点  这里的class 说明是使用方法名称匹配来实现的,切入点就是一个规则 -->
     <bean id="shoppingPointCutAdvisor"  class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">

    <!--   这个属性说明了对哪些方法进行拦截,也就是定义了拦截规则   -->

     <property name="mappedNames">
     <list>
     <value>sellSomething</value>
     <value>sellAnything</value>
     </list>
     </property>

<!--    织入切面的通知  -->

     <property name="advice">
     <ref  bean ="shoppingAdvise"/>
     </property>
     </bean>
     <!-- 代理类 这个和上面一个不同,这里的interceptorNames这个属性的值为上面定义的切入点的id  -->
     <bean id="StaticAdvisorTest"  class="org.springframework.aop.framework.ProxyFactoryBean">
     <property  name="proxyInterfaces">
     <value>com.inspur.dao.shopping</value>
     </property>
     <property name="interceptorNames">
     <list>
     <value>shoppingPointCutAdvisor</value>
     </list>
      </property>
      <property name="target">
      <ref bean="shoppingImpl"/>
      </property>
     </bean>
    
</beans>

(5)、测试类

/**
 *
 */
package com.test;

import java.util.List;
import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.inspur.dao.shopping;
import junit.framework.TestCase;
/**
 *@author WHD
 *2014-10-4
 */
public class TestDisk extends TestCase{
     public void testStatic(){
         ApplicationContext  act= new ClassPathXmlApplicationContext("ApplicationContext.xml");    
            shopping stu= (shopping)act.getBean("StaticAdvisorTest");
            stu.buyAnything("to buy antthing");
            stu.sellAnything("want sell angthing");
            stu.sellSomething("sellsomething");
     }

}



动态切入点

(1)、接口

package com.inspur.service;

/**
 *@author WHD
 *2015-2-4
 */
public interface Implement {
void  test();
}

(2)、目标类

package com.inspur.service;

/**
 *@author WHD
 *2015-2-4
 */
public class Target  implements Implement{

    @Override
    public void test() {
        // TODO Auto-generated method stub
        System.out.println("目标类");
    }
}

(3)、切面的前置通知

package com.inspur.service;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

/**
 *@author WHD
 *2015-2-4
 */
public class Before implements MethodBeforeAdvice {

    @Override
    public void before(Method arg0, Object[] arg1, Object arg2)
            throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("前置通知");
    }

}

(4)、动态切入点配置类,只有通过这个类调用目标类的方法拦截器才起作用

package com.inspur.service;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
 *@author WHD
 *2015-2-4
 */

// 当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean。

//换句话说,就是这个类可以直接获取spring配置文件中,所有有引用到的bean对象。


public class Argument  implements ApplicationContextAware {

//  代理类型
       private Implement impl;  


    @Override
    public void setApplicationContext(ApplicationContext context)
            throws BeansException {
        // TODO Auto-generated method stub
        impl = (Implement)context.getBean( "aop");  
    }
    public void test() {  
        System.out.println( "Argument.test()");  
        impl.test();  
    }
    
    public void test2(Implement i) {  
        i.test();     
    }

}

(5)、配置文件

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context-3.2.xsd
                http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
   
     
     <!-- 动态切入点 -->
      <bean id="arg" class="com.inspur.service.Argument" />  
     <!-- 这个类中配置切入点,也就是调用这个的构造函数中的那个类的方法拦截器才会起作用 -->
      <bean id="dponintcut" class="org.springframework.aop.support.ControlFlowPointcut">
       <constructor-arg>
       <value>com.inspur.service.Argument</value>
       </constructor-arg>
      </bean>

     <!-- 和之前一样 定义通知(拦截器),不同的是这里是给属性赋值-->
    <bean id="advice" class="org.springframework.aop.support.DefaultPointcutAdvisor">  
        <property name="advice">  
            <bean class="com.inspur.service.Before" />  
        </property>  
        <property name="pointcut">  
               <ref bean="dponintcut"/>
        </property>  
    </bean>  
 

     <!-- 代理类  -->
    <bean id="aop" class="org.springframework.aop.framework.ProxyFactoryBean">  

     <!-- 要实现的接口-->
        <property name="proxyInterfaces">  
            <list>  
                <value>com.inspur.service.Implement</value>  
            </list>  
        </property>  

     <!-- 通知-->
        <property name="interceptorNames">  
            <list>  
                <value>advice</value>  
            </list>  
        </property>  

     <!-- 目标类 -->
        <property name="target">  
            <bean class="com.inspur.service.Target" />  
        </property>  
    </bean>  
 
</beans>


(6)、测试类

/**
 *
 */
package com.test;

import java.util.List;
import java.util.Map;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.inspur.service.Argument;
import com.inspur.service.Implement;
import junit.framework.TestCase;

    public  void  test(){
          ApplicationContext context = new ClassPathXmlApplicationContext( "ApplicationContext.xml"); 

          //  获取动态切入点类      

          Argument arg = (Argument)context.getBean( "arg"); 

          //  对于这个调用前置通起作用

            arg.test();  
            System.out.println( "----------------"); 

          //  代理类

            Implement impl = (Implement)context.getBean( "aop"); 

          //  对于这个调用不起作用

            impl.test();  
            System.out.println( "----------------"); 

         //  对于这个调用前置通起作用

            arg.test2( impl);  
      
    }
  
}

测试结果:

(7)、从这个测试结果我们看到,通过Argument    类调用的方法都调用前置通知,而没有通过他的则不会调用前置通知,所以这个Argument 就是一个切入点。


对有些内容还是有点模糊,如果有写错的希望各位指正,谢谢!


0 0
原创粉丝点击