记录spring AOP的一些知识

     上篇博客我们简单的了解了Spring IOC的一些知识点,接下来让我们再学习一下AOP相关的知识点。

一、什么是AOP

     AOP相对于OOP而言,是面向切面(Aspect Oriented Programming)的,传统的OOP开发中的代码逻辑是至上而下的,在这些至上而下的过程中会产生一些横切性的问题,这些横切性的问题和我们的主业务逻辑关系不大,会散落在代码的各个地方,造成难以维护,AOP的编程思想就是把业务逻辑和横切的问题进行分离,从而达到解耦的目的,使代码的重用性和开发效率高。

1、应用场景

      (1)日志记录

      (2)权限验证

      (3)效率检查

      (4)事务管理

3、aop,spring aop、aspectj的关系

    aop是一种思想,spring aop、aspectj都是实现aop的技术。

4、spring aop 中为什么还要使用aspcetj? spring aop 也有自己的一套语法,但是相当复杂,难以令人接受,因此spring aop借助了aspectj的语法,但是底层还是用的自己的技术(通过源码分析了,我们可以知道spring底层使用的是JDK或者CGLIB来完成的代理)。

5、启用aspectj支持的两种方式

二、Spring AOP的一应用

 aspect:一定要给spring去管理,是 抽象的概念      在类中用aspectj表示,在xml中用标签label表示。

pointcut:切点表示连接点的集合 -------------------> 表

Joinpoint:连接点 目标对象 中的方法 ----------------> 记录

Weaving :把代理逻辑加入到目标对象上的过程叫做织入

target: 目标对象 原始对象

aop Proxy: 代理对象 包含了原始对象的代码和增强的代码的那个对象

advice:通知 由需要放入逻辑代码块的切面内容  和 切面放入逻辑代码的位置 组成   

通知类型: Before  、After 、 After throwing 、After (finally) 、Around advice:

三、各种连接点joinPoint的意义

1、execution

      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: execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))

      这是一个公共的方法 任意返回类型 包下面的BusinessObject类,businessService()方法,方法参数至少一个并且第一个为String类型的 。

      关于这个表达式的详细写法,可以脑补也可以参考官网很容易的,可以作为一个看spring官网文档的入门,打破你害怕看官方文档的心理,其实你会发觉官方文档也是很容易的: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-exampl

注:由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的信息,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的

2、within

    within表达式的最小粒度为类,其参数为全路径的类名(可使用通配符),表示匹配当前表达式的所有类都将被当前方法环绕。例如 (IndexDao为类):@Pointcut("within(com.zlu.IndexDao*)")

3、args

    args表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关。例如:@Pointcut("args(String)")

4、this 和target

       this和target需要放在一起进行讲解,主要目的是对其进行区别。this和target表达式中都只能指定类或者接口,在面向切面编程规范中,this表示匹配调用当前切点表达式所指代对象方法的对象,target表示匹配切点表达式指定类型的对象。比如有两个类A和B,并且A实现了B接口,如果切点表达式为this(B),那么B的实例将会被匹配;如果这里切点表达式为target(B),那么B的实例也即被匹配。

       在讲解Spring中的this和target的使用之前,首先需要讲解一个概念:业务对象(目标对象)和代理对象。对于切面编程,有一个目标对象,也有一个代理对象,目标对象是我们声明的业务逻辑对象,而代理对象是使用切面逻辑对业务逻辑进行包裹之后生成的对象。如果使用的是Jdk动态代理,那么业务对象和代理对象将是两个对象,在调用代理对象逻辑时,其切面逻辑中会调用目标对象的逻辑;如果使用的是Cglib代理,由于是使用的子类进行切面逻辑织入的,那么只有一个对象,即织入了代理逻辑的业务类的子类对象,此时是不会生成业务类的对象的。

       通过上面的讲解可以看出,this和target的使用区别其实不大,大部分情况下其使用效果是一样的,但其区别也还是有的。Spring使用的代理方式主要有两种:Jdk代理和Cglib代理。针对这两种代理类型,关于目标对象与代理对象,理解如下两点是非常重要的:

(1)如果目标对象被代理的方法是其实现的某个接口的方法,那么将会使用Jdk代理生成代理对象,此时代理对象和目标对象是两个对象,并且都实现了该接口;

(2)如果目标对象是一个类,并且其没有实现任意接口,那么将会使用Cglib代理生成代理对象,并且只会生成一个对象,即Cglib生成的代理类的对象。

结合上述两点来理解this和target的异同就相对比较简单了。我们这里分三种情况进行说明:

(1)this(SomeInterface)或target(SomeInterface):这种情况下,无论是对于Jdk代理还是Cglib代理,其目标对象和代理对象都是实现SomeInterface接口的(Cglib生成的目标对象的子类也是实现了SomeInterface接口的),因而this和target语义都是符合的,此时这两个表达式的效果一样;

(2)this(SomeObject)或target(SomeObject),这里SomeObject没实现任何接口:这种情况下,Spring会使用Cglib代理生成SomeObject的代理类对象,由于代理类是SomeObject的子类,子类的对象也是符合SomeObject类型的,因而this将会被匹配,而对于target,由于目标对象本身就是SomeObject类型,因而这两个表达式的效果一样;

(3)this(SomeObject)或target(SomeObject),这里SomeObject实现了某个接口:对于这种情况,虽然表达式中指定的是一种具体的对象类型,但由于其实现了某个接口,因而Spring默认会使用Jdk代理为其生成代理对象,Jdk代理生成的代理对象与目标对象实现的是同一个接口,但代理对象与目标对象还是不同的对象,由于代理对象不是SomeObject类型的,因而此时是不符合this语义的,而由于目标对象就是SomeObject类型,因而target语义是符合的,此时this和target的效果就产生了区别;这里如果强制Spring使用Cglib代理,因而生成的代理对象都是SomeObject子类的对象,其是SomeObject类型的,因而this和target的语义都符合,其效果就是一致的。

     配置类  :

@Configuration
@ComponentScan("com.zlu")
@EnableAspectJAutoProxy(proxyTargetClass = false)//proxyTargetClass = true 表示使用cglib代 理,否则为jdk代理
//@ImportResource("spring.xml")
public class AppConfig {
}

横切代码:

@Component
@Aspect
public class AspectTest {

    /**
     * Dao是接口 IndexDao是实现Dao接口的类
     */
    @Pointcut("this(com.zlu.dao.Dao)")
    //@Pointcut("this(com.zlu.dao.IndexDao)") 
    public void this1(){

    }

  
    @Before("this1()")
    public void before(JoinPoint jpt){
        System.out.println("before:");
    }

}

测试:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext a=new AnnotationConfigApplicationContext(AppConfig.class);
        Dao dao1 = a.getBean(Dao.class);
        dao1.query();
        // 使用如下代码会报异常
//        IndexDao dao = a.getBean(IndexDao.class);
//        dao.query();
    }
}

得到结果:

如果将上述 @Pointcut("this(com.zlu.dao.Dao)")  改成  @Pointcut("this(com.zlu.dao.IndexDao)"),则显示结果为:

5、JoinPoint :是aop中的连接点,通过这个对象,获得连接点的信息,比如她所在的类,他的代理对象,目标对象,参数,方法返回类型等。

例如:

   @Before("this1()")
    public void before(JoinPoint jpt){
        System.out.println("before:");
        System.out.println(jpt.getThis());
        System.out.println(jpt.getTarget());
    }

6、ProceedingJoinPoint继承了JoinPoint,是JoinPoint类的增强,其中proceed()方法用来执行连接点方法(目标方法),用在环绕通知方法中。

@Around("myWithin()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("around");
   Object o = pjp.proceed();//执行连接点方法
    System.out.println("around1");
}

大多数情况下,在环绕通知中改变连接点传入的参数,例如:

@Around("myWithin()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
    Object[] args = pjp.getArgs();
    if(args!=null && args.length>0){
        for (int i = 0; i < args.length; i++) {
            args[i]+="hello---";
        }
    }
   System.out.println("around");
   pjp.proceed(args);//执行连接点方法
   System.out.println("around1");
}

7、引入(Introductions):将value="com.zlu.dao.*"目标对象 去实现Dao接口,可以使用defaultImpl中定义的类的方法

@DeclareParents(value="com.zlu.dao.*", defaultImpl= IndexDao.class)
public static Dao dao;
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
Dao dao = (Dao) context.getBean("testDao");
dao.query("a");
dao.query();

TestDao.java

@Repository("testDao")
public class TestDao  {
}

8、@aspect切面默认是单例模式

您可以perthis通过perthis@Aspect 注释中来限定某个dao来创建实例,使切面成为原型模式

@Aspect("perthis(within1())") 

@Scope("prototype")

9、spring aop和aspectj的区别:aspectj是静态织入,springaop是动态织入。动态织入是指在运行期间织入,静态织入是指在编译期间完成织入

 

发布了6 篇原创文章 · 获赞 5 · 访问量 1004

猜你喜欢

转载自blog.csdn.net/qq_28203555/article/details/83504569