面向切面的Spring(一)

AOP术语

如果要重用通用功能的话,常见的面向对象技术是继承或委托,但是如果在整个应用中都是用相同的基类,继承往往会导致一个脆弱的体系,而使用委托则有比较复杂的调用

切面,就是将关注的功能模块化为特殊的类,这类就是切面。


通知Advice: 切面中具体要做的事情,分为前置、后置、返回、异常、环绕通知。

连接点(Joint point): 执行过程中能够插入切面的一个点。

切点(Pointcur):如果说通知定义了切面具体做的事情,那么切点具体定义了在何处。切点匹配到所要织入的一个或多个连接点。

切面(Aspect): 在哪里,做什么事情,就是切点+通知

引入(Introduction): 在不改变现有的类的情况下,给现有的类添加新方法和属性。

织入(Weaving): 将切面应用到目标对象并创建新的代理对象的过程。创建代理对象的方式,jdk代理和cglib代理

,一般在三种情况下进行织入:

* 编译期 和 类加载期实现:需要特定的编译器和类加载器

* 运行期:这种是Spring AOP实现方式。

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

* 基于代理的经典Spring AOP

* 纯pojo切面

* @AspectJ注解驱动的切面

* 注入式Aspect切面(适合Spring各版本)

前面三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础上,Spring对AOP的支持仅限于方法拦截

通过切点来选择连接点

切点用于准确定位在什么地方应用切面的通知。在Spring AOP中,要使用AspectJ的切点表达式语言来定义切点。

AspectJ切点表达式定义Spring切面
AspectJ指示器 描述
arg() 限制连接点匹配参数为指定类型的执行方法 
@args() 限制连接点匹配参数有指定注解标注的执行方法
execution() 用于匹配是连接点的执行方法
this() 限制连接点匹配AOP代理的bean引用为指定类型的类
target() 限制连接点匹配目标对象为指定类型的类
@target 限制连接点匹配特定的执行对象,这些对象对应的类要具有执行类型的注解
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型
@annotation 限定匹配带有指定注解的连接点

这上面的Spring支持的指示器中,只有execution指示器是实际执行匹配的(编写切点时候最主要指示器),其他的指示器都是用来限制匹配的

编写切点

package com.bing.proxy;

public interface UserService {
	void sayAdd();
}

这个接口存在多个实现类,假设编写UserService在sayAdd()方法执行时候触发通知,使用AspectJ表达式来定义切点:

execution语法结构: 

execution(方法修饰符  方法返回值  方法所属类 匹配方法名 (方法中的形参表)方法申明抛出的异常)

例如:execution(** com.sample.service.impl..*.*(..))

第一个*表示方法不关心方法修饰符

第二个* 号表示不关心方法的返回值类型

..表示:当前包和子包

第三个*号表示所有类

.* 则是表示所有方法

(..)则是表示不关心方法的入参是什么

note:如果希望去使用多个AspectJ指示器定义切点,多个指示器之间可以使用&&,||,!标识逻辑操作

例如:execution(* com.bing.proxy.UserService.sayAdd(..))&&within(com.bing.proxy.*)

表示UserService的sayAdd方法执行时候触发通知,并且是连接点是在com.bing.proxy包下的类

在切点中选中Bean

Spring中引入一个指示器bean(),里面是beanID来标识bean

例如:execution(* com.bing.proxy.UserService.sayAdd(..))&&bean('userService')

表示类UserService的sayAdd方法执行时候触发通知,并且限制BeanID是userService

定义切面

package com.bing.proxy;

import org.aspectj.lang.annotation.AfterReturning;
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.bing.proxy.UserService.sayAdd(..))")
	public void performance(){}
	@Before("performance()")
	public void silencePhones(){
		System.out.println("silence cell phones .... ");
	}
	@Before("performance()")
	public void takeState(){
		System.out.println("take state  .... ");
	}
	@AfterReturning("performance()")
	public void appluase(){
		System.out.println("clap clap clap! ..");
	}
}

@Pointcut注解设置的值是一个切点表达式,对应的方法内容不重要。主要指的是切入的方法位置

@Before @After 表示方法是通知

@Aspect表示该类是一个切面,如何使得切面生效?

*JavaConfig中

需要在配置类里面加上注解@EnableAspectJAutoProxy,启动AspectJ代理,测试如下:

1. 创建UseService的一个实现类:

package com.bing.proxy;

public class UserServiceImpl implements UserService {
	@Override
	public void sayAdd() {
		System.out.println("----add-----");
	}

}

2. 创建JavaConfig的配置类

package com.erong.service;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Primary;

import com.bing.proxy.Audience;
import com.bing.proxy.UserService;
import com.bing.proxy.UserServiceImpl;
import com.erong.interface_.CompactDisc;
import com.erong.service.profile.MagicExistCondition;

@Configuration
@EnableAspectJAutoProxy //启用AspectJ自动代理
public class CDConfig {
	@Bean
	public Audience audience(){//创建切面的Bean
		return new Audience();
	}
	@Bean
	public UserService userService(){
		return new UserServiceImpl();
	}
}

3. 创建Junit测试类

package springDemo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.bing.config.ExpressiveConfig;
import com.bing.proxy.Audience;
import com.bing.proxy.UserService;
import com.erong.interface_.CompactDisc;
import com.erong.service.CDConfig;
import com.erong.service.CDPlayer;
import com.erong.service.CDPlayerConfig;
import com.erong.service.HelloWorld;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={CDConfig.class})
public class JavaConfigTest {
	@Autowired
	private UserService u;
	@Test
	public void test(){
		u.sayAdd();
	}
}

输出如下:

silence cell phones .... 
take state  .... 
----add-----
clap clap clap! ..

,至此完成了JavaConfig中切面的测试

* XML中生效AspectJ代理

   <!-- spring基于注解的配置 -->
   <context:annotation-config/>
   <!-- 启动AspectJ自动代理 -->
   <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
   <bean class="com.bing.proxy.Audience"></bean>
   <bean class="com.bing.proxy.UserServiceImpl"></bean>

切面类代码,不变

测试类修改如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value="classpath:aspectJ-test.xml")
public class CDPlayerTest {
	@Autowired
	private UserService us;
	@Test
	public void test(){
		us.sayAdd();
	}
}

另外关于环绕通知,需要注解@Around并在通知方法里面加上参数ProceedingJoinPoint ,给上面的Audience类加上方法

@Around("performance()")
public void round(ProceedingJoinPoint jp){
	try {
		System.out.println("silence cell phones .... ");
		System.out.println("take state  .... ");
		jp.proceed();
		System.out.println("clap clap clap! ..");
	} catch (Throwable e) {
		e.printStackTrace();
	}
}

注意,如果不调用proceed方法,被通知方法将不会执行

处理通知中的参数

package com.bing.proxy;

import java.util.HashMap;
import java.util.Map;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class TrackCounter {
	private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>();
	@Pointcut("execution(* com.bing.interface_.CompactDisc.playTrack(int)&&args(i))")
	public void trackPlay(int i){}
	@Before("trackPlay(i)")
	public void countTrack(int i){
		int currentCount = getPlayCount(i);
		trackCounts.put(i, currentCount+1);
	}
	public int getPlayCount(int count){
		return trackCounts.containsKey(count)?trackCounts.get(count):0;
	}
}

这里,将切点定位的方法的参数,传入到通知中,记录切点定位的方法不同参数时候的值。

关于AspectJ指示器:execution(* com.bing.interface_.CompactDisc.playTrack(int)&&args(i))

args中的参数名称和切点方法签名中的参数匹配

总的来说,Spring AOP 提供了给现有的方法增加额外的功能,另外,可以给现有的bean增加新的方法

* 给现有的对象添加新的方法

存在接口:

public interface Encoreable {
	void performEncore();
}

现在将这个接口应用到UserService实现中。可能存在不可能修改的实现类,直接实现接口UserService不是可行的解决方案。

借助Spring的引入功能,注解@DelareParent可以实现修改现有的对象(可以是所有接口的实现类),并加上新方法

需要引入接口的实现类的接口及实现类

public interface UserService {
	void sayAdd();
}
package com.bing.proxy;

public class UserServiceImpl implements UserService {
	@Override
	public void sayAdd() {
		System.out.println("----add-----");
	}
}

1. 创建Encoreable实现类:

public class DefaultEncoreable implements Encoreable{
	@Override
	public void performEncore() {
		System.out.println("encoreable........");
	}
}

2. 创建切面类

@Aspect
public class EncoreableIntroduce {
	@DeclareParents(value="com.bing.proxy.UserService+",
			defaultImpl=com.erong.service.DefaultEncoreable.class)
	public static Encoreable encoreable;
}

value属性:哪个类型的bean需要引入Encoreable接口,+号表示UserService的所有子类型

defaultImpl属性:指的是引入接口提供实现的类,其实就是默认引入对象的实现方法

3. xml:

   <!-- spring基于注解的配置 -->
   <context:annotation-config/>
   <!-- 启动AspectJ自动代理 -->
   <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
   <bean class="com.bing.aspect.EncoreableIntroduce"></bean>
   <bean class="com.bing.proxy.UserServiceImpl"></bean>

4. 测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value="classpath:aspectJ-test.xml")
public class CDPlayerTest {
	@Autowired
	private UserService us;
	@Test
	public void test(){
		us.sayAdd();
		Encoreable ea = (Encoreable) us;
		ea.performEncore();
	}
}

从输出,可以看出实现类已经成功引入接口

----add-----
encoreable........



猜你喜欢

转载自blog.csdn.net/ditto_zhou/article/details/80482946
今日推荐