上篇博客我们简单的了解了Spring IOC的一些知识点,接下来让我们再学习一下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支持的两种方式
aspect:一定要给spring去管理,是 抽象的概念 在类中用aspectj表示,在xml中用标签label表示。
pointcut:切点表示连接点的集合 -------------------> 表
Joinpoint:连接点 目标对象 中的方法 ----------------> 记录
Weaving :把代理逻辑加入到目标对象上的过程叫做织入
target: 目标对象 原始对象
aop Proxy: 代理对象 包含了原始对象的代码和增强的代码的那个对象
advice:通知 由需要放入逻辑代码块的切面内容 和 切面放入逻辑代码的位置 组成
通知类型: Before 、After 、 After throwing 、After (finally) 、Around advice:
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是动态织入。动态织入是指在运行期间织入,静态织入是指在编译期间完成织入