Spring系列Spring中AOP面向切面的编程(动态代理)
Posted 一宿君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring系列Spring中AOP面向切面的编程(动态代理)相关的知识,希望对你有一定的参考价值。
5 什么是AOP面向切面编程?
AOP(Aspect Oriented Programming,面向方面编程)
-
我们知道OOP(Object-Oriented Programming,面向对象编程),OOP引入了封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一种集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。简单来说,就是OOP定义了从上到下的关系,但不适合从左到右的关系。eg:日志功能,日志业务往往水平的分布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系;类似的还有核心业务代码的预处理、异常处理以及结束信息处理等操作,是完全与核心业务没有关联的存在。然而这些散布到各处的无关的代码我们称为 “横切(cross-cutting)代码”,这写无关核心的业务代码会被大量引用,从而就导致了代码的耦合性,且不利于各个模块之间的重用。
-
而AOP技术恰好是对OOP面向对象的补充和增强处理,它利用一种 “横切(cross-cutting)技术”,横贯在封装的对象内部,使那些影响了多个类的公共行为通过 代理机制 封装在一个可重用的模块中,并将其命名为 “Aspect”,即方面。简单的来说,就是将那些与核心业务无关的代码,却被核心业务模块共同调用的逻辑代码封装起来,便于减少系统的重用代码,降低模块之间的耦合度,并有利于。未来的 可维护性和可扩展性。
5.1 AOP目标和原理
AOP目标:
- 让我们可以“专心做事”;
AOP原理:
- 将复杂的需求分解出不同的方面,将散布在系统中的公共功能集中解决;
- 采用代理机制将公共功能组装起来运行,在不改变原程序的基础上对代码段进行增强处理,增加新的功能。
5.2 实现AOP的技术,主要分为两大类:
- 一是采用动态代理技术,利用截取消息的方式,对该消息进行封装,以取代原有对象行为的执行;
- 二是采用静态织入的方式,引入特定的语法创建“方面”,从而使编译器可以在编译期间织入有关“方面”的代码。
5.3 AOP技术常用场景:
- Authentication 权限
- Caching 缓存
- Context passing 内容传递
- Error handling 错误处理
- Lazy loading 懒加载
- Debugging 调试
- logging, tracing, profiling and monitoring 记录跟踪 优化 校准
- Performance optimization 性能优化
- Persistence 持久化
- Resource pooling 资源池
- Synchronization 同步
- Transactions 事务
5.4 AOP相关概念:
- 切面(Aspect):
一个关注点的模块,这个关注点的实心可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子(eg:典型案例:银行转款,事务前操作和后操作,但是要保证整个事务机制同时成功或同时失败),方面用Spring的Advisor(顾问)或interceptor(拦截器)来实现。
- 切入点(PointCut):
指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如使用正则表达式。Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可用通过名字清晰的理解;
MethidMatcher:是用来检查目标类的方法是否可以被应用次通知;
ClassFilter:是用来检查Poincut是否应该应用到目标类上。 - 连接点(Join Point):
程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
- 增强处理(Advice):
在特定的连接点,AOP执行的动作,包括一下各种类型的通知。
前置增强(Before)
后置增强(AfterReturning)
环绕增强(Around)
异常抛出增强(AfterThrowing)
最终增强(After) - 目标对象(Target Object):
包含连接点的对象,也被称作被代理通知或被代理对象,POJO。
- AOP代理(AOP Proxy):
AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或者是CGLIB动态代理。
- 织入(Weaving):
组装方面来创建一个被通知对象,这可以在编译时完成(eg:使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,也是在运行时完成织入。
5.5 如何使用Spring AOP
可以通过配置xml文件或编程的方式实现。
通过配置xml文件,可以概括为四种方式:
- 1 配置ProxyFactoryBean,显示的配置advisors、advice、target等属性;
- 2 配置AutoProxyCreator,这种方式,还是如以前一样使用定义的Bean,但是从容器中获取到的其实已经是代理对象了;
- 3 通过< aop-config >来配置;
- 4 通过 < aop:aspectj-autoproxy > 来配置,使用AspectJ的注解来标识通知及切入点(开发中常用)。
5.6 废话少说,还是举个例子,先来演示第一种实现Spring AOP的方式,配置ProxyFactoryBean
上章节Spring系列(四)、设计模式之代理模式中,我们已经简单的讲解了java动态代理的功能及原理,那仍然是在通过代码硬性编程实现的动态代理,如何在Spring的AOP中实现,看如下分析,AOP中的增强处理(Advice):
创建一个人类共同接口Human(创建说话speak方法和睡觉sleep方法),有其实现类Chinese,通过Spring的Advice增强处理,对speak和sleep方法执行前后做一定的业务处理(LogBeforeAdvice和LogAfterAdvice),eg:预处理、记录日志、结束信息处理等。
- 创建Human接口类,包含speak和sleep方法:
package com.dao; /** * @author 一宿君(CSDN : qq_52596258) * @date 2021-07-14 10:52:07 */ public interface Human { /** * 说话 * @param name */ void speak(String name); /** * 睡觉 */ void sleep(String name); }
- 创建Chinese实现类:
package com.dao.impl; import com.dao.Human; /** * @author 一宿君(CSDN : qq_52596258) * @date 2021-07-14 10:58:51 */ public class Chinese implements Human { @Override public void speak(String name) { System.out.println("你好:" + name); } @Override public void sleep(String name) { System.out.println("你好:" + name + "该睡了"); } }
- 创建前置增强通知LogBeforeAdvice类,实现MethodBeforeAdvice接口:
package com.advice; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; /** * @author 一宿君(CSDN : qq_52596258) * @date 2021-07-15 09:54:58 */ public class LogBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("方法执行前!"); } }
- 创建后置增强通知LogAfterAdvice类,实现AfterReturningAdvice接口:
package com.advice; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; /** * @author 一宿君(CSDN : qq_52596258) * @date 2021-07-15 09:54:58 */ public class LogAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println("方法执行后!"); } }
- applicationContext.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--创建Chinese的Bean实例--> <bean id="ch" class="com.dao.impl.Chinese"/> <!--创建LogBeforeAdvice的Bean实例--> <bean id="logBeforeAdvice" class="com.advice.LogBeforeAdvice"/> <!--创建LogAfterAdvice的Bean实例--> <bean id="logAfterAdvice" class="com.advice.LogAfterAdvice"/> <!--增量式配置--> <bean id="humanProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--setter方法注入:目标对象--> <property name="target" ref="ch"/> <!--面向接口--> <property name="interfaces"> <value>com.dao.Human</value> </property> <!--代理方法:要要执行业务的拦截方法--> <property name="interceptorNames"> <list> <value>logBeforeAdvice</value> <value>logAfterAdvice</value> </list> </property> </bean> </beans>
通过上述配置我们看出,Spring中的配置文件的原理与Proxy代理类的newInstance方法的实现原理是一模一样的。
-
TestAdvice测试类:
package com.test; import com.dao.Human; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author 一宿君(CSDN : qq_52596258) * @date 2021-07-15 10:04:17 */ public class TestAdvice { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Human human1 = (Human)applicationContext.getBean("humanProxy"); human1.speak("狗腿子"); System.out.println("------------------------"); human1.sleep("二狗子"); } }
-
控值台结果:
我们知道代理类值是将一些公共的业务集中在一起,为被代理类的方法进行业务增强处理。,但是有时候并不是所有的核心业务方法都需要进行增强业务处理,所以我们就需要一个业务顾问(Advisor)来询问哪些核心业务需要进行哪写增强操作。 -
applicationContext.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--创建Chinese的Bean实例--> <bean id="ch" class="com.dao.impl.Chinese"/> <!--创建LogBeforeAdvice的Bean实例--> <bean id="logBeforeAdvice" class="com.advice.LogBeforeAdvice"/> <!--创建LogAfterAdvice的Bean实例--> <bean id="logAfterAdvice" class="com.advice.LogAfterAdvice"/> <!--通过日志顾问,允许哪些操作--> <!--日志顾问(前置)--> <bean id="logAdvisorBefore" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"> <property name="mappedName"><!--切入点:拦截方法名(以前缀开头的方法)--> <value>sp*</value> </property> <!--放行的通知Advice--> <property name="advice"> <ref bean="logBeforeAdvice"/> </property> </bean> <!--日志顾问(后置)--> <bean id="logAdvisorAfter" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"> <property name="mappedName"><!--切入点:拦截方法名(以前缀开头的方法)--> <value>sp*</value> </property> <!--放行的通知Advice--> <property name="advice"> <ref bean="logAfterAdvice"/> </property> </bean> <!--增量式配置--> <bean id="humanProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--setter方法注入:目标对象--> <property name="target" ref="ch"/> <!--面向接口--> <property name="interfaces"> <value>com.dao.Human</value> </property> <!--代理方法:要要执行业务的拦截方法--> <property name="interceptorNames"> <list> <!--<value>logBeforeAdvice</value> <value>logAfterAdvice</value>--> <value>logAdvisorBefore</value> <value>logAdvisorAfter</value> </list> </property> </bean> </beans>
-
控制台结果:
5.7 通过< aop:config >来配置AOP事务机制
在使用Spring框架配置AOP的时候,不管是通过XML配置文件还是注解方式,都需要配置“Pointcut”切入点。
AspectJ中定义切入点表达式:execution( com.aop….(…))*
execution()是常用的切点函数,整个表达式可以分为5部分,其语法如下:
- 1 execution():表达式主体;
- 2 第一个*号:表示任意类型的返回值;
- 3 包名com.aop. . :表示需要拦截的包名,后面的两个点表示当前包和当前包下的所有子包都可以扫描到;
- 4 第二个*号:表示要扫描的类名,*表示所有的类;
- 5 第三个*(…)号:表示要扫描的所有类中的所有方法,后面括号代表参数列表,括号里的两个点,代表任意类型的参数均可访问到。
我们通过一个用户实例来演示AOP的AspectJ的切面编程思想:
-
创建UserAction类(目标对象):
package com.aop.action; /** * @author 一宿君(CSDN : qq_52596258) * @date 2021-07-15 11:10:29 */ public class UserAction { private String name; public String getName() { System.out.println("get方法执行了!"); return name; } public void setName(String name) { System.out.println("set方法执行了!"); this.name = name; } }
-
创建Aop类(切面):
package com.aop; /** * @author 一宿君(CSDN : qq_52596258) * @date 2021-07-15 11:09:09 */ public class Aop { public void beforeInvoke(){ System.out.println("前置通知!"); } public void afterInvoke(){ System.out.println("后置操作!"); } }
-
applicationAop.xml切面配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--引入切点(目标对象)--> <bean id="userAction" class="com.aop.action.UserAction"/> <!--引入切面(声明式)--> <bean id="aop" class="com.aop.Aop"/> <!--配置切面--> <aop:config> <!--execution(* com.aop.action.*.*(..))--> <!--表达式 *:函数的返回值 第一个.*:包下所有类 第二个.*:类下所有方法 (..):函数的参数列表(任意) --> <!--execution(* com.aop..*.*(..))--> <!--第一个..*是代表所有类及同级包下的子类都可访问到--> <!--配置全局切入点--> <aop:pointcut id="pointcut1" expression="execution(* com.aop..*.*(..))"/> <!--此处是只对set开头的方法切入--> <aop:pointcut id="pointcut2" expression="execution(* com.aop.action.*.set*(..))"/> <!--配置切入面中的方法--> <aop:aspect ref="aop"> <aop:before method="beforeInvoke" pointcut-ref="pointcut1"/> <aop:after-returning method="afterInvoke" pointcut-ref="pointcut2"/> </aop:aspect> </aop:config> </beans>
-
控制台结果:
5.8 基于Annotation(注解)的装配方式实现AOP事务机制
在Spring中,尽管使用XML配置文件可以实现Bean的装配工作,但如果应用中有很多Bean时,也有很多增强事务通知时,会到值XMl配置文件过于臃肿,不利于后续的升级维护和扩展,因此,Spring就根据这种情况提供了基于Annotation注解技术的全面支持。
Spring中常用的注解方法:
注解名称 | 注解描述 |
---|---|
@Component | 描述Spring中的Bean,是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次,使用时只需将该注解标注在相应类上即可。 |
@Repository | 用于将数据访问层(Dao层)的实现类标识为Spring中的Bean,其功能与@Component相同 |
@Service | 用于将业务层(Service层)的实现类标识为Spring中的Bean,其功能与@Component相同 |
@Controller | 用于将控制层(Controller层)的类标识为Spring中的Bean,其功能与@Component相同 |
@Autowired | 用于对Bean的属性变量、属性的setter方法以及构造方法进行标注,配合对应的注解处理器完成Bean的自动装配工作,默认按照Bean的类型进行装配。 |
@Resource | 其作用与Autowired一样。其区别在于@Autowired默认按照Bean类型装配,而@Resource默认按照Bean实例名称进行装配 |
@Qualifier | 与@Autowired注解配合使用,会将默认的按Bean类型装配修改为按Bean实例名称装配,Bean的实例名称由@Qualifier的参数指定 |
@Aspect | 一般作用在一个共用模块类上,该模块中有多个影响类的公共行为,我们称之为切面,该注解的作用就是把当前的切面类标识为一个切面共容易读取 |
@Pointcut | Pointcut是织入Advice的触发条件,每个Pointcut的定义包括2两部分,一是:表达式;二是:方法签名。方法签名必须是public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码 |
@Around | 环绕增强,相当于MethodInterceptor |
@Before | 前置增强,相当于BeforeAdvice的功能 |
@AfterReturning | 后置增强,想当于AfterReturningAdvice,方法正常退出时执行 |
@AfterThrowing | 异常抛出增强,相当于ThrowsAdvice |
@After | final最终增强,不管是抛出异常还是正常退出都会执行 |
@Resource中有两个重要属性:name和type。Spring将name解析为Bean实例名称,type属性解析为Bean实例类型。如果指定name属性,则按Bean实例名称进行装配;如果指定type属性,则按照Bean类型进行装配;如果都不指定,则先按照Bean实例名称装配,如果不能匹配,再按照Bean类型进行装配;如果都无法匹配,则抛出NoSuchBeanDefinitionException异常。
UserAction.java和Aop.java依然是上述的;
我们把上述在xml配置文件使用的< aop:config > 的相关配置以及Aop的Bean实例注释掉,并开启注解和开启注解扫描包。
- applicationAop.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmln
以上是关于Spring系列Spring中AOP面向切面的编程(动态代理)的主要内容,如果未能解决你的问题,请参考以下文章