Spring AOP:切面开发详解


  在 《Spring AOP增强》一文中,演示了各种增强的实现方式。增强提供了连接点的方位信息:如织入方法前面、后面等。但是增强会被织入目标类的所有方法,如何像手术刀一样找到目标方法,进行增强的织入,就要配合切点。

  切点进一步描述织入哪些类的哪些方法上。切点和增强组合起来我们称为切面。当然,增强本身就包含了一部分连接点信息,所以增强类本身就是一个普通的切面。但是这个普通切面还不够精确,如果需要更精确的增强织入,就需要配合切点,组成即另两种切面:切点切面 和引介切面。

在讨论切面之前,我们先看看切点。

切点的类型

  Spring通过org.springframework.aop.Pointcut接口描述切点。Pointcut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到某些特定的类上,通过MethodMatcher定位到某些特定的方法上,从而实现了精确的定位。

在这里插入图片描述

Spring支持两种方法匹配器:静态方法匹配器和动态方法匹配器,一般情况下,动态匹配不常使用。方法匹配器的类型由isRuntime()返回值决定默认是false表示静态方法匹配。

Spring中定义了6中类型的切点:

  • 1.静态方法切点:

    rg.springframework.aop.support.StaticMethodMatcherPointcut

    是静态方法切点的抽象基类,默认情况下它匹配所有的类。StaticMethodMatcherPointcut包括两个主要的子类,分别是NameMatchMethodPointcutAbstractRegexpMethodPointcut,前者提供简单字符串匹配方法签名,而后者使用正则表达式匹配方法签名。

  • 2.动态方法切点:

  • 3.注解切点:【下次讨论】

  • 4.表达式切点:【下次讨论】

  • 5.流程切点:

  • 6.复合切点:

切面的类型

  Spring使用org.springframework.aop.Advisor接口表示切面的概念,一个切面同时包含横切代码和连接点信息。切面分为三类:一般切面、切点切面和引介切面。

  • Advisor:

    代表一般切面,它仅包含一个Advice。所以Advice(增强) 本身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法。因为这个横切面太宽泛,所以一般不会直接使用。

  • PointcutAdvisor:

    代表具有切点的切面,它包含Advice和Pointcut两个类。这是最常用的一种切面,也是本文讨论的重点。

    它常用的实现类有:(后边也会围绕他们进行讨论)

在这里插入图片描述

  • IntroductionAdvisor:

    代表引介切面。

  这里我们主要讨论PointcutAdvisor,它有两个常用的实现类:

StaticMethodMatcherPointcutAdvisor:普通方法名匹配切面

regexpMethodPointcutAdvisor:正则表达式匹配切面

普通方法名匹配切面

StaticMethodMatcherPointcutAdvisor代表一个静态方法匹配切面,它通过StaticMethodMatcher Pointcut(即静态方法切点)定义切点,通过类过滤和方法名匹配定义切点。

使用步骤:

  • 1.定义目标对象和增强实现类:

    目标对象:

//目标对象Waiter
public class Waiter {

    public void greetTo(String name) {
        System.out.println("waiter greet to " + name + "...");
    }

    public void serveTo(String name) {
        System.out.println("waiter serving " + name + "...");
    }
}
//目标对象Seller
public class Seller {
    public void greetTo(String name) {
        System.out.println("seller greet to " + name + "...");
    }
}

  对象Waiter中有两个方法:greetTo和serveTo.对象Seller中有一个同名的方法greetTo

增强实现类:这里定义一个前置增强。织入增强后,会在方法执行前打印一句话。

public class GreetingBeforeAdvice implements MethodBeforeAdvice {

    public void before(Method method, Object[] args, Object obj) throws Throwable {
        String clientName = (String) args[0];
        System.out.println("How are you!Mr." + clientName + ".");
    }
}
  • 2.定义切面对象,这里只需要继承StaticMethodMatcherPointcutAdvisor,实现的两个方法分别用于过滤类和方法名,这样就能保证定位到具体类的具体方法上,进行增强的织入。
/**
 * 定义一个切面
 */
public class StaticMethodMatcherAdvisor extends StaticMethodMatcherPointcutAdvisor {

    /**
     * 用于匹配方法
     * @param method
     * @param clazz
     * @return
     */
    public boolean matches(Method method, Class clazz) {
        return "greetTo".equals(method.getName());
    }

    /**
     * 用于匹配类
     * @return
     */
    public ClassFilter getClassFilter() {
        return new ClassFilter() {
            public boolean matches(Class clazz) {
                return Waiter.class.isAssignableFrom(clazz);
            }
        };
    }
}
  • 3.测试验证:首先我们通过编码的方式实现。随后再演示如何通过配置文件实现。
public class StaticMethodMatcherAdvisorTest {

    private Waiter waiter;
    private Seller seller;
    private ProxyFactory pfWaiter;
    private ProxyFactory pfSaller;
    private PointcutAdvisor advisor;//切面
    private Advice advice;//增强

    @BeforeClass
    public void init(){
        waiter = new Waiter();
        seller = new Seller();
        advisor = new StaticMethodMatcherAdvisor();
        advice = new GreetingBeforeAdvice();
        ((StaticMethodMatcherAdvisor) advisor).setAdvice(advice);

        //waiter的代理类
        pfWaiter = new ProxyFactory();
        pfWaiter.setTarget(waiter);
        pfWaiter.addAdvisor(advisor);
        //seller的代理类
        pfSaller = new ProxyFactory();
        pfSaller.setTarget(seller);
        pfSaller.addAdvisor(advisor);

    }

    @Test
	public  void staticMethod() {
        //获取代理进行方法调用
        Waiter proxy = (Waiter) pfWaiter.getProxy();
        proxy.greetTo("John");
        proxy.serveTo("John");

        //获取代理进行方法调用
        Seller proxy2 = (Seller) pfSaller.getProxy();
        proxy2.greetTo("John");
	}
}

  我们在测试类中首先定义了相关的目标对象,代理工厂和将要织入的增强。在初始化中,把增强和切面关联,同时构造了目标对象的代理工厂并将增强和期关联。最后,通过代理工厂获取代理类,进行调用。

最终打印结果如下:

How are you!Mr.John.  //增强中的输出
waiter greet to John...
waiter serving John...
seller greet to John...

可以看到,只有Waiter中的greetTo方法被织入了增强,说明切面的过滤已经生效。

如何通过配置文件实现?

  上面我们通过编码的方式实现目标对象、增强、切面还有代理类之间的相关装配。采用配置文件同样可以实现,本质上是一样,只不过把代码的逻辑采用了配置的方式实现。我们只需要将上面的第3步替换成以下两步:

1.编写配置文件:

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

	<!-- 普通方法名匹配切面 -->
	<bean id="waiterTarget" class="com.smart.advisor.Waiter" />
	<bean id="sellerTarget" class="com.smart.advisor.Seller" />
	<bean id="greetingAdvice" class="com.smart.advisor.GreetingBeforeAdvice" />
	<bean id="greetingAdvisor" class="com.smart.advisor.StaticMethodMatcherAdvisor"
		p:advice-ref="greetingAdvice" />

	<bean id="parent" abstract="true"
		class="org.springframework.aop.framework.ProxyFactoryBean"
		p:interceptorNames="greetingAdvisor" p:proxyTargetClass="true" />
	<bean id="waiter" parent="parent" p:target-ref="waiterTarget" />
	<bean id="seller" parent="parent" p:target-ref="sellerTarget" />

</beans>

  说明:我们首先配置了两个目标对象和增强类,紧接着我们配置了一个切面类,它指向:com.smart.advisor.StaticMethodMatcherAdvisor,并通过advice-ref属性关联了上面配置的增强。

因为两个目标类使用相同的切面,所以配置了一个parent节点,用以简化。其中p:interceptorNames属性指明了切面,p:proxyTargetClass="true"表明使用CGLib代理。最后配置两个代理类,通过target-ref属性和目标对象进行关联。

  1. 使用配置文件:
public class StaticMethodMatcherAdvisorXMLTest {

    private String configPath ="";
    private ApplicationContext ctx =null;

    @BeforeClass
    public void init(){
        configPath = "com/smart/advisor/beans.xml";
        ctx = new ClassPathXmlApplicationContext(configPath);
    }

    @Test
    public  void staticMethod() {
        Waiter waiter = (Waiter)ctx.getBean("waiter");
        Seller seller = (Seller)ctx.getBean("seller");
        waiter.greetTo("John");
        waiter.serveTo("John");
        seller.greetTo("John");
    }
}

  说明:通过ClassPathXmlApplicationContext加载配置文件。获取对应的代理对象,进行方法调用。结果和上面通过代码完全一样。这种方式,代码比较简洁,对象的依赖关系,配置在xml中统一管理。

小结:

  通过方法名匹配生成切面,可以对具体的方法进行增强织入,但是这种方式不够灵活,假设目标类中有多个方法,且命名有一定的规范,使用正则表达式匹配描述就灵活的多。

静态正则表达式方法匹配切面

下面直接使用RegexpMethodPointcutAdvisor,通过配置的方式为Waiter目标类定义一个切面。

1.在配置文件中增加正则表达式的切面配置和另一个代理对象:

<!-- 正则表达式方法名匹配切面 -->
	<bean id="regexpAdvisor"
		  class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
		  p:advice-ref="greetingAdvice">
		<property name="patterns">
			<list>
				<value>.*greet.*</value>
			</list>
		</property>
	</bean>
	<bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean"
		  p:interceptorNames="regexpAdvisor" p:target-ref="waiterTarget"
		  p:proxyTargetClass="true" />

2.代码测试:

public class RegexpAdvisorTest {
    private String configPath ="";
    private ApplicationContext ctx =null;

    @BeforeClass
    public void init(){
        configPath = "com/smart/advisor/beans.xml";
        ctx = new ClassPathXmlApplicationContext(configPath);
    }

    @Test
	public  void regexpAdvisor() {
		Waiter waiter = (Waiter)ctx.getBean("waiter1");
		waiter.greetTo("John");
		waiter.serveTo("John");
	}
}

输出结果: 匹配规则生效,只有greetTo方法被织入增强

How are you!Mr.John.
waiter greet to John...
waiter serving John...

总结:

  Spring中增强包含了要织入的代码和部分连接点信息,为了更精确的进行增强织入,需要配合切点精确定位。所以我们引出了切面的概念,切面就是增强和切点相结合。切点有6中类型,我们讨论最常用的静态方法切点,和与之对应的两个常用切面:StaticMethodMatcherAdvisor、RegexpMethodPointcutAdvisor。前者通过方法名和类过滤器,来确定对哪个类的哪个方法进行织入增强。后者定义了匹配的正则表达式规则,更加灵活。

  同时我们采用代码和配置文件两种不同的方式实现了相同的逻辑,可以看出,两者的本质和逻辑几乎一致,只是不同的表现形式。这也是Spring一贯的风格,包容开放。我们似乎感觉到通过配置文件能简化代码,但是配置xml文件的工作依然繁琐无趣,而这正是SpringBoot目前给我们解决的一个痛点。

  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论
spring 的优点? 1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦 2.可以使用容易提供的众多服务,如事务管理,消息服务等 3.容器提供单例模式支持 4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能 5.容器提供了众多的辅助类,能加快应用的开发 6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等 7.spring属于低侵入式设计,代码的污染极低 8.独立于各种应用服务器 9.spring的DI机制降低了业务对象替换的复杂性 10.Spring的高度开放性,并不强制应用完全依赖于Spring开发者可以自由选择spring的部分或全部 什么是DI机制? 依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色 需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中 创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者 因此也称为依赖注入。 spring以动态灵活的方式来管理对象 , 注入的两种方式,设置注入和构造注入。 设置注入的优点:直观,自然 构造注入的优点:可以在构造器中决定依赖关系的顺序。 什么是AOP? 面向切面编程(AOP)完善spring的依赖注入(DI),面向切面编程在spring中主要表现为两个方面 1.面向切面编程提供声明式事务管理 2.spring支持用户自定义的切面 面向切面编程(aop)是对面向对象编程(oop)的补充, 面向对象编程将程序分解成各个层次的对象,面向切面编程将程序运行过程分解成各个切面AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop是静态的抽象,aop是动态的抽象, 是对应用执行过程中的步骤进行抽象,,从而获得步骤之间的逻辑划分。 aop框架具有的两个特征: 1.各个步骤之间的良好隔离性 2.源代码无关性 Hibernate工作原理及为什么要用? 原理: 1.读取并解析配置文件 2.读取并解析映射信息,创建SessionFactory 3.打开Sesssion 4.创建事务Transation 5.持久化操作 6.提交事务 7.关闭Session 8.关闭SesstionFactory 为什么要用: 1. 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。 2. Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作 3. hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。 4. hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。 2. Hibernate是如何延迟加载? 1. Hibernate2延迟加载实现:a)实体对象 b)集合(Collection) 2. Hibernate3 提供了属性的延迟加载功能 当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。 3.Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系) 类与类之间的关系主要体现在表与表之间的关系进行操作,它们都市对对象进行操作,我们程序中把所有的表与类都映射在一起,它们通过配置文件中的many-to-one、one-to-many、many-to-many、 4. 说下Hibernate的缓存机制 1. 内部缓存存在Hibernate中又叫一级缓存,属于应用事物级缓存 2. 二级缓存: a) 应用及缓存 b) 分布式缓存 条件:数据不会被第三方修改、数据大小在可接受范围、数据更新频率低、同一数据被系统频繁使用、非 关键数据 c) 第三方缓存的实现 5. Hibernate的查询方式 Sql、Criteria,object comptosition Hql: 1、 属性查询 2、 参数查询、命名参数查询 3、 关联查询 4、 分页查询 5、 统计函数 6. 如何优化Hibernate? 1.使用双向一对多关联,不使用单向一对多 2.灵活使用单向一对多关联 3.不用一对一,用多对一取代 4.配置对象缓存,不使用集合缓存 5.一对多集合使用Bag,多对多集合使用Set 6. 继承类使用显式多态 7. 表字段要少,表关联不要

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

freesOcean

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值