这儿提供一个我实验的源代码地址(有注释,如有错误,欢迎留言^_^,实验是检验真理的唯一标准):https://github.com/flycat0112/springArt
二、AOP(对应项目的模块是spring-aop)
spring-aop的jar包中提供两种aop方式,一种是AspectJ的织入方式使用的cglib,也是asm(有些博客说的是静态织入,我保持疑问,因为我实验打开class文件没有发现改变,我看一个人的博客说加强类有两种方式,一种是在编译时期可以直接产生二进制 class 文件,另一种可以在类被加载入 Java 虚拟机之前动态改变类行为,因为一个项目的类很多,不可能位每个类直接产生class文件,我想cglib使用的是第二种。等待我spring研究好了去研究这个^_^),第二种是spring-aop中国spring采用的jdk的动态代理。
Ⅰ、AspectJ(对应项目的模块是spring-aop-aspectj):
1、使用注解方式:
1) 在spring中开启AspectJ
在@Configuration配置中添加@EnableAutoConfiguration注解开启。
@Configuration
@EnableAspectJAutoProxy
@EnableAutoConfiguration
@ComponentScan
public class App
{
public static void main(String... args) {
SpringApplication.run(App.class, args);
}
}
2) 声明切入类:
在类上面使用@Aspect注解。
3) 定义切入点:(通用的advice切入点定义,自己只能被advice来触发,自己单独不能存在)
在无参方法上面标记@Pointcut注解。
4) 定义advice:(可以使用已经定义好的切入点对象,也可以直接写表达式)
注解有:
@Before 在调用方法之前调用
@After 在before执行后,方法体执行完成后,afterReturning和afterThrowing执行前。
@AfterReturning 和afterThrowing执行的互斥的,当方法体执行完成并且没有抛出异常的时候执行
@AfterThrowing 和afterReturning执行的互斥的,当方法体执行完成并且有抛出异常的时候执行
@Around 环绕是特殊的,它可以拦截该方法调用,还可以对执行的结果值进行处理,还可以选择是否抛出异常与否
ps : Around的执行顺序也是特别的,它进入在before切点前面执行,因为它有可以选择是否执行该方法, *但是方法体执行完成的后,它是在after切点的前面,因此,方法体内部是否处理异常会改变AfterReturning或者AfterThrowing两个,那个切点执行。
实例Demo部分代码:
IPerson.java
public interface IPerson {
String getName();
void setName(String name);
boolean isSex();
void setSex(boolean sex);
}
Person.java
/**
* @FileName: <p>Person</p>
* @Description: <p>IPerson接口的实现</p>
* @Author <p>flycat</p>
* @Date <p>18-9-7</p>
*/
@ManagedBean
public class Person implements IPerson{
private String name;
private boolean sex;
@Override
public String getName() {
System.out.println("执行了getName");
return this.name;
}
@Override
public void setName(String name) {
System.out.println("执行了setName");
this.name = name;
}
@Override
public boolean isSex() {
System.out.println("执行了isSex");
return sex;
}
@Override
public void setSex(boolean sex) {
//判断当强值是否和设置值相同,如果相同抛出异常
//代理目标内部方法调用是无法感知的
//通过从AopContext中获取代理对象的上下文获取当强的代理对象来调用isSex方法。
//这儿需要注意,设置允许暴露代理对象,可以让AopContext获取。@EnableAspectJAutoProxy(exposeProxy = true)
if(null != AopContext.currentProxy()
&& sex == ((Person)AopContext.currentProxy()).isSex()){
throw new RuntimeException("值已经存在!");
}
this.sex = sex;
System.out.println("执行了setSex");
}
}
AopAspect.java
/**
* before : 在调用方法之前调用
* After : 在before执行后,方法体执行完成后,afterReturning和afterThrowing执行前。
* AfterReturning : 和afterThrowing执行的互斥的,当方法体执行完成并且没有抛出异常的时候执行
* AfterThrowing : 和afterReturning执行的互斥的,当方法体执行完成并且有抛出异常的时候执行
*
* Around : 环绕是特殊的,它可以拦截该方法调用,还可以对执行的结果值进行处理,还可以选择是否抛出异常与否
* ps : Around的执行顺序也是特别的,它进入在before切点前面执行,因为它有可以选择是否执行该方法,
* 但是方法体执行完成的后,它是在after切点的前面,因此,方法体内部是否处理异常会改变AfterReturning或者AfterThrowing两个,那个切点执行。
*/
@ManagedBean
@Aspect
public class AopAspect {
public AopAspect() {
System.out.println("初始化了!");
}
@Pointcut("execution(* flycat.domain.IPerson.getName(*))")
public void getName(){
}
@Pointcut("execution(* flycat.domain.Person.setName(*))")
public void setName(){
}
@Pointcut("execution(* flycat.domain.Person.setSex(*))")
public void setSex(){
}
@Before("getName()")
public void aspectBefore3(){
System.out.println("代理对象的getName方法调用了!");
}
@Before("setName()")
public void aspectBefore(){
System.out.println("切面Before调用了!setName");
}
@Before("setSex()")
public void aspectBefore1(){
System.out.println("切面Before调用了!setSex");
}
@AfterReturning("setSex()")
public void aspectAfterReturning(){
System.out.println("切面AfterReturning调用了有异常的方法!");
}
@AfterReturning("setName()")
public void aspectAfterReturning1(){
System.out.println("切面AfterReturning调用了无异常的方法!");
}
@AfterThrowing("setSex()")
public void aspectAfterThrowing(){
System.out.println("切面AfterThrowing调用了有异常的方法!");
}
@AfterThrowing("setName()")
public void aspectAfterThrowing1(){
System.out.println("切面AfterThrowing调用了没有异常的方法!");
}
@After("setSex()")
public void aspectSetName(){
System.out.println("切面After调用了有异常的方法!");
}
@After("setName()")
public void aspectSetName1(){
System.out.println("切面After调用了无异常的方法!");
}
@Around("setSex() && args(name,..) && target(flycat.domain.IPerson) && this(flycat.domain.IPerson)")
public Object aspectAround(ProceedingJoinPoint pjp, boolean name){
Object[] obj = {name};
System.out.println("切面Around调用了有异常的方法!");
try {
Object o = pjp.proceed(obj);
return o;
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("fasdfasdf");
return null;
}
}
@Around(value = "setName() && args(name,..) && target(flycat.domain.IPersonOther) && this(flycat.domain.IPerson)")
public Object aspectAround1(ProceedingJoinPoint pjp, String name){
Object[] obj = {name};
System.out.println("切面Around调用了无异常的方法!");
try {
Object o = pjp.proceed(obj);
return o;
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("sdfasdfasd");
return null;
}
}
}
5) Spring AOP支持以下AspectJ切入点指示符(PCD)用于切入点表达式:
execution : 对于匹配方法执行连接点,这是您在使用Spring AOP时将使用的主要切入点指示符
within : 限制匹配某些类型中的连接点(只是在使用Spring AOP时执行在匹配类型中声明的方法)
this : 限制匹配连接点(使用Spring AOP时执行方法),其中bean引用(Spring AOP代理)是给定类型的实例,(表示生成的代理对象)
target : 限制匹配到连接点(使用Spring AOP时执行方法),其中目标对象(被代理的应用程序对象)是给定类型的实例(表示原目标对象)
args : 限制匹配连接点(使用Spring AOP时执行方法),其中参数是给定类型的实例
@target : 限制匹配连接点(使用Spring AOP时执行方法),其中执行对象的类具有给定类型的注释
@args : 限制匹配到连接点(使用Spring AOP时执行方法),其中传递的实际参数的运行时类型具有给定类型的注释
@within : 限制匹配以连接具有给定注释的类型中的点(使用Spring AOP时在具有给定注释的类型中声明的方法的执行)
@annotation : 限制匹配到连接点的主题,其中连接点的主题(在Spring AOP中执行的方法)具有给定的注释
ps :
1、代理目标内部方法调用是无法感知的,有时候我们需要内部方法调用代理对象能够感知,可以通过从AopContext中获取代理对象的上下文获取当强的代理对象来调用相应的方法。其实就是内部调用不会经过代理对象,通过AopContext获取代理对象就是让内部调用经过代理对象,虽然这样设计性能下降,但是代码维护很好。这儿需要注意,设置允许暴露代理对象,可以让AopContext获取。@EnableAspectJAutoProxy(exposeProxy = true)
2、target和this分别表示“目标对象”和“代理对象”,他们的功能只是为了限制匹配的对象,想要体现出他们的不同必须要目标对象是通过代理织入另一个接口,不能是简单的实现。
通过代理的demo代码:(从上面新增的类)
IPersonOther.java
/**
* @Title: <p>IPersonOther</p>
* @Description: <p>这个来区别target和this的区别</p>
* @Company: <p></p>
* @Author <p>zengqiang</p>
* @Date <p>18-9-7</p>
*/
public interface IPersonOther {
void isBoli();
}
PersonOther.java
/**
* @FileName: <p>PersonOther</p>
* @Description: <p>这个来区别target和this的区别,</p>
* @Author <p>flycat</p>
* @Date <p>18-9-7</p>
*/
public class PersonOther implements IPersonOther{
@Override
public void isBoli() {
System.out.println("使用了isBoli方法,你是玻璃!");
}
}
AddPersonOtherForPersonAspect.java
/**
* @FileName: <p>AddPersonOtherForPersonAspect</p>
* @Description: <p>通过代理的方式方Person实现IPersonOther接口,这儿是为了区别target和this</p>
* <p> 因为这儿是通过代理的方式,target代表的是目标,这儿的目标代表的是IPersonOther接口,所以target就会把它过滤掉
* ,this代表的代理对象,属于IPersonOther和IPerson两个接口的结合,所以this就不会被过滤掉 </p>
* @Author <p>flycat</p>
* @Date <p>18-9-7</p>
*/
@ManagedBean
@Aspect
public class AddPersonOtherForPersonAspect implements Ordered {
@DeclareParents(value = "flycat.domain.Person", defaultImpl = PersonOther.class)
IPersonOther iPersonOther;
@Override
public int getOrder() {
return 2;
}
}
AopAspect1.java
@ManagedBean
@Aspect
public class AopAspect1 implements Ordered {
@Pointcut("execution(* flycat.domain.IPerson.*(..))")
public void executionFlag(){
}
@Pointcut("target(flycat.domain.IPersonOther)")
public void targetFlag(){
}
@Pointcut("this(flycat.domain.IPersonOther)")
public void thisFlag(){
}
@Before("executionFlag() && targetFlag()")
public void getNameTarget(){
System.out.println("该切点属于IPerson和IPersonOther并且是Target(目标对象)");
}
@Before("executionFlag() && thisFlag()")
public void getNameThis(){
System.out.println("该切点属于IPerson和IPersonOther并且是This(代理对象)");
}
@Before("targetFlag()")
public void getNameTarget1(){
System.out.println("该切点属于IPersonOther并且是Target(目标对象)");
}
@Before("thisFlag()")
public void getNameThis1(){
System.out.println("该切点属于IPersonOther并且是This(代理对象)");
}
@Override
public int getOrder() {
return 1;
}
}
PerformanceAction.java
@RestController
@RequestMapping("/springArt")
@RequestScope
public class PerformanceAction {
@Inject
IPerson person;
@GetMapping(value = "/{id}")
public ModelAndView performance(@PathVariable("id") String id){
person.setName(id);
person.setSex(true);// 这儿第一次执行传入false就会发生错误,传入true不会发生错误
person.getName();
((IPersonOther)person).isBoli(); //用来测试target和this的区别(target会有表现,会拦截,this却不会)
return new ModelAndView("index");
}
}
最后执行完打印信息是:
该切点属于IPerson和IPersonOther并且是This(代理对象)
该切点属于IPersonOther并且是This(代理对象)
执行了setName
该切点属于IPerson和IPersonOther并且是This(代理对象)
该切点属于IPersonOther并且是This(代理对象)
该切点属于IPerson和IPersonOther并且是This(代理对象)
该切点属于IPersonOther并且是This(代理对象)
执行了isSex
执行了setSex
该切点属于IPerson和IPersonOther并且是This(代理对象)
该切点属于IPersonOther并且是This(代理对象)
执行了getName
该切点属于IPersonOther并且是Target(目标对象)
该切点属于IPersonOther并且是This(代理对象)
使用了isBoli方法,你是玻璃!
结论: 通过代理的方式实现IPerson接口,并且target代表的是目标,这儿的目标代表的是IPersonOther接口,由@DeclareParents注释下方变量声明,本列子是IPersonOther, 所以target就会把它过滤掉* ,this代表的代理对象,属于IPersonOther和IPerson两个接口的结合,所以this就不会被过滤掉 。
2、通过配置方式实现AspectJ这儿就不用写放代码的,需要看github(对应项目spring-aop-aspectj-xml)(不建议这样做,太复杂了)
Ⅱ、spring aop方式(对应项目的模块是spring-aop-springAOP)
aspectj的@DeclareParents注解可以让代码非侵入式的方式扩展类的方法,而spring AOP提供的是通过IntroductionAdvisor设置那些需要功能增强,保存IntroductionInterceptor的实现(里面理解调用的时候有个“拦截器”,该拦截器判断当前调用的方法是否是增强的接口的方法,调用代理的方法,如果不是,通过委托调用目标类的方法。)
pointcut 切点(通常针对的对象是方法级别的)
advice 增强(就会对原有的功能进行添加),如果事实应用中没有和pointcut进行绑定,相当于拦截器的作用。
advisor 代表的切面(里面包含一个advice)
Interceptor 拦截器 (源代码里面继承自advice)
advisor中有IntroductionAdvisor和PointcutAdvisor两种。
IntroductionAdvisor 针对的是对类里面的功能增强,通过接口在类中增加方法,但是它是通过advice隐士增强的,不是通过cglib,而是jdk的代理实现,原有的类没有发生改变。想要实现需要和IntroductionInterceptor配合使用。
PointcutAdvisor 切点增强,主要是针对的方法级别,该接口继承advisor,另外多了pointcut方法,一个PointcutAdvisor里面只有一个pointcut和一个advice。pointcut是为了筛选那些类,那些方法。advice里面有around,before,afterReturn,afterThrowing表示侵入方法那个位置。
ps:spring aop中和around环绕通过MethodInterceptor实现,aspectj中afterThrowing在spring aop中是ThrowsAdvice是不同的,它的接口没有定义一个方法,但是spring源码里面是通过反射会调用,你必须手动打这些方法。after么
afterThrowing([Method, args, target], subclassOfThrowable)