面向切面的Spring

AOP语言:通知(advice)、切点(pointcut)、连接点(join point)。

    通知(advice):切面的工作被称为通知;

        1.    前置通知(Before):在目标方法调用前调用通知功能;

        2.    后置通知(After):在目标方法调用后调用通知功能;

        3.    返回通知(After-returning):在目标方法成功执行够调用;

        4.    异常通知(After-throwing):在目标方法抛出异常后调用;

        5.    环绕通知(Around):在目标方法前后调用。

    连接点(join point):应用执行过程中能够插入切面的一个点,这个点可以是调用方法、抛出异常、甚至修改字段时。

    切点(pointcut):匹配通知所要织入的一个或多个连接点。

    切面(Aspect):通知和切点的结合。

    引入(Introduction):允许我们向现有的类添加方法和属性。

    织入(Weaving):把切面应用到目标对象并创建新的代理对象的过程。

    在目标对象的生命周期有多个连接点可以进行织入:

        1.    编译期:切面在目标编译时被织入。这种方式需要特殊的编译器,AspectJ织入编译器就是以这种方式织入切面的。

        2.    类加载期:切面在目标被加载到JVM时织入。需要特殊的类加载器,可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入(load-time-weaving LTW)就支持以这种方式织入切面。

        3.    运行期:切面在应用运行的某个时刻被织入。在织入切面时,AOP容器会为目标对象动态创建一个代理对象,SpringAOP就是以这种方式织入切面的。


    Spring对AOP的支持

    Spring提供了4种类型的AOP支持:

        1.    基于代理的SpringAOP;

        2.    纯POJO切面;

        3.    @AspectJ注解切面;

        4.    注入式AspectJ切面(试用Spring各个版本)。

前三种都是Spring AOP的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。


借助Spring的aop命名空间,我们可以将纯POJO转换为切面。

如果你的AOP超出了简单的方法调用(如构造器和属性拦截),那么需要考虑使用AspectJ来实现切面。

Spring通知是Java编写的。

Spring在运行时通知对象。

Spring只支持方法级别的连接点。

Spring AOP的AspectJ切点,最重要一点就是Spring仅支持AspectJ切点指示器的一个字子集。Spring AOP是基于代理的,而某些切点表达式是与基于代理的AOP无关的。

Spring借助AspectJ切点表达式语言定义Spring切面:

    arg():限制连接点匹配参数为指定类型的执行方法;

    @args():限制连接点匹配参数由指定注解标注的执行方法;

    execution():用于匹配是连接点的执行方法;

    this():限制连接点匹配AOP代理的bean引用为指定类型的类;

    target:限制连接匹配目标对象为指定类型的类;

    @target:限制连接点匹配特定的执行对象,这些对象对应的类上要有指定类型的注解;

    within():限制连接点匹配指定的类型;

    @within():限制连接点匹配指定注解所标志的类型(当使用Spring AOP时,方法定义在由指定注解所标注的类里)

    @annotation:限定匹配带有指定注解的连接点。 

    在Spring中使用其它AspectJ指示器时,当抛出异常。

    当我们查看以上所展示的指示器,只有execution是实际执行匹配的,其它都是用来限制匹配的。    

    例子:

    

    在描述切点时,可以使用“and”代替“&&”,“or”和“not”分别代替“||”“!”。

    在切点中选择bean:

execution(* concert.Performance.perform())and bean('woodstock') 

    在这里我们希望在执行perform()方法时应用通知,但限定bean的ID。

    

使用注解创建切面:

    使用注解创建切面是AspectJ5引入的重要特性,AspectJ5之前,编写AspectJ切面需要学习一种Java语言的扩展。

    定义切面:

package com.lcf.spring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Audience {
	@Pointcut("execution(** com.lcf.spring.aop.PerformanceImp.perform(..))")
	public void performance(){};
	
//	@Before("performance()")
//	public void silenceCellPhones(){
//		System.out.println("silencing cell phone");
//	}
//	@Before("performance()")
//	public void takeSeat(){
//		System.out.println("take seat");
//	}
//	@AfterReturning("performance()")
//	public void applause(){
//		System.out.println("CLAP CLAP CLAP!");
//	}
//	@AfterThrowing("performance()")
//	public void demanrefund(){
//		System.out.println("demanding a refund!");
//	}
//	
	
	/**
	 * 定义环绕通知 
       * ProceedingJoinPoint 参数必须存在,在通知中通过它调用别通知的方法,可以重复调用,可以不调用,从而阻塞被通知方法的调用。
*/@Around("performance()")public void watchPerformance(ProceedingJoinPoint jp){try {System.out.println("silencing cell phone");System.out.println("take seat");jp.proceed();System.out.println("CLAP CLAP CLAP!");} catch (Throwable e) {System.out.println("demanding a refund!");}}

    @AspectJ表明该类不仅是一个POJO,还是一个切面。

    该类也可以声明成一个bean:

	@Bean
	public Audience audience(){
		return new Audience();
	}

    仅仅到此为止是无法开启注解的,所以需要在JavaConfig中或者XML中配置代理。

package com.lcf.spring.aop;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AopConfig {

	@Bean
	public Audience audience(){
		return new Audience();
	}
	
	@Bean
	public Performance performance(){
		return new PerformanceImp();
	}
	@Bean
	public Teacher Teacher(){
		return new Teacher();
	}
	@Bean
	public Student Student(){
		return new Student();
	}
}

}@Beanpublic Student Student(){return new Student();}}

或者在XML中:

    <!-- 开启AspectJ自动代理 -->	
    <aop:aspectj-autoproxy/>

    不管你是使用JavaConfig还是XML,AspectJ自动代理都会为使用@AspectJ注解的的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。

    Spring的AspectJ自动代理仅仅使用@AspectJ作为创建切面的向导,切面依然是基于代理的。这意味着Spring AOP仅限于方法级别的代理。如果想利用所有的AspectJ能力,我必须运行时使用AspectJ并且不依赖Spring来创建基于代理的切面。

    处理被通知方法中的参数


    args(trackNumber)限定符:它表明palyTrack()方法的int参数也会传递到通知中去。参数名称应与切点中的参数相匹配。

package com.lcf.spring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Teacher {
	@Pointcut("execution(* com.lcf.spring.aop.Student.goal(int)) && args(score)")
	public void goal(int score){};
	
//	@After("goal(score)")
//	public void say(int score){
//		if(score<60){
//			System.out.println("该学生未及格!");
//		}else{
//			System.out.println("该学生及格了!");
//		}
//	}
	@Around("goal(score)")
	public void round(ProceedingJoinPoint pjp,int score){
	
		try {
			System.out.println("宣布分数");
			
			pjp.proceed();
			System.out.println("学生获取的分数为:"+score);
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}
	
}
package com.lcf.spring.aop;

public class Student {
	private int score;
	public void goal(int score){
		System.out.println("....");
		this.score=score;
	}
}

    我试验了一下,pjp.proceed()方法似乎无法更改参数,这里如果想要更改int参数,不知道应该怎么办?

   

    通过注解引入新的功能:

    Java是静态语言,所以只能通过代理模式来动态的为对象增加新的能力。

    动态增加新的功能:

package com.lcf.spring.aop;
public interface Encoreable {
	void performEnocre();
}
package com.lcf.spring.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;

@Aspect
public class EnocreableIntroducer {
	@DeclareParents(value="com.lcf.spring.aop.Performance+",defaultImpl=DefaultEnocreable.class)
	private static Encoreable encoreable;
}
package com.lcf.spring.aop;

public class DefaultEnocreable implements Encoreable{

	@Override
	public void performEnocre() {
		System.out.println("通过AOP引入新的方法!");
		
	}
}

    @DeclareParents注解分为三部分:

    1.    value属性指定了那种类型的bean需要引入该接口(+表示所有子类);

    2.    defaultImpl指定了为引入该功能提供实现的类;

    3.    @DeclareParents注解标注的静态属性指明了要引入的接口。

    最后将该切面声明为一个bean:

	@Bean
	public EnocreableIntroducer enocreableIntroducer(){
		return new EnocreableIntroducer();
	}

    测试:

	@Autowired 
	private Performance per;
	@Test
	public void defaultEnocreable(){
		Encoreable encoreable = ((Encoreable)per);
		encoreable.performEnocre();
	}

    结果证明为Performance类增加了新的功能。

    但是面向切面的注解有一个明显的劣势,你必须能够为通知类添加注解。所以必要的时候,需要在XML中声明:

    在XML中声明切面:

    Spring的AOP配置元素能够以非侵入式的方式声明切面:

    <aop:advisor>:定义AOP通知器;

    <aop:after>:定义AOP后置通知(不管通知的方法是否执行成功);

    <aop:after-returning>:定义AOP返回通知;

    <aop:after-throwing>:定义AOP异常通知;

    <aop:around>:定义AOP环绕通知;

    <aop:aspect>:定义一个切面。

    <aop:aspectj-autoproxy>:启用@AspectJ注解驱动的切面;

    <aop:before>:定义一个AOP前置通知;

    <aop:config>:顶层的AOP配置元素。大多数的<aop:*>元素必须包含在其内;

    <aop:declare-parents>:以透明的方式为被通知的对象引入新的接口;

    <aop:pointcut>:定义一个切点。

    通过XML配置切面

    声明前置和后置通知:

    <!-- 声明切面 -->
    <aop:config>
    	<aop:aspect ref="audience">
    		<aop:before pointcut="execution(** com.lcf.spring.aop.Performance.perform(..))" method="silenceCellPhones"/>
    		<aop:before pointcut="execution(** com.lcf.spring.aop.Performance.perform(..))" method="takeSeats"/>
    		<aop:after-returning pointcut="execution(** com.lcf.spring.aop.Performance.perform(..))" method="applause"/>
    		<aop:after-throwing pointcut="execution(** com.lcf.spring.aop.Performance.perform(..))" method="demandRefund"/>
    	</aop:aspect>
    </aop:config>
    <!-- 定义切点使用 -->
    <aop:config>
    	<aop:aspect ref="audience">
    		<aop:pointcut id="performance" expression="execution(** com.lcf.spring.aop.Performance.perform(..))"/>
    	   	<aop:before pointcut-ref="performance" method="silenceCellPhones"/>
    		<aop:before pointcut-ref="performance" method="takeSeats"/>
    		<aop:after-returning pointcut-ref="performance" method="applause"/>
    		<aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
    	</aop:aspect>
    </aop:config>

    如果切点被多个切面引用,可以将其定义在<aop:config>元素内。

    声明环绕通知:

    <!-- 声明环绕通知 -->
    <aop:config>
    	<aop:aspect ref="audience">
    		<aop:pointcut id="performance" expression="execution(** com.lcf.spring.aop.Performance.perform(..))"/>
    		<aop:around pointcut-ref="performance" method="watchPerformance"/>
    	</aop:aspect>
    </aop:config>

    为通知传递参数:

    <!-- 为通知传递参数 -->
    <bean id="teacher" class="com.lcf.spring.aop.Teacher" />
    <bean id="teacher" class="com.lcf.spring.aop.Teacher">
    	<property name="score" value="100"/>
    </bean>
    <aop:config>
    	<aop:aspect ref="teacher">
    		<aop:pointcut id="goal" expression="execution(** com.lcf.spring.aop.Student.goal(int)) and args(score)"/>
    		<aop:around pointcut-ref="goal" method="say"/>
    	</aop:aspect>
    </aop:config>

    表达式与JavaConfig中定义切面几乎一模一样,区别只是使用了“and”代替了“&&”(因为在XML中,“&”会被解析为实体的开始)。

    通过切面引入新的功能:

    <!-- 通过切面引入新的功能 -->
    <aop:config>
    	<aop:aspect>
    		<aop:declare-parents types-matching="com.lcf.spring.aop.Performance"
    		 implement-interface="com.lcf.spring.aop.Encoreable" default-impl="com.lcf.spring.aop.DefaultEncoreable"/>
    	</aop:aspect>
    </aop:config>
    <!-- 可以使用定义Spring bean的方式,映入默认实现的类 -->
    <bean id="encoreableDelegate" class="com.lcf.spring.aop.DefaultEncoreable"/>
    <aop:config>
    	<aop:aspect>
    		<aop:declare-parents types-matching="com.lcf.spring.aop.Performance"
    		 implement-interface="com.lcf.spring.aop.Encoreable" delegate-ref="encoreableDelegate"/>
    	</aop:aspect>
    </aop:config>

    delegate-ref属性引用一个Spring bean作为引入的委托,区别在于,这样可以被引入、通知或者使用其他的Spring配置。


注入AspectJ切面

    虽然Spring AOP可以满足许多应用的切面需求,但是与AspectJ相比,功能仍旧显得弱,AspectJ提供了许多Spring AOP所不能支持的许多类型的切点。

    此处没有实现,我想在maven中创建aspect类似乎无法实现,希望有懂得大佬给我解释一下!!谢谢


   

    

    


    









    












    


    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值