Spring AOP面向方面编程原理:AOP概念

Spring AOP面向方面编程原理:AOP概念,第1张

AOP(Aspect Oriented Programming) 也就是面向方面编程的技术 AOP基于IoC基础 是对OOP的有益补充

AOP将应用系统分为两部分 核心业务逻辑(Core business concerns)及横向的通用逻辑 也就是所谓的方面Crosscutting enterprise concerns 例如 所有大中型应用都要涉及到的持久化管理(Persistent) 事务管理(Transaction Management) 安全管理(Security) 日志管理(Logging)和调试管理(Debugging)等

AOP正在成为软件开发的下一个光环 使用AOP 你可以将处理aspect的代码注入主程序 通常主程序的主要目的并不在于处理这些aspect AOP可以防止代码混乱

Spring framework是很有前途的AOP技术 作为一种非侵略性的 轻型的AOP framework 你无需使用预编译器或其他的元标签 便可以在Java程序中使用它 这意味着开发团队里只需一人要对付AOP framework 其他人还是像往常一样编程

AOP概念

让我们从定义一些重要的AOP概念开始

— 方面(Aspect) 一个关注点的模块化 这个关注点实现可能另外横切多个对象 事务管理是J EE应用中一个很好的横切关注点例子 方面用Spring的Advisor或拦截器实现

— 连接点(Joinpoint) 程序执行过程中明确的点 如方法的调用或特定的异常被抛出

通知(Advice) 在特定的连接点 AOP框架执行的动作 各种类型的通知包括 around before 和 throws 通知 通知类型将在下面讨论 许多AOP框架包括Spring都是以拦截器做通知模型 维护一个 围绕 连接点的拦截器链

切入点(Pointcut) 指定一个通知将被引发的一系列连接点的集合 AOP框架必须允许开发者指定切入点 例如 使用正则表达式

— 引入(Introduction) 添加方法或字段到被通知的类 Spring允许引入新的接口到任何被通知的对象 例如 你可以使用一个引入使任何对象实现IsModified接口 来简化缓存

— 目标对象(Target Object) 包含连接点的对象 也被称作被通知或被代理对象

— AOP代理(AOP Proxy) AOP框架创建的对象 包含通知 在Spring中 AOP代理可以是JDK动态代理或CGLIB代理

— 编织(Weaving) 组装方面来创建一个被通知对象 这可以在编译时完成(例如使用AspectJ编译器) 也可以在运行时完成 Spring和其他纯Java AOP框架一样 在运行时完成织入

各种通知类型包括

—  Around通知 包围一个连接点的通知 如方法调用 这是最强大的通知 Aroud通知在方法调用前后完成自定义的行为 它们负责选择继续执行连接点或通过返回它们自己的返回值或抛出异常来短路执行

—  Before通知 在一个连接点之前执行的通知 但这个通知不能阻止连接点前的执行(除非它抛出一个异常)

—  Throws通知 在方法抛出异常时执行的通知 Spring提供强制类型的Throws通知 因此你可以书写代码捕获感兴趣的异常(和它的子类) 不需要从Throwable或Exception强制类型转换

—  After returning通知 在连接点正常完成后执行的通知 例如 一个方法正常返回 没有抛出异常

Around通知是最通用的通知类型 大部分基于拦截的AOP框架(如Nanning和Jboss )只提供Around通知

如同AspectJ Spring提供所有类型的通知 我们推荐你使用最为合适的通知类型来实现需要的行为 例如 如果只是需要用一个方法的返回值来更新缓存 你最好实现一个after returning通知 而不是around通知 虽然around通知也能完成同样的事情 使用最合适的通知类型使编程模型变得简单 并能减少潜在错误 例如 你不需要调用在around通知中所需使用的MethodInvocation的proceed()方法 因此就调用失败

切入点的概念是AOP的关键 它使AOP区别于其他使用拦截的技术 切入点使通知独立于OO的层次选定目标 例如 提供声明式事务管理的around通知可以被应用到跨越多个对象的一组方法上 因此切入点构成了AOP的结构要素

下面让我们实现一个Spring AOP的例子 在这个例子中 我们将实现一个before advice 这意味着advice的代码在被调用的public方法开始前被执行 以下是这个before advice的实现代码

package ascenttech springaop test;

import java lang reflect Method;

import springframework aop MethodBeforeAdvice;

public class TestBeforeAdvice implements MethodBeforeAdvice {

public void before(Method m Object[] args Object target)

throws Throwable {

System out println( Hello world! (by

+ this getClass() getName()

+ ) );

}

}

接口MethodBeforeAdvice只有一个方法before需要实现 它定义了advice的实现 before方法共用 个参数 它们提供了相当丰富的信息 参数Method m是advice开始后执行的方法 方法名称可以用作判断是否执行代码的条件 Object[] args是传给被调用的public方法的参数数组 当需要记日志时 参数args和被执行方法的名称都是非常有用的信息 你也可以改变传给m的参数 但要小心使用这个功能 编写最初主程序的程序员并不知道主程序可能会和传入参数的发生冲突 Object target是执行方法m对象的引用

在下面的BeanImpl类中 每个public方法调用前 都会执行advice 代码如下

package ascenttech springaop test;

public class BeanImpl implements Bean {

public void theMethod() {

System out println(this getClass() getName()

+ + new Exception() getStackTrace()[ ] getMethodName()

+ ()

+ says HELLO! );

}

}

类BeanImpl实现了下面的接口Bean 代码如下

package ascenttech springaop test;

public interface Bean {

public void theMethod();

}

虽然不是必须使用接口 但面向接口而不是面向实现编程是良好的编程实践 Spring也鼓励这样做

pointcut和advice通过配置文件来实现 因此 接下来你只需编写主方法的Java代码 代码如下

package ascenttech springaop test;

import ntext ApplicationContext;

import ntext support FileSystemXmlApplicationContext;

public class Main {

public static void main(String[] args) {

//Read the configuration file

ApplicationContext ctx

= new FileSystemXmlApplicationContext( springconfig xml );

//Instantiate an object

Bean x = (Bean) ctx getBean( bean );

//Execute the public method of the bean (the test)

x theMethod();

}

}

我们从读入和处理配置文件开始 接下来马上要创建它 这个配置文件将作为粘合程序不同部分的 胶水 读入和处理配置文件后 我们会得到一个创建工厂ctx 任何一个Spring管理的对象都必须通过这个工厂来创建 对象通过工厂创建后便可正常使用

仅仅用配置文件便可把程序的每一部分组装起来 代码如下

<xml version= encoding= UTF >

<!DOCTYPE beans PUBLIC //SPRING//DTD BEAN//EN /dtd/spring beans dtd >

<beans>

<! CONFIG >

<bean id= bean class= springframework aop framework ProxyFactoryBean >

<property name= proxyInterfaces >

<value> ascenttech springaop test Bean</value>

</property>

<property name= target >

<ref local= beanTarget />

</property>

<property name= interceptorNames >

<list>

<value>theAdvisor</value>

</list>

</property>

</bean>

<! CLASS >

<bean id= beanTarget class= ascenttech springaop test BeanImpl />

<! ADVISOR >

<! Note: An advisor assembles pointcut and advice >

<bean id= theAdvisor class= springframework aop support RegexpMethod PointcutAdvisor >

<property name= advice >

<ref local= theBeforeAdvice />

</property>

<property name= pattern >

<value>\ ascenttech\ springaop\ test\ Bean\ theMethod</value>

</property>

</bean>

<! ADVICE >

<bean id= theBeforeAdvice class= ascenttech springaop test TestBefore Advice />

</beans>

个bean定义的次序并不重要 我们现在有了一个advice 一个包含了正则表达式pointcut的advisor 一个主程序类和一个配置好的接口 通过工厂ctx 这个接口返回自己本身实现的一个引用

BeanImpl和TestBeforeAdvice都是直接配置 我们用一个惟一的ID创建一个bean元素 并指定了一个实现类 这就是全部的工作

advisor通过Spring framework提供的一个RegexMethodPointcutAdvisor类来实现 我们用advisor的第一个属性来指定它所需的advice bean 第二个属性则用正则表达式定义了pointcut 确保良好的性能和易读性

最后配置的是bean 它可以通过一个工厂来创建 bean的定义看起来比实际上要复杂 bean是ProxyFactoryBean的一个实现 它是Spring framework的一部分 这个bean的行为通过以下的 个属性来定义

— 属性proxyInterface定义了接口类

— 属性target指向本地配置的一个bean 这个bean返回一个接口的实现

— 属性interceptorNames是惟一允许定义一个值列表的属性 这个列表包含所有需要在beanTarget上执行的advisor 注意 advisor列表的次序是非常重要的

lishixinzhi/Article/program/Java/hx/201311/26338

用户登录验证在所有业务中基本都有涉及,这里使用AOP的方式进行用户登录的校验拦截

前提:当前项目为Spring-Boot的项目,项目在IDEA下使用了lombok插件

由于SpringBoot项目的SPI机制,此处引入依赖包之后,就不用额外做配置了

用于在需要校验登录的方法入口处做标记

创建一个Aspect用来处理做了上述自定义注解标记的方法

经过上述步骤之后,在需要验证的方法上打上 @CheckLogin 的自定义注解,就可以实现token的校验了

这里使用的是AOP的环绕切入,原理是将需要调用的目标方法包入 CheckLoginAspect 的前置逻辑中@Around的意思为环绕切入,在方法前通过获取Request并从Request中获取到header,然后再从header中获取到“X-Token”,之后的方法就是通过jwt工具类进行校验和一些参数放置到request的 *** 作,直到执行 pointproceed(); 方法才是需要调用到的目标类

几个注解的含义

这里检测到token异常之后,会抛出一个 SecurityException 实际上这个只是继承了 RuntimeException ,方便在异常处理时分辨异常的类型

接下来,添加一个全局处理异常的Handler

以往我再处理异常时,都会将>

Bean的后置处理器,首先来说,他是Spring中抽象出来的一个顶级的接口,他里面有如下两个方法。

简单点来理解,就是spring会自动从它的所有的bean定义中检测BeanPostProcessor类型的bean定义,然后实例化它们,再将它们应用于随后创建的每一个bean实例,在bean实例的初始化方法回调之前调用BeanPostProcessor的postProcessBeforeInitialization的方法(进行bean实例属性的填充),在bean实例的初始化方法回调之后调用BeanPostProcessor的postProcessAfterInitialization的方法(可以进行bean实例的代理封装)。

大家说它是Spring对外提供的拓展点,也许是因为,通过实现这个接口,程序员可以对Spring管理的bean的生命周期进行插手。

这也体现了AOP的设计思想,就比如在init()方法执行前后做出不同的动作,其实就是对bean的一种增强。

此外BeanPostProcessor可以存在多个。他们会被存储在一个列表中。然后依次被执行。

所谓的注册,只不过是对当前上下文中所有的BeanPostProcessor进行一种集中式管理罢了,为什么非得这么做呢 因为上下文中BeanPostProcessor的数量不是一成不变的,Spring为了启动的正常,需要添加原生的BeanPostProcessor,程序员因为自己的需求也会添加不同数量的bean的后置处理器,因此需要这种策略将上下文中所有的后置处理器进行统一的管理,方便回调。

Spring作为一个优秀的框架,拥有良好的可扩展性。Spring对对象的可扩展性主要就是依靠InstantiationAwareBeanPostProcessor和BeanPostProcessor来实现的。

AnnotationConfigApplicationContext构造方法中调用refresh()方法

refresh() 方法中这里主要关心两个方法:

通过beanFactorygetBeanNamesForType来获取所有BeanPostProcessor。

BeanPostProcessor按优先级分为PriorityOrdered,Ordered和其他的,对他们分别进行 *** 作。

InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation方法,该方法主要是在bean实例化之后,并且在其被设置上属性值之前去进行调用,用来判断是否继续去执行对属性赋值的流程,方法返回true,即默认情况下都会去执行populateBean方法,对Bean的属性进行赋值。

postProcessAfterInstantiation方法调用是在populateBean方法中,这里从doCreateBean方法进入。

在populateBean方法中,在对bean的属性进行真正赋值之前,会依次调用InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation方法。

aop 几个应用场景:

1、用于日志打印

2、用于全局异常处理拦截

3、返回值统一处理

4、多数据源切换

execution(方法表达式)

execution( cnsunpiaoliangservice ())

cnsunpiaoliangservice 这个路径下的包或子包所有方法和所有类型的参数 返回值为任何类型

注:

第一个 “ ” 任何类型返回值

cnsunpiaoliangservice 路径

包或子包

第二个“ ” 所有类

@annotation

匹配当前执行方法持有指定注解的方法,可以直接写注解,也可以直接写全路径

@within

匹配所以持有指定注解类型内的方法,全路径

aop主要是通过代理实现

1、JDK代理

2、CGlib 代理

先说注解,使用注解配置Spring AOP总体分为两步,第一步是在xml文件中声明激活自动扫描组件功能,同时激活自动代理功能(同时在xml中添加一个UserService的普通服务层组件,来测试AOP的注解功能):

<xml version="10" encoding="UTF-8">

<beans xmlns=">

第二步是为Aspect切面类添加注解:

package cnyshstudiospringaopaspect;

import orgapachecommonsloggingLog;

import orgapachecommonsloggingLogFactory;

import orgaspectjlangJoinPoint;

import orgaspectjlangProceedingJoinPoint;

import orgaspectjlangannotationAfter;

import orgaspectjlangannotationAfterReturning;

import orgaspectjlangannotationAfterThrowing;

import orgaspectjlangannotationAround;

import orgaspectjlangannotationAspect;

import orgaspectjlangannotationBefore;

import orgaspectjlangannotationPointcut;

import orgspringframeworkstereotypeComponent;

/

系统服务组件Aspect切面Bean

@author Shenghany

@date 2013-5-28

/

//声明这是一个组件

@Component

//声明这是一个切面Bean

@Aspect

public class ServiceAspect {

private final static Log log = LogFactorygetLog(ServiceAspectclass);

//配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点

@Pointcut("execution( cnyshstudiospringaopservice())")

public void aspect(){ }

/

配置前置通知,使用在方法aspect()上注册的切入点

同时接受JoinPoint切入点对象,可以没有该参数

/

@Before("aspect()")

public void before(JoinPoint joinPoint){

if(logisInfoEnabled()){

loginfo("before " + joinPoint);

}

}

//配置后置通知,使用在方法aspect()上注册的切入点

@After("aspect()")

public void after(JoinPoint joinPoint){

if(logisInfoEnabled()){

loginfo("after " + joinPoint);

}

}

//配置环绕通知,使用在方法aspect()上注册的切入点

@Around("aspect()")

public void around(JoinPoint joinPoint){

long start = SystemcurrentTimeMillis();

try {

((ProceedingJoinPoint) joinPoint)proceed();

long end = SystemcurrentTimeMillis();

if(logisInfoEnabled()){

loginfo("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");

}

} catch (Throwable e) {

long end = SystemcurrentTimeMillis();

if(logisInfoEnabled()){

loginfo("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + egetMessage());

}

}

}

//配置后置返回通知,使用在方法aspect()上注册的切入点

@AfterReturning("aspect()")

public void afterReturn(JoinPoint joinPoint){

if(logisInfoEnabled()){

loginfo("afterReturn " + joinPoint);

}

}

//配置抛出异常后通知,使用在方法aspect()上注册的切入点

@AfterThrowing(pointcut="aspect()", throwing="ex")

public void afterThrow(JoinPoint joinPoint, Exception ex){

if(logisInfoEnabled()){

loginfo("afterThrow " + joinPoint + "\t" + exgetMessage());

}

}

}

测试代码:

package cnyshstudiospringaop;

import orgapachecommonsloggingLog;

import orgapachecommonsloggingLogFactory;

import orgspringframeworkcontextApplicationContext;

import orgspringframeworkcontextsupportClassPathXmlApplicationContext;

import cnyshstudiospringaopserviceUserService;

import cnyshstudiospringmvcbeanUser;

/

Spring AOP测试

@author Shenghany

@date 2013-5-28

/

public class Tester {

private final static Log log = LogFactorygetLog(Testerclass);

public static void main(String[] args) {

//启动Spring容器

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContextxml");

//获取service组件

UserService service = (UserService) contextgetBean("userService");

//以普通的方式调用UserService对象的三个方法

User user = serviceget(1L);

servicesave(user);

try {

servicedelete(1L);

} catch (Exception e) {

if(logisWarnEnabled()){

logwarn("Delete user : " + egetMessage());

}

}

}

}

控制台输出如下:

INFO [springaopaspectServiceAspect:40] before execution(User cnyshstudiospringaopserviceUserServiceget(long))

INFO [springaopserviceUserService:19] getUser method

INFO [springaopaspectServiceAspect:60] around execution(User cnyshstudiospringaopserviceUserServiceget(long)) Use time : 42 ms!

INFO [springaopaspectServiceAspect:48] after execution(User cnyshstudiospringaopserviceUserServiceget(long))

INFO [springaopaspectServiceAspect:74] afterReturn execution(User cnyshstudiospringaopserviceUserServiceget(long))

INFO [springaopaspectServiceAspect:40] before execution(void cnyshstudiospringaopserviceUserServicesave(User))

INFO [springaopserviceUserService:26] saveUser method

INFO [springaopaspectServiceAspect:60] around execution(void cnyshstudiospringaopserviceUserServicesave(User)) Use time : 2 ms!

INFO [springaopaspectServiceAspect:48] after execution(void cnyshstudiospringaopserviceUserServicesave(User))

INFO [springaopaspectServiceAspect:74] afterReturn execution(void cnyshstudiospringaopserviceUserServicesave(User))

INFO [springaopaspectServiceAspect:40] before execution(boolean cnyshstudiospringaopserviceUserServicedelete(long))

INFO [springaopserviceUserService:32] delete method

INFO [springaopaspectServiceAspect:65] around execution(boolean cnyshstudiospringaopserviceUserServicedelete(long)) Use time : 5 ms with exception : spring aop ThrowAdvice演示

INFO [springaopaspectServiceAspect:48] after execution(boolean cnyshstudiospringaopserviceUserServicedelete(long))

INFO [springaopaspectServiceAspect:74] afterReturn execution(boolean cnyshstudiospringaopserviceUserServicedelete(long))

WARN [studiospringaopTester:32] Delete user : Null return value from advice does not match primitive return type for: public boolean cnyshstudiospringaopserviceUserServicedelete(long) throws javalangException

可以看到,正如我们预期的那样,虽然我们并没有对UserSerivce类包括其调用方式做任何改变,但是Spring仍然拦截到了其中方法的调用,或许这正是AOP的魔力所在。

再简单说一下xml配置方式,其实也一样简单:

<xml version="10" encoding="UTF-8">

<beans xmlns=">

个人觉得不如注解灵活和强大,你可以不同意这个观点,但是不知道如下的代码会不会让你的想法有所改善:

//配置前置通知,拦截返回值为cnyshstudiospringmvcbeanUser的方法

@Before("execution(cnyshstudiospringmvcbeanUser cnyshstudiospringaopservice())")

public void beforeReturnUser(JoinPoint joinPoint){

if(logisInfoEnabled()){

loginfo("beforeReturnUser " + joinPoint);

}

}

//配置前置通知,拦截参数为cnyshstudiospringmvcbeanUser的方法

@Before("execution( cnyshstudiospringaopservice(cnyshstudiospringmvcbeanUser))")

public void beforeArgUser(JoinPoint joinPoint){

if(logisInfoEnabled()){

loginfo("beforeArgUser " + joinPoint);

}

}

//配置前置通知,拦截含有long类型参数的方法,并将参数值注入到当前方法的形参id中

@Before("aspect()&&args(id)")

public void beforeArgId(JoinPoint joinPoint, long id){

if(logisInfoEnabled()){

loginfo("beforeArgId " + joinPoint + "\tID:" + id);

}

}

附上UserService的代码(其实很简单):

package cnyshstudiospringaopservice;

import orgapachecommonsloggingLog;

import orgapachecommonsloggingLogFactory;

import cnyshstudiospringmvcbeanUser;

/

用户服务模型

@author Shenghany

@date 2013-5-28

/

public class UserService {

private final static Log log = LogFactorygetLog(UserServiceclass);

public User get(long id){

if(logisInfoEnabled()){

loginfo("getUser method ");

}

return new User();

}

public void save(User user){

if(logisInfoEnabled()){

loginfo("saveUser method ");

}

}

public boolean delete(long id) throws Exception{

if(logisInfoEnabled()){

loginfo("delete method ");

throw new Exception("spring aop ThrowAdvice演示");

}

return false;

}

}

应该说学习Spring AOP有两个难点,第一点在于理解AOP的理念和相关概念,第二点在于灵活掌握和使用切入点表达式。概念的理解通常不在一朝一夕,慢慢浸泡的时间长了,自然就明白了,下面我们简单地介绍一下切入点表达式的配置规则吧。

通常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:

execution(modifiers-pattern ret-type-pattern declaring-type-pattern name-pattern(param-pattern) throws-pattern)

modifiers-pattern:方法的 *** 作权限

ret-type-pattern:返回值

declaring-type-pattern:方法所在的包

name-pattern:方法名

parm-pattern:参数名

throws-pattern:异常

其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution( comspringservice())表示comspringservice包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。

最后说一下通知参数

可以通过args来绑定参数,这样就可以在通知(Advice)中访问具体参数了。例如,<aop:aspect>配置如下:

<aop:config>

<aop:aspect id="TestAspect" ref="aspectBean">

<aop:pointcut id="businessService"

expression="execution( comspringservice(String,)) and args(msg,)" />

<aop:after pointcut-ref="businessService" method="doAfter"/>

</aop:aspect>

</aop:config>上面的代码args(msg,)是指将切入点方法上的第一个String类型参数添加到参数名为msg的通知的入参上,这样就可以直接使用该参数啦。

SpringAOP是利用代理模式,在运行时生成一个目标对象的代理,并且使用代理代替目标对象,整个过程对使用者透明,使用者无法像使用目标对象一样使用代理对象,代理对象类型是目标对象所属类的子类或者接口实现,北京IT培训>

以上就是关于Spring AOP面向方面编程原理:AOP概念全部的内容,包括:Spring AOP面向方面编程原理:AOP概念、使用AOP校验用户登录和异常处理-2020-10-26、Spring5AOP——BeanPostProcessor等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9826817.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-02
下一篇 2023-05-02

发表评论

登录后才能评论

评论列表(0条)

保存