一.aop术语
aop概念:aop相对于oop来说,oop是面向对象编程,万物皆对象,我们很多个对象组成一个controller,组成一个model,组成一个mapper,然后不同的层之间相互调用,在这些调用之间,肯定会有一些非常相似的代码,散落在每个角落,我们aop需要做的就是把这些代码维护起来,放到一个统一的切面中。
1.连接点
连接点是一个表达式,这个表达式一写出来,那就是可以匹配到一个或者多个点。所以连接点是连接表达式锁表达出来的一个一个点。
2.切点
切点其实就是一系列连接点的集合,切点=连接点表达式,但切点!=连接点。切点=连接点的集合
3.目标
要被代理的对象成为目标
4.织入
把代理逻辑加入到目标对象上的过程叫做织入
5.切面
所有的切点和通知,称之为一个切面
6.通知
在切点的基础上,设置一个位置,意识就是需要在这个切点的前后左右环绕去做aop。
advice通知类型:
Before 连接点执行之前,但是无法阻止连接点的正常执行,除非该段执行抛出异常
After 连接点正常执行之后,执行过程中正常执行返回退出,非异常退出
After throwing 执行抛出异常的时候
After (finally) 无论连接点是正常退出还是异常退出,都会执行
Around advice: 围绕连接点执行,例如方法调用。这是最有用的切面方式。around通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续加入点还是通过返回自己的返回值或抛出异常来快速建议的方法执行。
二.实操
1.springAop和aop的关系?
aop是一种思想,aop可以有很多种实现方式,springAop是Aop的一个实现方式,Aaspectj也是一种实现方式。
2.spring和aspjectj关系
spring借助了aspectj的语法,但是底层还是spring自己的处理逻辑。借用了spring的一个注解。所以我们的程序首先要支持aspectj,然后才能使用aspectj语法,再而才能让spring来处理aspectj的语法。
3.开始
3-1.先建个工程,引入依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
</dependencies>
3-2.创建一个对应的javaconfig
主配置类
被代理类
aspectj类,可以是javaconfig或者是xml
启动,测试
总结,一个连接点表达式组成一个切点,一个切点需要和一个通知做关联才有执行的意义。
/**
* 申明Aspect,并且交给spring容器管理
*/
@Component
@Aspect
public class UserAspect {
/**
* 申明切入点,匹配UserDao所有方法调用
* execution匹配方法执行连接点
* within:将匹配限制为特定类型中的连接点
* args:参数
* target:目标对象
* this:代理对象
*/
@Pointcut("execution(* com.yao.dao.UserDao.*(..))")
public void pintCut(){
System.out.println("point cut");
}
/**
* 申明before通知,在pintCut切入点前执行
* 通知与切入点表达式相关联,
* 并在切入点匹配的方法执行之前、之后或前后运行。
* 切入点表达式可以是对指定切入点的简单引用,也可以是在适当位置声明的切入点表达式。
*/
@Before("com.yao.aop.UserAspect.pintCut()")
public void beforeAdvice(){
System.out.println("before");
}
}
4.切点声明所支持的表达式
先来看看所有的表达式:
execution:粒度控制最细的连接点控制,可以匹配到任意方法,任意包。execution的表达式规则如下:
注意,public要么不写,要么写,不能写*来代替public/private等
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下
modifiers-pattern:方法的可见性,如public,protected;
ret-type-pattern:方法的返回值类型,如int,void等;
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:方法的参数类型,如java.lang.String;
throws-pattern:方法抛出的异常类型,如java.lang.Exception;
example:
@Pointcut("execution(* com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和类的任意方法
@Pointcut("execution(public * com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和类的public方法
@Pointcut("execution(public * com.chenss.dao.*.*())")//匹配com.chenss.dao包下的任意接口和类的public 无方法参数的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))")//匹配com.chenss.dao包下的任意接口和类的第一个参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(public * *(..))")//匹配任意的public方法
@Pointcut("execution(* te*(..))")//匹配任意的以te开头的方法
@Pointcut("execution(* com.chenss.dao.IndexDao.*(..))")//匹配com.chenss.dao.IndexDao接口中任意的方法
@Pointcut("execution(* com.chenss.dao..*.*(..))")//匹配com.chenss.dao包及其子包中任意的方法
关于这个表达式的详细写法,可以脑补也可以参考官网很容易的,可以作为一个看spring官网文档的入门,打破你害怕看官方文档的心理,其实你会发觉官方文档也是很容易的
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples
within:匹配到对应的方法,不考虑入参。粒度比execution大了一些,匹配到任意的类,但是无法匹配指定的方法.
™表达式的最小粒度为类
// ------------
// within与execution相比,粒度更大,仅能实现到包和接口、类级别。而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等
@Pointcut("within(com.chenss.dao.*)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao..*)")//匹配com.chenss.dao包及其子包中的任意方法
args
args的就指定参数类型和个数,运行时会自动匹配,他与execution不同之处在于,execution在生命时能确定,而args在运行时才回去确定参数类型和个数。哪怕你有一万个类,他也会一个一个类去匹配里面对应的方法的参数类型和参数个数
args表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关
/**
* args同execution不同的地方在于:
* args匹配的是运行时传递给方法的参数类型
* execution(* *(java.io.Serializable))匹配的是方法在声明时指定的方法参数类型。
*/
@Pointcut("args(java.io.Serializable)")//匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配
@Pointcut("@args(com.chenss.anno.Chenss)")//接受一个参数,并且传递的参数的运行时类型具有@Classified
通知时使用表达式:
@annotation
表示加了这个注解的方法是一个切点,如下:所有加了该注解的方法都会被执行通知
@args
和args比较,粒度更加细致把,args只能匹配java自带的数据类型,而用了@args,我们可以匹配很多自定的类型
1.声明一个自定义注解
2.把这个注解拿去修饰某个类,我们假设为A
3.然后我们给类B的某个方法的入参设置为A类型,这样的话,类B的入参为A类型的所有方法都能够被增强
@Component
@PrintArgs(name = "parmentModel")
public class ParentModel
{
Logger logger = LoggerFactory.getLogger(ParentModel.class);
public void methodA(String hello)
{
logger.info("parmentModel method");
}
}
目标类
@Component
public class ArgsModel
{
Logger logger = LoggerFactory.getLogger(ArgsModel.class);
public void testArgs(ParentModel parentModel)
{
logger.info("入参为{}",parentModel);
}
}
切面类
@Component
@Aspect
@Order(-1)
public class ArgsAspect
{
Logger logger = LoggerFactory.getLogger(ArgsAspect.class);
@Before("@args(PrintArgs)")
public void printArgs(JoinPoint joinPoint)
{
logger.info("{},此方法入参数标注了PrintArgs注解",joinPoint.getSignature());
}
}
主函数:
public class ArgsAspectTest
{
static Logger logger = LoggerFactory.getLogger(ArgsAspect.class);
public static void main(String[] args)
{
ApplicationContext context = new ClassPathXmlApplicationContext("spring-args.xml");
ArgsModel argsModel = (ArgsModel)context.getBean("argsModel");
ParentModel parentModel = (ParentModel)context.getBean("parentModel");
argsModel.testArgs(parentModel);
}
}。
@within
和within相比,好像没什么卵用,within是直接指定一个类名。而@within还要搞一个注解,把这个注解加到某个类上面,才会生效,和within对比,最大的一个特点就是解耦把,你用了within,如果不想用,还需要把对应的代码注释掉。而使用了@within,只需要去掉注解即可,代码修改量少且侵入性地。
1.生命一个自定义注解,我们假设为A
2.声明一个类B,B里面有query方法,我们给类B加上A注解
3.从spring拿到类B,执行query方法,发现会被增强
package com.luban.test;
import com.luban.app.AppConfig;
import com.luban.dao.Dao;
import com.luban.dao.IndexDao;
import com.luban.dao.IndexDaoWithin;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext(AppConfig.class);
IndexDaoWithin dao = annotationConfigApplicationContext.getBean(IndexDaoWithin.class);
dao.query();
}
}
package com.luban.app;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LuBanAspectj {
@Pointcut("execution(* com.luban.dao.*.*(..))")
public void pointCut(){
}
@Pointcut("@within(com.luban.anno.cjf)")
public void pointWithin(){}
// @Before("pointCut()")
// public void before(){
// System.out.println("before");
// }
@Before("pointWithin()")
public void before(){
System.out.println("before");
}
}
package com.luban.anno;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface cjf {
}
package com.luban.app;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com")
@EnableAspectJAutoProxy
public class AppConfig {
}
package com.luban.dao;
import com.luban.anno.cjf;
import org.springframework.stereotype.Repository;
@Repository
@cjf
public class IndexDaoWithin {
public void query(){
System.out.println("query");
}
}
jdk动态代理和gclib代理
@EnableAspectJAutoProxy(proxyTargetClass = false) => jdk动态代理
@EnableAspectJAutoProxy(proxyTargetClass = true) => gclib
jdk动态代理 => 不能转为原来的对象,但是能转成接口 =>
如下图,我们使用一个接口,利用jdk的一个方法来生成一个对象
可以看到,这个生成的对象继承了Proxy和Dao。
这里我们进行一下迁移 => 假设我们现在有一个类A,类A实现了接口B => class A implements B
然后我们把A交给spring管理 => spring使用jdk动态代理生成的对象结果为:
class 新对象 extends proxy implements B => 这个新的对象拥有A的所有内容,但是它又不是A,继承了proxy是为了提高复用性吧,实现B是为了保持原有的状态。
所以我们把新对象取出来之后,想要强转成A,是不能的,因为它和A已经没有半毛钱关系了。转成proxy倒是可以。
gclib动态代理 => 能转为原来的对象 => 因为gclib继承了目标对象。所以gclib能够转为原来的对象。
总结:jdk基于继承,gclib基于接口。jdk搞出来的代理对象不等于原来的对象,cglib
this => 匹配当前增强后的对象是不是括号里面的。
target
增强前的对象,是不是target里面的,如果是,则进行增强。
@target
实际上他和@within差不多,
@target(注解A)
public abc(){}
@Before(abc()的类权限定名)
@注解A
class B{}
那么B的所有方法就会被增强