首先这个配置模式估计现在已经不用了,因为我在我们公司的项目里面并没有看到这么配置AOP相关的东西。虽然公司项目使用的是3.1,而我学习的这个版本是2.5。
不过,这个就和学习spring的控制反转(IOC)和依赖注入(DI)一样,刚刚开始的时候,都是从简单的xml配置学起、然后再进一步简化:最开始也是在xml文件里面配置很多的bean,每个model都得配置一个bean标签,直到后来的只要一句话
<context:component-scan base-package="com.lxk.hello" />;
就可以搞定spring的依赖注入,也就是使用注解相关的配置了。
这个AOP配置也是这么个道理。所以先从这个麻烦但是基础的配置模式看起。好理解这个aop。
这个aop测试所使用到的jar包有以下:
aspectjrt.jar
aspectjweaver.jar
cglib-nodep-2.1_3.jar
spring.jar
某度云盘如下:
链接:http://pan.baidu.com/s/1nuUrV3z 密码:zppw

下面是测试代码。

共同接口:(其实这个在实际使用中是可以省略的,又不是对他进行添加切面编程的)

package com.lxk.spring.aop; 
 
import java.util.List; 
 
/** 
 * 目标对象和代理对象都实现的接口 
 */ 
public interface PersonDao { 
    void deletePerson(); 
    List<Person> getPerson() throws Exception; 
    void savePerson(); 
    void updatePerson(); 
} 


目标对象实现目标接口:(这才是真正要面对的,要扩展的。要未他们做切面的地方)

package com.lxk.spring.aop; 
 
import com.google.common.collect.Lists; 
 
import java.util.List; 
 
/** 
 * 目标对象:实现目标接口 
 */ 
public class PersonDaoImpl implements PersonDao { 
 
    @Override 
    public void deletePerson() { 
        System.out.println("delete perosn"); 
    } 
 
    @Override 
    public List<Person> getPerson() throws Exception { 
        List<Person> personList = Lists.newArrayList(); 
        Person person1 = new Person(); 
        person1.setPid(1L); 
        person1.setPname("person1"); 
        System.out.println("get person"); 
        personList.add(person1); 
        Person person2 = new Person(); 
        person2.setPid(2L); 
        person2.setPname("person2"); 
        personList.add(person2); 
        return personList; 
    } 
 
    @Override 
    public void savePerson() { 
        System.out.println("delete perosn"); 
    } 
 
    @Override 
    public void updatePerson() { 
        System.out.println("delete perosn"); 
    } 
 
} 

切面和切面里面的通知

这个才是对上面的要扩展的扩展,这就是切面里面的操作,,,对很多相同的操作提取到切面上去操作。这个只是把切面内部的方法写好,下面的配置文件说明这个切面切哪里也就是切入点在哪,怎么切,是前置,环绕,后置,还是异常等等。

package com.lxk.spring.aop; 
 
import org.aspectj.lang.JoinPoint; 
import org.aspectj.lang.ProceedingJoinPoint; 
 
import java.util.ArrayList; 
import java.util.List; 
 
/** 
 * 切面(spring aop 就不需要拦截器啦) 
 * (模拟hibernate里面保存数据要打开事物,然后各种增删改之后,再提交事物。) 
 */ 
public class Transaction { 
 
    public void beginTransaction() {//前置通知 
        //打开事物 
        System.out.println("begin Transaction"); 
    } 
 
    /** 
     * @param joinPoint 通过joinPoint可以得到目标类和目标方法的一些信息 
     * @param val       目标方法的返回值 
     *                  和<aop:after-returning returning="val"/>中returning的值保质一致 
     */ 
    public void commit(JoinPoint joinPoint, Object val) {//后置通知 
        String methodName = joinPoint.getSignature().getName(); 
        System.out.println(methodName); 
        System.out.println(joinPoint.getTarget().getClass().getName()); 
        //提交事物 
        System.out.println("commit"); 
        List<Person> personList = (ArrayList<Person>) val; 
        for (Person person : personList) { 
            System.out.println(person.getPname()); 
        } 
    } 
 
    public void finalMethod() { 
        System.out.println("最终通知"); 
    } 
 
    public void aroundMethod(ProceedingJoinPoint joinPoint) {//环绕通知 
        try { 
            System.out.println("around method"); 
            joinPoint.proceed();//调用目标类的目标方法 
        } catch (Throwable e) { 
            // TODO Auto-generated catch block 
            e.printStackTrace(); 
        } 
    } 
 
    /** 
     * 异常通知 
     */ 
    public void throwingMethod(Throwable except) { 
        System.out.println(except.getMessage()); 
    } 
} 


xml的配置文件:

<?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 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> 
    <!--  
    	1、目标类 
    	2、切面 
		3、进行aop的配置 
		(目标接口没有:因为引入到容器是为了实例化对象,接口是不能实现对象的。) 
		(也没有拦截器的引入,有的只是aop的配置,如上的3、) 
     --> 
    <!-- 目标类 --> 
    <bean id="personDao" class="com.lxk.spring.aop.PersonDaoImpl"/> 
    <!-- 切面的声明 --> 
    <bean id="transaction" class="com.lxk.spring.aop.Transaction"/> 
    <!-- aop配置 --> 
    <aop:config> 
        <!-- 
            配置aop的切入点 
              id 是切入点的标识 
              expression 为切入点的表达式 
                 execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) 
                    throws-pattern?) 
                 modifiers-pattern  修饰符  可选  public private protected 
                 ret-type-pattern  返回类型  必选  *  代表任意类型 
                 declaring-type-pattern  方法的声明类型 
                 name-patterm  方法名称类型 
                     set*  以set开头的所有的方法名称 
                     update*  以update开头的所有的方法名称 
                 param-pattern  参数匹配 
                     (..)  任意多个参数,每个参数任意多个类型 
                     (*,String) 两个参数  第一个是任意类型,第二个是String 
                     (String,*,Integer) 三个参数,第一个是String类型,第二个是任意类型,第三个是Integer类型 
                    throws-pattern  异常的匹配模式 
             例子: 
               execution(* cn.itcast.spring.aop.xml.AService.*(..)); 
                  cn.itcast.spring.aop.xml.AService下的所有的方法 
               execution(public * cn.itcast.oa..*.*(..)) 
                   返回值为任意类型,修饰符为public,在cn.itcast.oa包及子包下的所有的类的所有的方法 
               exectuion(* cn.itcast.oa..*.update*(*,String)) 
                   返回值是任意类型,在cn.itcast.oa包及子包下所有的以update开头的参数为两个,第一个为任意类型 
                   第二个为String类型的所有类的所有的方法 
         --> 
        <aop:pointcut expression="execution(* com.lxk.spring.aop.PersonDaoImpl.*(..))" id="perform"/> 
        <!-- 配置切面(切面里面配置通知)—— ref 指向声明切面的类 --> 
        <aop:aspect ref="transaction"> 
            <!-- 前置通知pointcut-ref 引用一个切入点 --> 
            <aop:before method="beginTransaction" pointcut-ref="perform"/> 
 
            <!-- 
                后置通知 
                   *  returning 目标方法的返回值 
                   *  如果目标方法中有可能存在异常,异常确实发生了,这个时候,后置通知将不再执行 
             --> 
 
            <!--<aop:after-returning method="commit" pointcut-ref="perform" returning="val"/>--> 
 
            <!-- 
                最终通知 
                   *   不能得到目标方法的返回值 
                   *   无论目标方法是否有异常,最终通知都将执行 
                   *   资源的关闭、连接的释放写在最终通知里 
             --> 
            <!--<aop:after pointcut-ref="perform" method="finalMethod"/>--> 
 
            <!-- 
                    环绕通知 
                       *  ProceedingJoinPoint的proceed方法就是目标对象的目标方法 
                       *  环绕通知可以控制目标对象目标方法执行 
             --> 
            <!-- 
            <aop:around method="aroundMethod" pointcut-ref="perform"/> 
             --> 
            <!-- 
                    异常通知 
                      在异常通知中获取目标方法抛出的异常 
             --> 
            <!--<aop:after-throwing method="throwingMethod" pointcut-ref="perform" throwing="except"/>--> 
        </aop:aspect> 
    </aop:config> 
</beans>

上面 id=perform 的bean,就是后面切面声明的时候,用pointcut-ref关联这个bean,声明为切入点。意思就是说这个类里面的所有方法,执行的时候,都会执行这个切面里面的方法。

id = transaction,也仅仅是把这个bean放到spring 容器里面,当然,上面的perform也是这么滴。

<aop>.......</aop>,这里面才是对切面的详细声明。

aspect ref="transaction 说明spring容器里面哪个bean是切面类。

pointcut-ref设置切入点,也就是被选中的bean里面的方法执行的时候,就可以执行对应的切面的方法啦。

也就是说,什么样的地方,会执行切面内部的代码操作。这里设置的是这个impl类里面的所有方法被调用的时候,就会执行切面的方法。

下面有before after around 等等几种类型的切面方法,后面的method 指出这个前置通知还是后置通知,还是什么通知执行的方法的名称。对应于切面类里面的方法。这个是必须存在的,不然关联不到,那就不行啦。

main方法测试类

package com.lxk.spring.aop; 
 
import org.junit.Test; 
import org.springframework.context.ApplicationContext; 
import org.springframework.context.support.ClassPathXmlApplicationContext; 
/** 
 * 原理: 
 *    当启动spring容器的时候, 
 *       *  会把纳入spring容器的bean实例化 
 *       *  spring容器会解析配置文件中的 
 *                <aop:config>,从而解析aop:config下的切入点表达式 
 *       *  解析出来以后就会在spring容器的bean中查找 
 *              是否有类符合切入点表达式,如果符合,则为这个对象创建代理对象 
 *       *  客户端根据bean的id寻找一个bean,如果该bean有代理对象,则返回代理对象,如果没有代理对象则 
 *          返回bean的对象 
 * @author Administrator 
 * 
 */ 
public class AOPXMLTest { 
	@Test 
	public void test() throws Exception{ 
		ApplicationContext context = new ClassPathXmlApplicationContext("com/lxk/spring/aop/applicationContext.xml"); 
		PersonDao personDao = (PersonDao)context.getBean("personDao"); 
		personDao.getPerson(); 
	} 
} 

person的model:

package com.lxk.spring.aop; 
 
/** 
 * model 
 */ 
public class Person { 
    private Long pid; 
    private String pname; 
 
    public Long getPid() { 
        return pid; 
    } 
 
    public void setPid(Long pid) { 
        this.pid = pid; 
    } 
 
    public String getPname() { 
        return pname; 
    } 
 
    public void setPname(String pname) { 
        this.pname = pname; 
    } 
} 



上述代码模仿的是hibernate在操作数据库的时候,每当要增删改的时候,就要开启事物,然后增删改完之后,又得提交事物,每个增删改方法都得有这2个动作。然后就做这个优化。

具体如图:



然后各种注意事项都基本写在代码里面了。有不足的改天再补上。

实际开发用的不是这么个配置姿势,实际开发配置页和spring注解实现一样,也就一两行的配置,下次再说吧。


Spring AOP 中的 execution切入点指示符。执行表达式格式和实际代码对照,以及参数的理解。

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) 
          throws-pattern?)



?,问号表示可选项。可填可不填。declaring-type是方法全名,只到类,不含方法名,具体就是类路径。看上面的图的对应关系可知。


关于切入点表达式,应该选中谁?
应该把目标类的目标方法给选中。
<aop:pointcut expression="execution(* com.lxk.spring.aop.PersonDaoImpl.*(..))" id="perform"/>
返回类型是任意类型,在com.lxk.spring.aop.PersonDaoImpl这个类下面的所有方法,方法的参数任意。然后这个切入点在spring容器中的名字也就是id是 id="perform"

在xml配置文件中,除前置通知外,其他的几个通知都给注释了。
下面讨论,这些个通知,一共5个。


前置通知:(<aop:before>)
method对应的是切面里面的通知--也就是切面里面的方法啦。参数就是切面里面的方法名,写错就找不到了。
pointcut-ref="perform",是引用上面定义的切入点。


后置通知:(<aop:after-returning>)

method对应的是切面里面的通知--也就是切面里面的方法啦。参数就是切面里面的方法名,写错就找不到了。
pointcut-ref="perform",是引用上面定义的切入点。
returning="val" 目标方法的返回值
这个目标方法对应的是 List<Person> getPerson() ,这方法的最终效果是获得查询结果。在最终测试文件里面要调用系统生成的代理对象:personDao.getPerson();
如果目标方法中有可能存在异常,异常确实发生了,这个时候,后置通知将不再执行
比如在getPerson()方法加入如下code : int a = 100 / 0;
他就异常了,这个后置通知就不会执行了,与最终通知的区别:后置通知可能会不执行,但是最终通知会始终执行。

最终通知:(<aop:after>)

pointcut-ref="perform",是引用上面定义的切入点。
method对应的是切面里面的通知--也就是切面里面的方法啦。参数就是切面里面的方法名,写错就找不到了。
不能得到目标方法的返回值
无论目标方法是否有异常,最终通知都将执行
资源的关闭、连接的释放写在最终通知里

环绕通知:(<aop:around>)

pointcut-ref="perform",是引用上面定义的切入点。
method对应的是切面里面的通知--也就是切面里面的方法啦。参数就是切面里面的方法名,写错就找不到了。
有了环绕通知,那么前置后置通知,就可以暂时先注释掉。
ProceedingJoinPoint的proceed方法就是目标对象的目标方法
环绕通知可以控制目标对象目标方法执行.
前置通知和后置通知,则不能控制目标对象的目标方法的执行

异常通知:(<aop:after-throwing>)

在异常通知中获取目标方法抛出的异常
pointcut-ref="perform",是引用上面定义的切入点。
method对应的是切面里面的通知--也就是切面里面的方法啦。参数就是切面里面的方法名,写错就找不到了。
throwing="except" 的参数和切面的异常通知抛的异常要一致。


最后关于xml配置spring aop 的注意事项:
1.如果切入点表达式的类写得不正确,则直接报错,找不到该类
2.如果切入点表达式的方法写错了,则客户端直接返回该bean的对象
3.spring的aop的原理可以采用两种形式产生代理对象
  *  如果目标类实现接口,则spring会采用jdk动态代理
  *  如果目标类没有实现接口,则采用cglib动态代理
4.针对一个目标类,切面可以有很多个


关于上面的第三点,可以如下测试:
PersonDaoImpl这个类不实现PersonDao接口,那么在主测试文件中,把 context.getBean("personDao")的返回值类型,改为目标对象类型。那么程序还是可以正常运行。
由前面的jdk动态代理和cglib动态代理的区别。可以确定,当 不实现接口的时候,使用的就是cglib来实现动态代理的。
做软件,做程序员,这个结果咱就不要猜,最有效的就是测试,自己整一下。就造了。
下面是我放的debug测试的截图,注意这个变量的名字,就可以说明这个就是cglib实现的动态代理。

关于上面的第四点,可以如下测试:
在xml配置文件里面再添加个id=logger的bean标签,将其纳入spring容器管理,当然对应实现个Java类代码
然后还在主测试文件调用,就可以。具体的xml配置文件如下。

<?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 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> 
 
    <bean id="personDao" class="com.lxk.spring.aop.xml.PersonDaoImpl"/> 
    <bean id="transaction" class="com.lxk.spring.aop.xml.Transaction"/> 
    <bean id="logger" class="com.lxk.spring.aop.xml.Logger"/> 
    <aop:config> 
        <aop:pointcut id="perform" expression="execution(* com.lxk.spring.aop.xml.PersonDaoImpl.*(..))"/> 
        <aop:aspect ref="transaction"> 
            <aop:before method="beginTransaction" pointcut-ref="perform"/> 
        </aop:aspect> 
 
        <aop:aspect ref="logger"> 
            <aop:before method="logging" pointcut-ref="perform"/> 
        </aop:aspect> 
    </aop:config> 
</beans>



评论关闭
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!