文章目录
Spring Aop
基于AspectJ和基于schema的Aop命名的使用
在main方法中写上 System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);可以在com.sun.proxy路径下看到生成的代理类class
基本概念
- 连接点(Joinpoint) 所有可以执行额外代码的地方,比如方法前,方法后的时候
- 切入点(Pointcut): 从众多的连接点根据条件找到实际执行代码的地方的点
- 增强(Advice): 实际执行额外逻辑的代码
- 目标对象(targer) :被增强目标类
- 引介(Introduction):可以让目标类实现额外得方法,类似动态继承接口
- 织入(weaving):将增强加入到切点得这个过程。有编译器织入(AspectJ原生),装载器织入(AspectJ原生),动态代理织入(spring原生aop,spring后面融入AspectJ,但是底层还是动态代理)
- 代理(proxy):最后实际运行的类,功能和目标方法一样,只是加入了增强
- 切面(Aspect):切入点+增强
简单使用
目标对象的接口以及实现
public interface Hello {
void say();
String tell(String mes);
}
public class HelloImpl implements Hello {
@Override
public void say() {
System.out.println("hi");
}
@Override
public String tell(String mes) {
System.out.println(mes);
return mes;
}
}
实现一个切面
@Aspect
public class BeformAspect {
@Before("execution(* say(..))")
public void print()
{
System.out.println("before");
}
}
- @Aspect表示定位为一个切面
基于schema配置xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--自动注入切面-->
<aop:aspectj-autoproxy/>
<!--切面-->
<bean class="AspectJ.BeformAspect"/>
<!--目标类-->
<bean id="hello" class="AspectJ.HelloImpl"/>
</beans>
使用
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Hello hello = (Hello)applicationContext.getBean("hello");
hello.say();
AspectJ基本语法
切点表达式
在函数中使用通配符
符号 | 作用 |
---|---|
* | 匹配任意字符,但只能匹配上下文种的一个 |
…(两个点) | 匹配任意字符可以匹配上下文多个,但在表示类的时候,必须和*联合使用,表示入参单独使用 |
+ | 必须跟在类名后,代表类的子类,包括自己 |
不同的增强类型
注解 | 参数 | 作用 |
---|---|---|
@Before (前置) | value | 定义切点 |
argNames | 可以获得目标对象的参数,参数名要和目标的参数名一致,多个已逗号隔开 | |
@AfterReturning (后置) | value | 定义切点 |
pointcut | 显示定义将覆盖value,和value同义 | |
returning | 将目标的返回值绑定给增强方法 | |
argNames | 同前面一样 | |
@Around (环绕) | value | 定义切点 |
argNames | 同前面一样 | |
@AfterThrowing (异常) | value | 定义切点 |
pointcut | 同前面一样 | |
throwing | 将抛出的异常绑定到方法中 | |
argNames | 同前面一样 | |
@After (不管异常还是正常退出都会执行) | value | 定义切点 |
argNames | 同前面一样 | |
@DeclareParents (引介增强) | value | 定义切点 |
defaultImpl | 默认的接口实现类 |
引介方法例子
比较特殊不同于其他增强类型的使用,
将HelloImpl融入SellerImpl类
public interface Hello {
void say();
String tell(String mes);
}
public class HelloImpl implements Hello {
@Override
public void say() {
System.out.println("hi");
}
@Override
public String tell(String mes) {
System.out.println(mes);
return mes;
}
}
public interface Seller {
String sell(String goods);
}
public class SellerImpl implements Seller{
@Override
public String sell(String goods) {
System.out.println(goods);
return null;
}
}
介引切面
@Aspect
public class BeformAspect {
@DeclareParents(value = "AspectJ.HelloImpl",defaultImpl = SellerImpl.class)
public Seller seller;
}
- value 切点,找到需要介引的目标
- defaultImpl 找到需要增强的具体实现类
- 下面的变量为defaultImpl的接口
配置文件
<aop:aspectj-autoproxy />
<bean class="AspectJ.BeformAspect"/>
<bean id="hello" class="AspectJ.HelloImpl"/>
使用
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//可以将HelloImpl强转为Seller
Seller hello = (Seller)applicationContext.getBean("hello");
System.out.println(hello);
切点函数详解
@annotation
@Before("@annotation(AspectJ.Mytest)")
被注解标记的方法会执行切面注入
execution
通过方法签名定义
匹配 所有public的方法(其他修饰符不行),第一个*代表返回值 第二个代表方法名,(…)表示任意入参
@Before("execution(public * *(..))")
任意返回值,任意参数,方法名字以ll结尾的方法
@Before("execution(* *ll(..))")
通过类来定义
匹配Hello接口所有实现类,第一个*为返回值。(只包括接口定义的方法)
@Before("execution(* AspectJ.Hello.*(..))")
匹配Hello接口所有实现类,第一个*为返回值。(包括子类里不是接口的方法)
@Before("execution(* AspectJ.Hello+.*(..))")
通过类包来定义
这个包下的所有方法(如果子孙包中的类的父类或接口也在这个包下,也会触发)
@Before("execution(* AspectJ.*.*(..))")
这个包下的所有方法(包括子包孙包)
@Before("execution(* AspectJ..*.*(..))")
AspectJ包下的开头为x的包中类名以Son结尾,方法名为sa开头的所有方法
@Before("execution(* AspectJ.x*.*Son.sa*(..))")
通过方法入参来定义
匹配所有say方法,参数为两个,第一个类型为String
@Before("execution(* say(String,*))")
匹配所有say方法,参数为任意,第一个类型为String
@Before("execution(* say(String,..))")
匹配所有say方法,参数为一个,为Object以及其子类,(没有+号,代表只匹配object类型)
@Before("execution(* say(Object+))")
arg()和@arg()
arg()
根据入参的类型来匹配切点
args(Object) 等价于 execution(* (Object)) 也等价于 execution( *(Object+)) 也等价于args(Object+)
@arg()
该函数接受一个注解类的类名(接口无效)。当方法的运行时入参对象标注了指定的注解时,匹配切点。
mytest为自定义的注解
@Before("@args(Mytest)")
public void test()
{
System.out.println("test");
}
入参定义
public class helloSon {
public void say(T1 t)
{
System.out.println("helloSon");
}
}
参数类(T1 标注了自定义注解)
@Mytest
public class T1 {
}
public class T2 extends T1{
}
public class T3 extends T2{
}
public class T4 extends T3{
}
调用结果测试(注解没添加@Inherited)
- 如果方法定义入参为T1,T1也标注了注解,那么在实际传参时T2,T3,T4也会被捕获。
- 如果方法定义入参为T1,T2标注了注解,那么只有在实际传参为T2才会捕获。
- 如果方法定义入参为T2,T1标注了注解,那么实际传参T2,T3,T4 都不会生效
(注解添加了@Inherited)
- 只要实际传参的父类有注解都会生效
within()
execution 可以大到包,小到方法的定义,within等于简化版本,只能定义到类。
helloSon类所有方法被捕获
@Before("within(AspectJ.x.helloSon)")
AspectJ.x包下所有类被捕获(不包括子包,孙包。。)
@Before("within(AspectJ.x.*)")
AspectJ.x包下所有类被捕获(包括子包,孙包。。)
@Before("within(AspectJ.x..*)")
@within(),@target()
标注接口都不生效
被注解Mytest标注的类中所有方法被捕获
@Before("@target(Mytest)")
被注解Mytest标注的类(以及该类没有注解标记的子类)中所有方法被捕获
@Before("@within(Mytest)")
target(),this()
AspectJ进阶
切点的复合运算
例如
@Before("!@target(Mytest)")
@Before("@target(Mytest) || args(String,*)")
@Before("@target(Mytest) && args(String,*)")
切点命名,复用
切面1用@Pointcut定义了切点
@Aspect
public class BeformAspect {
@Pointcut("@target(Mytest)")
private void test()
{ }
@Pointcut("args(String,*)")
private void test1()
{ }
@Pointcut("test() || test1()")
public void cut()
{ }
}
切面2可以直接引用切面1定义好的切点
@Aspect
public class BeformAspect1 {
@Before("BeformAspect.cut()")
public void use()
{
System.out.println("hhhh");
}
}
在切面1中,切点方法的访问控制决定是否可以在其他切面中发现,也可以继承切面进行扩展。
增强织入的顺序
访问连接点信息
使用JoinPoint接口表示目标类连接点对象,如果是环绕增强,则使用ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口
JoinPoint
方法 | 作用 |
---|---|
Object[] getArgs() | 获取连接点方法运行时的入参列表 |
Signature getSignature() | 获取连接点的方法签名对象 |
Object getTarget() | 获取连接点所在的目标对象 |
Object getThis() | 获取代理对象本身 |
ProceedingJoinPoint 相对比 JoinPoint 多了两个方法
方法 | 作用 |
---|---|
Object proceed() | 通过反射执行目标连接点的方法 |
Object proceed(Object[] var1) | 通过反射执行目标连接点的方法,方法参数替换新的 |
@Aspect
public class TestAspect {
@Around("within(com.ke.AspectJ.Around)")
public void test(ProceedingJoinPoint pj) throws Throwable {
Object[] args = pj.getArgs();
System.out.println(Arrays.toString(args)); //获取调用目标方法的实际参数
System.out.println(pj.getSignature().getName());//获取目标方法的名字
System.out.println(pj.getTarget());//获取目标对象
System.out.println(pj.getThis());//获取代理之后的对象
System.out.println("before");
pj.proceed();//执行目标方法
System.out.println("after");
}
}
绑定连接点方法入参
args(),this(),target(),@args(),@within(),@target(),@annotation()这几个可以参数绑定到增强方法上
- agrs() 方法参数值
- @annotation() 可以获取注解的内容
- @args() 可以获取方法参数值上的注解
- 其他几个获取对应的目标对象
agrs()例子:
@Around("args(cyz)")
public void test(ProceedingJoinPoint pj,String cyz) throws Throwable {
System.out.println(cyz);
System.out.println("before");
pj.proceed();//执行目标方法
System.out.println("after");
}
public class Around {
public void say()
{
System.out.println("say");
}
public void say(String mes)
{
System.out.println(mes);
}
}
该切点匹配到public void say(String mes)方法,@Around(“args(cyz)”)的cyz可以根据test方法对应的cyz参数名判断出类型为String。在执行增强方法时,cyz就是目标方法的实际入参
@annotation()例子
@Aspect
public class TestAspect {
@Around("@annotation(cyz)")
public void test(ProceedingJoinPoint pj,cyz cyz) throws Throwable {
System.out.println(cyz.argNames());
System.out.println("before");
pj.proceed();//执行目标方法
System.out.println("after");
}
}
public class Around {
public void say()
{
System.out.println("say");
}
@cyz(argNames = "s")
public void say(String mes)
{
System.out.println(mes);
}
}
可以获取注解的内容
@args()例子:
@cyz(argNames = "CYZ")
public class T {
}
///
public class Around {
public void say()
{
System.out.println("say");
}
public void say(T mes)
{
System.out.println(mes);
}
}
@Aspect
public class TestAspect {
@Around("@args(cyz)")
public void test(ProceedingJoinPoint pj,cyz cyz) throws Throwable {
System.out.println(cyz.argNames());
System.out.println("before");
pj.proceed();//执行目标方法
System.out.println("after");
}
}
可以获取目标参数T类型上的注解内容
其余几个也类似,但是在增强方法中获取的对应的目标对象。
绑定返回值
//经过测试 cyz的类型如果和目标方法不一致,不会被后置增强捕获
@AfterReturning(value = "@args(Mytest)",returning = "cyz")
public void us1e(String cyz)
{
System.out.println(cyz);
}
@Around(value = "@args(Mytest)")
public void us1e(ProceedingJoinPoint j) throws Throwable {
//o就是返回值
Object o = j.proceed();
System.out.println(o+"s");
}
绑定抛出的异常
异常类型必须要和抛出的异常可以匹配上才会进这个增强,且在处理异常后依然会抛出异常
@AfterThrowing(value = "@args(Mytest)",throwing = "e")
public void use(Throwable e)
{
System.out.println(e.getMessage());
}
装载期织入切面
LWT织入切面是在装载class的时候就进行织入。前面的都是在运行期间生成代理对象织入