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指示器 | 描述 |
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........