1、什么是OOP
、AOP
?
OOP(Object-Oriented Programming)
面向对象编程,允许开发者定义纵向的关系,但定义横向的关系会导致了大量代码的重复,而不利于各个模块的重用。AOP(Aspect-Oriented Programming)
,一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect)
,这样可以减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。
2、Spring AOP and AspectJ AOP
有什么区别?AOP
有哪些实现方式?
答: AOP
实现的关键在于代理模式,AOP
代理主要分为静态代理和动态代理。静态代理的代表为 AspectJ
;动态代理则以 Spring AOP
为代表。
AspectJ
是静态代理的增强,所谓静态代理,就是AOP
框架会在编译阶段生成AOP
代理类,因此也称为编译时增强,他会在编译阶段将AspectJ
(切面)织入到Java字节码中,运行的时候就是增强之后的AOP
对象。Spring AOP
使用的动态代理,所谓的动态代理就是说AOP
框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP
对象,这个AOP
对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
3、JDK
动态代理和CGLIB
动态代理的区别?
JDK
动态代理:JDK
动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler
接口和Proxy
类。InvocationHandler
通过invoke()
方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;- 接着,
Proxy
利用InvocationHandler
动态创建一个符合某一接口的的实例,生成目标类的代理对象。
CGLIB
动态代理: 如果代理类没有实现InvocationHandler
接口,那么Spring AOP
会选择使用CGLIB
来动态代理目标类。CGLIB(Code Generation Library)
,是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP
。CGLIB
是通过继承的方式做的动态代理,因此如果某个类被标记为final
,那么它是无法使用CGLIB
做动态代理的。
InvocationHandler
的invoke(Object proxy,Method method,Object[] args)
:proxy
是最终生成的代理实例;method
是被代理目标实例的某个具体方法;args
是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
4、解释一下Spring AOP
里面的几个名词?
- 切面(
Aspect
):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP
中,切面可以使用通用类(基于模式的风格) 或者在普通类中以@AspectJ
注解来实现。 - 连接点(
Join point
): 指方法,在Spring AOP
中,一个连接点总是代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。 - 通知(
Advice
): 在AOP
术语中,切面的工作被称为通知。 - 切入点(
Pointcut
): 切点的定义会**匹配通知所要织入的一个或多个连接点。**我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。 - 引入(
Introduction
): 引入允许我们向现有类添加新方法或属性。 - 目标对象(
Target Object
): 被一个或者多个切面(aspect
)所通知(advise
)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced
) 对象。既然Spring AOP
是通过运行时代理实现的,这个对象永远是一个 被代理(proxied
) 对象。 - 织入(
Weaving
): 织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:- 编译期: 切面在目标类编译时被织入。
AspectJ
的织入编译器是以这种方式织入切面的。 - 类加载期: 切面在目标类加载到
JVM
时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5
的加载时织入就支持以这种方式织入切面。 - 运行期: 切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,
AOP
容器会为目标对象动态地创建一个代理对象。Spring AOP
就是以这种方式织入切面。
- 编译期: 切面在目标类编译时被织入。
5、Spring
在运行时通知对象?
- 通过在代理类中包裹切面,
Spring
在运行期把切面织入到Spring
管理的Bean
中。代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标Bean
。当代理拦截到方法调用时,在调用目标Bean
方法之前,会执行切面逻辑。 - 直到应用需要被代理的
Bean
时,Spring
才创建代理对象。如果使用的是ApplicationContext
的话,在ApplicationContext
从BeanFactory
中加载所有Bean
的时候,Spring
才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP
的切面。
6、Spring
只支持方法级别的连接点吗?
答: 因为Spring
基于动态代理,所以Spring
只支持方法连接点。Spring
缺少对字段连接点的支持,而且它不支持构造器连接点。方法之外的连接点拦截功能,我们可以利用Aspect
来补充。
7、在Spring AOP
中,关注点和横切关注的区别是什么?在 Spring AOP
中concern
和 cross-cutting concern
的不同之处?
- 关注点: 关注点(
concern
)是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能; - 横切关注点: 横切关注点(
cross-cutting concern
)是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。
8、Spring
通知有哪些类型?
答: 在AOP
术语中,切面的工作被称为通知,实际上是程序执行时要通过Spring AOP
框架触发的代码段。Spring切面可以应用5种类型的通知:
- 前置通知(
Before
): 在目标方法被调用之前调用通知功能; - 后置通知(
After
): 在目标方法完成之后调用通知,此时不会关心方法的输出是什么; - 返回通知(
After-returning
): 在目标方法成功执行之后调用通知; - 异常通知(
After-throwing
): 在目标方法抛出异常后调用通知; - 环绕通知(
Around
): 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
9、动态代理失效的原因
-
Spring的事务注解
@Transactional
只能放在public
修饰的方法上才起作用,如果放在其他非public
(private
,protected
)方法上,虽然不报错,但是事务不起作用; -
如果采用
Spring + Spring MVC
,则context:component-scan
重复扫描问题可能会引起事务失败。如果Spring
和mvc
的配置文件中都扫描了service
层,那么事务就会失效;答: 因为按照
Spring
配置文件的加载顺序来讲,先加载Spring MVC
配置文件,再加载Spring配置文件,我们的事物一般都在Spring
配置文件中进行配置,如果此时在加载Spring MVC
配置文件的时候,把service
也给注册了,但是此时事物还没加载,也就导致后面的事物无法成功注入到service
中。所以把对service
的扫描放在Spring
配置文件中或是其他配置文件中。 -
如使用
mysql
且引擎是MyISAM
,则事务会不起作用,原因是MyISAM
不支持事务,可以改成InnoDB
引擎; -
@Transactional
注解开启配置,必须放到listener
里加载,如果放到DispatcherServlet
的配置里,事务也是不起作用的。如果Spring
和Spring MVC
的配置文件中都扫描了service
层,那么事务就会失效。**答:**因为按照
Spring
配置文件的加载顺序来讲,先加载Spring MVC
配置文件,再加载Spring配置文件,我们的事物一般都在Spring
配置文件中进行配置,如果此时在加载Spring MVC
配置文件的时候,把service
也给注册了,但是此时事物还没加载,也就导致后面的事物无法成功注入到service
中。所以把对service
的扫描放在Spring
配置文件中或是其他配置文件中。 -
Spring团队建议在具体的类(或类的方法)上使用
@Transactional
注解,而不要使用在类所要实现的任何接口上。在接口上使用@Transactional
注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。 -
在业务代码中如果抛出
RuntimeException
异常,事务回滚;但是抛出Exception
,事务不回滚; -
如果在加有事务的方法内,使用了
try...catch..
语句块对异常进行了捕获,而catch
语句块没有throw new RuntimeExecption
异常,事务也不会回滚。 -
在类
A
里面有方法a
和方法b
, 然后方法b
上面用@Transactional
加了方法级别的事务,在方法a
里面 调用了方法b
, 方法b
里面的事务不会生效。原因是在同一个类之中,方法互相调用,切面无效,而不仅仅是事务。这里事务之所以无效,是因为Spring
的事务是通过aop
实现的。