Spring4.1——AOP

1、 AOP框架的分类(按修改的时机来划分)

      (1)静态AOP实现:AOP框架在编译阶段即实现对目标类的修改(增强),生成静态的AOP代理类(生成*.class文件已经被改掉了,需要使用特定的编译器)。以AspectJ为代表。

      (2)动态AOP实现:AOP框架在运行阶段动态生成AOP代理(在内存中动态地生成AOP代理类:JDK动态代理或cglib),以实现对目标对象的增强。以Spring AOP为代表。

      性能上来看: 静态AOP实现的性能好。动态AOP实现需要在运行阶段额外地执行动态生成,有了运行开销。

      方便性来看: 动态AOP更方便,因为它不需要额外的编译器。

2、 AspectJ

 AspectJ是一个基于Java语言的AOP框架

下载和安装AspectJ步骤:

①在软件所在处打开cmd窗口,使用 java -jar aspectj-1.8.0.jar指令



②进入安装界面,安装即可


成功安装了AspectJ之后,将会在AspectJ的安装路径下看到如下文件结构:
       - bin:该路径下存放了aj、aj5、ajc、ajdoc、ajbrowser等命令,其中ajc命令最常用,它的作用类似于javac,用于对普通Java类进行编译时增强。
       - docs:该路径下存放了AspectJ的使用说明、参考手册、API文档等文档。
       - lib:该路径下的4个JAR文件是AspectJ的核心类库。
       - 相关授权文件。

③将...\aspectj1.8\bin路径添加到PATH环境变量中,
         将...\aspectj1.8\lib\aspectjrt.jar和...\aspectj1.8\lib\aspectjtools.jar添加到CLASSPATH环境变量中。

3、初试——通过记事本使用AspectJ

eg:

(1)创建三个类:

TargetOne.java

package cony.aspect.service;

public class TargetOne
{
	public void info(){
	System.out.println("目标方法1");
	}
}

TargetTwo.java

package cony.aspect.service;

public class TargetTwo
{
	public void info(){
	System.out.println("目标方法2");
	}
}

Main.java

package cony.aspect.service;
public class Main
{
	public static void main(String[] args){
	TargetOne one = new TargetOne();
	one.info();

	TargetTwo two = new TargetTwo();
	two.info();
	}
}
(2)

①使用

测试:


②使用后

增加一个类:

package cony.aspect;

public aspect AuthAspect
{
	before():execution(* cony.aspect.service.*.*(..))
	{
		System.out.println("====模拟执行权限检查====");
	}
}
测试:


此时会发现一个问题:多出了一个"====模拟执行权限检查====",这是因为执行切面的是cony.aspect.service包下面所有类的所有方法,也就是包括了Main类,所以,只要更改Main的包不与执行切面包相同即可。

更改后再次运行:


再增加一个around切面类

TxAspect.java

package cony.aspect;

public aspect TxAspect{

Object around():execution(* cony.aspect.service.*.*(..))
	{
		System.out.println("---模拟开启事务---");
		//回调目标方法
		Object rvt = proceed();
		System.out.println("---模拟关闭事务---");
		return rvt;
	}
}
测试:


4、AOP相关概念

    ① 切面(Aspect):业务流程运行的某个特定步骤,也就是应用运行过程的关注点,关注点可能横切多个对象,所以常常也称为横切关注点。

    ② 连接点(Joinpoint):程序执行过程中明确的点。如方法的调用,或者异常的抛出。Spring AOP中,连接点总是方法的调用。

    ③ 增强处理(Advice):AOP框架在特定的切入点执行的增强动作。处理有“around”、“before”和“after”等类型。

    ④ 切入点(Pointcut):可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点。

    ⑤ 引入(Introduction):为被修改的类添加方法或成员变量。Spring允许引入新的接口到任何被处理的对象。

    ⑥ 目标对象:被AOP框架进行增强处理的对象,也被称为被增强的对象。

    ⑦ AOP代理:AOP框架创建的对象,简单地说,代理就是对目标对象的加强。
             Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理。
             前者为实现接口的目标对象的代理,后者为不实现接口的目标对象的代理。

    ⑧  织入(Weaving):将增强处理添加到目标对象中、并创建一个被增强的对象(AOP代理)的过程就是织入。
            织入有两种实现方式:编译时增强(例如AspectJ)和运行时增强(例如CGLIB)。
            Spring和其他纯Java AOP框架一样,在运行时完成织入。

5、AOP编程

(1)在eclipse中使用AOP和aspect需要先导包


(2)步骤:
① 需要有目标方法

Dog.java

public class Dog {
	public void printDog(){
		System.out.println("======我是金毛犬======");
	}
}

User.java

public class User {
	public void printUser(){
		System.out.println("=======我是小米========");
	}
}

需要Advice

        i.定义Aspect类。为了把一个类变成aspect,有3种方式。
          A、注解   B、XML配置    C、将class改成aspect     
       ii.在Aspect类定义方法,并将该方法专程Advice,有3种方式。
          A、注解   B、XML配置    C、将方法签名改成特殊语法

③ 把advice放到目标方法的指定位置。
          通过pointcut或pointcut-ref属性来指定。

      用注解时要注意
      a、需要添加<aop:aspectj-autoproxy/>元素。
      b、强制Spring去处理@AspectJ的注解。

eg1——使用xml配置

aspect类:

public class AspectTest1 {

	public void beforeAdv(){
		System.out.println("---模拟开启事务---");
	}
}
Spring配置文件(需要导入aop命名空间):


测试:

		ApplicationContext appContext = new ClassPathXmlApplicationContext("beans.xml");
		User user = (User) appContext.getBean("user");
		Dog dog = (Dog) appContext.getBean("dog");
		user.printUser();
		dog.printDog();

eg2——使用注解配置:

aspect类:

@Aspect
public class AspectTest1 {
	//execution(* cony.domain.*.*(..))表示执行返回类型不限、cony.domain包下的任意类的任意方法形参不限
	@Before(value = "execution(* cony.domain.*.*(..))")
	public void beforeAdv(){
		System.out.println("---模拟开启事务---");
	}
}
Spring配置文件(需要导入context命名空间):


eg3——使用将class改成aspect

参考:3、初试——通过记事本使用AspectJ例子

6、Advice

  Advice一共分成5种:Before、AfterReturning、AfterThrowing、After、Around
     不管配置哪种Advice,都需要指定2个属性:
     - method:指定将哪个方法转换成Advice。如果用注解就不需要该属性,因此注解放在方法上。
     - pointcut或pointcut-ref:pointcut直接指定切入点表达式;pointcut-ref引用已有的切入点表达式。

(1)Before——在目标执行之前织入的advice。

(2)AfterReturning——在目标方法成功完成之后织入的advice。    
         可额外指定一个returning属性,该属性值有2个作用:
         - Advice方法可通过该属性指定的值来访问目标方法的返回值。
         - Advice方法声明该返回值类型时,限制目标方法的返回值必须是指定的类型。否则该Advice不会织入。
           如果不想对目标方法的返回值进行限制,可将该返回值声明为Object

eg:

 ①修改5中Dog类为:

public class Dog {
	public int printDog(){
		System.out.println("======我是金毛犬======");
		return 1;
	}
}
②新增一个aspect类

public class AspectTest2 {
	public void afterReturn(int rvt){
		System.out.println("---模拟记录日志---,返回值为"+rvt);
	}
}
Spring配置文件

<bean id="user" class="cony.domain.User"/>
<bean id="dog" class="cony.domain.Dog"/>
<bean id="aspect1" class="cony.aspect.AspectTest1"/>
<bean id="aspect2" class="cony.aspect.AspectTest2"/>

<aop:config>
	<aop:pointcut expression="execution(* cony.domain.*.*(..))" id="myPc"/>
	
	<aop:aspect ref="aspect1">
		<aop:before method="beforeAdv" pointcut-ref="myPc"/>
	</aop:aspect>
	
	<aop:aspect ref="aspect2">
		<aop:after-returning method="afterReturn"
			pointcut-ref="myPc" returning="rvt"/>	
	</aop:aspect>
</aop:config>
④测试:

		ApplicationContext appContext = new ClassPathXmlApplicationContext("beans.xml");
		User user = (User) appContext.getBean("user");
		Dog dog = (Dog) appContext.getBean("dog");
		user.printUser();
		dog.printDog();

注意:

(3)AfterThrowing——在目标方法出现异常时织入的advice。
         这种Advice类似于catch块。
      
         可额外指定一个throwing属性,该属性值有2个作用:
         - Advice方法可通过该属性指定的值来访问目标方法抛出的异常。
         - Advice方法声明该返回值类型时,限制目标方法的抛出的异常必须是指定的类型。否则该Advice不会织入。
           如果不想对目标方法抛出的异常进行限制,可将该异常声明为Exception或Throwable
eg:

①修改5中User类为:

public class User {

	public void printUser(){
		System.out.println("=======我是小米========");
	}

	public void deleteUser(Integer id){
		if(id < 0){
			throw new IllegalArgumentException("删除用户id不能小于0");
		}
		System.out.println("模拟删除用户:"+id);
	}
}
新增一个aspect类
public class AspectTest3 {
	public void repair(Throwable e){
		System.out.println("~~~模拟对异常的修复,异常为:"+e);
	}
}
Spring配置文件新增:
<bean id="aspect3" class="cony.aspect.AspectTest3"/>
<aop:config>
	<aop:pointcut expression="execution(* cony.domain.*.*(..))" id="myPc"/>
	<aop:aspect ref="aspect3">
		<aop:after-throwing method="repair" 
		pointcut-ref="myPc" throwing="e"/>
	</aop:aspect>
	
</aop:config>
④测试:

        ApplicationContext appContext = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) appContext.getBean("user");
        Dog dog = (Dog) appContext.getBean("dog");
        dog.printDog();
        user.printUser();
        user.deleteUser(-2);

(4)After——不管目标方法是成功完成,还是异常结束,该Advice都会织入
         这种Advice类似于finally块。

新增一个aspect类

public class ReleaseAspect {
	public void release(){
		System.out.println("---模拟对资源的释放---");
	}
}
Spring配置文件新增:

<bean id="aspect4" class="cony.aspect.ReleaseAspect"/>

<aop:config>
	<aop:aspect ref="aspect4">
		<aop:after method="release" pointcut-ref="myPc"/>
	</aop:aspect>
</aop:config>
③测试:

正常情况下测试:

        ApplicationContext appContext = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) appContext.getBean("user");
        Dog dog = (Dog) appContext.getBean("dog");
        dog.printDog();
        
        user.printUser();
        user.deleteUser(1);

异常情况下测试:

		ApplicationContext appContext = new ClassPathXmlApplicationContext("beans.xml");
		User user = (User) appContext.getBean("user");
		Dog dog = (Dog) appContext.getBean("dog");
		dog.printDog();
		
		user.printUser();
		user.deleteUser(-1);

(5)Around
         这种Advice功能最强大,它既可访问、修改目标方法调用参数,也可访问、修改目标方法的返回值。
         它甚至可以完全阻止目标方法的执行。
         它既可以在目标方法之前织入,也可以在目标方法之后织入。
         弱点:它是线程不安全,能用其他Advice的,就不要用Around advice

         Around advice方法必须满足如下3点:
         a. 必须声明返回值类型。
         b. 必须声明一个类型为ProceedingJoinPoint类型的形参。
         c. 必须调用ProceedingJoinPoint形参的proceed()方法,这就是回调目标方法。如果没有这一行,目标方法不会执行。
eg:

删除除了before的advice类,新增一个aspect类

public class TxAspect {

	/**
	 * 环绕通知使用 @Around 注解来声明。
	 * 通知的第一个参数必须是 ProceedingJoinPoint 类型。 
	 * 在通知体内,调用 ProceedingJoinPoint 的 proceed() 方法将会导致潜在的连接点方法执行。
	 */
	public Object aroundAdv(ProceedingJoinPoint jp) throws Throwable{
		System.out.println("###模拟开启事务###");
    	/**
    	 * ProceedingJoinPoint继承了JoinPoint,JoinPoint包含了如下方法
    	 * getArgs:获取目标方法调用参数
    	 * getKind:获取连接点的类型。
    	 * getSignature:获取目标方法的方法签名
    	 * getTarget:获取目标对象
    	 * getThis:获取AOP代理
    	 */
		Object[] args = jp.getArgs();
		// 如果调用参数至少有1个,且第一个参数为String类型
		if(args!=null && args.length>0 && args[0] instanceof String){
			args[0] = "前缀:"+args[0];
		}
		//---------- 以上在目标方法之前织入---------------
		Object rvt = jp.proceed(args); // 回调原来的目标方法,如果去掉此行,目标方法就不会执行
		//---------- 以下在目标方法之后织入---------------
		System.out.println("###模拟结束事务###");
		if(rvt!=null  && rvt instanceof Integer){
			return (Integer)rvt * (Integer)rvt;
		}
		return rvt;
	}
}

修改User类

public class User {

	public void addUser(String name){
		System.out.println("=======我是 "+name+"========");
	}

	public void deleteUser(Integer id){
		if(id < 0){
			throw new IllegalArgumentException();
		}
		System.out.println("模拟删除用户:"+id);
	}
}

修改Dog类

public class Dog {
	public Integer printDog(Integer id){
		System.out.println("======我是金毛犬======"+id);
		return 3;
	}
}

Spring配置文件新增:

<bean id="txAspect" class="cony.aspect.TxAspect"/>

<aop:config>
	<aop:pointcut expression="execution(* cony.domain.*.*(..))" id="myPc"/>
	
        <aop:aspect ref="txAspect">
		<aop:around method="aroundAdv" pointcut-ref="myPc"/>
	</aop:aspect>
	
</aop:config>
④测试:

		ApplicationContext appContext = new ClassPathXmlApplicationContext("beans.xml");
		User user = (User) appContext.getBean("user");
		Dog dog = (Dog) appContext.getBean("dog");
		System.out.println(dog.printDog(2));
		System.out.println();
		user.addUser("coco");;
		user.deleteUser(1);

7、 访问目标方法的调用参数

(1)通过为Advice方法增加一个JoinPoint类型的形参。
           JoinPoint有个getArgs()即可获取所有形参。


(2)通过args切入点指示符。
            args(a,..)  —— 它还会对目标方法的形参类型进行限制。

eg:

①aspect类

public class AspectTest1 {
	// 如果你在args(a,..)指定什么参数,此处就可通过它们来访问目标方法调用参数
	public void beforeAdv(String a,String b){
		System.out.println("---模拟开启事务---,第一个参数为"+a+";第二个参数为"+b);
	
	}
}
Spring配置文件
<bean id="user" class="cony.domain.User"/>
<bean id="dog" class="cony.domain.Dog"/>
<bean id="aspect1" class="cony.aspect.AspectTest1"/>

<aop:config>
	<aop:pointcut expression="execution(* cony.domain.*.*(..)) and args(a,b,..)" id="myPc"/>
	
	<aop:aspect ref="aspect1">
		<aop:before method="beforeAdv" pointcut-ref="myPc"/>
	</aop:aspect>
	
</aop:config>

User.java

public class User {

	public void addUser(String name,String pass){
		System.out.println("=======添加用户========"+name);
	}

	public void deleteUser(Integer id){
		System.out.println("模拟删除用户:"+id);
	}
}
Dog.java

public class Dog {
	public int printDog(){
		System.out.println("======我是金毛犬======");
		return 1;
	}
}
④测试

		ApplicationContext appContext = new ClassPathXmlApplicationContext("beans.xml");
		Dog dog = (Dog) appContext.getBean("dog");
		dog.printDog();
		
		User user = (User) appContext.getBean("user");
		user.addUser("小米","123");
		user.deleteUser(1);

由结果可以看到只有addUser方法被织入了,因为在aspect类中规定两个参数都是String类型,而且在 Spring配置文件中规定参数为两个及以上。

8、 AOP的切入点指示符

格式:execution([访问权限] 返回值类型 包.类.方法(形参) [throws 异常])

默认情况下,都可用*作为通配符。形参列表支持2个通配符, ..代表任意个任意类型的参数; *代表一个任意类型的参数。

eg:

(*,java.lang.String) 2个形参,且第二个形参必须是String
(..,java.lang.String) 1~N个形参,最后一个形参必须是String
(*, ..) 1~N个任意类型的形参。
(..) 0~N个任意类型的形参。

target(类型) ——要求目标对象必须是指定类型。


this(类型) —— 要求AOP代理对象必须是指定类型。
args(a,b) —— 要求目标方法必须有匹配的形参。
bean(beanid) ——  只为特定Bean的所有方法织入增强处理。原生AspectJ并不支持,只有用Spring才支持。


切入点表达式还支持 and、or、not这3个逻辑运算符。

猜你喜欢

转载自blog.csdn.net/ack_finding/article/details/78957487