1 AOP
1.1 AOP是什么
如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用。
AOP
即 Aspect Oriented Program 面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。
- 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务(主业务)
- 所谓的周边功能,比如性能统计,日志,事务管理等等(辅助业务)
核心业务在 Spring 的面向切面编程AOP思想里,即被定义为切入点
周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP
1.2 AOP 的目的
AOP
能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
1.3 AOP 当中的概念
名称 | 说明 |
---|---|
Joinpoint(连接点) | 指那些被拦截到的点,在 Spring 中,可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) | 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到 Joinpoint 之后要做的事情,即对切入点增强/通知的内容。 |
Target(目标) | 指代理的目标对象。 |
Weaving(织入) | 把切面(Aspect)加入到目标对象(target),并创建出代理对象(proxy)的过程,由Spring 来完成。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切入点+通知的结合。通俗点就是:在什么时机,什么地方,做什么增强/通知! |
1.4 Spring aop所需要的包:
- Spring四大基础包(core、beans、context、expression
- 日志包:commons-logging.jar
- aop扩展包(用来环绕通知):aopalliance.jar(aop联盟)
提供一个百度云连接:链接提取码:5nnr
1.5 Spring aop常用增强/通知方法
类型 | 名称 | 说明 |
---|---|---|
前置通知 | org.springframework.aop.MethodBeforeAdvice | 在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。 |
后置通知 | org.springframework.aop.AfterReturningAdvice | 在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。 |
环绕通知 | org.aopalliance.intercept.MethodInterceptor | 在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。 |
异常通知 | org.springframework.aop.ThrowsAdvice | 在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。 |
引介通知 | org.springframework.aop.IntroductionInterceptor | 在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。 |
2 AOP实例
2.1 idea 建立一个普通的web 4.0项目,并导入包
项目目录
2.2 代码
一个买手机的小例子,增加一些趣味性
BuyPhone接口
package com.cheng.spring.service;
//主业务,购买手机服务
public interface BuyPhone {
//编写业务方法
public void giveMeAphone(String name, String pass);
}
BuyPhoneImpl实现类
package com.cheng.spring.serviceImpl;
import com.cheng.spring.service.BuyPhone;
public class BuyPhoneImpl implements BuyPhone {
@Override
public void giveMeAphone(String name, String pass) {
System.out.println("我的身份只有8848钛金手机才配得上,8848你值得拥有");
}
}
三种AOP增强/通知方法
前置通知
BuyPhoneBeforeAdvice
package com.cheng.spring.advice;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
//购买手机业务的前置通知
//前置通知需要实现MethodBeforeAdvice接口
public class BuyPhoneBeforeAdvice implements MethodBeforeAdvice {
//前置通知,做用户身份校验
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
//arg1是主业务当中提供的参数
String name = arg1[0].toString();
String pass = arg1[1].toString();
if(name.equals("此成")&&pass.equals("123")){
System.out.println(name+"同学,欢迎来本站购物---前置通知");
}else{
throw new RuntimeException("对不起,您不是本站会员,购物前请先注册");
}
}
}
后置通知
BuyPhoneAfterAdvice
package com.cheng.spring.advice;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
//后置通知需要实现AfterReturningAdvice接口
public class BuyPhoneAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3)
throws Throwable {
System.out.println("自从用了8848,身体倍儿棒,吃嘛嘛香。---后置通知");
}
}
环绕通知
BuyPhoneAroundAdvice
package com.cheng.spring.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
//环绕通知需要实现MethodInterceptor接口
//环绕通知:在主业务的前,后执行服务功能
public class BuyPhoneAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//环绕通知的前置
System.out.println("此成同学,您定制的钛金手机给您送来了,赶紧拍照发朋友圈---环绕前置");
//执行核心业务,作为环绕的前后通知分割点
invocation.proceed();
System.out.println("用了两天就坏了,智商税还交的不够啊!---环绕后置");
//环绕通知的后置
return null;
}
}
Spring Bean配置 (applicationContext.xml)
Spring 创建一个 AOP 代理的基本方法是使用代理工厂:
org.springframework.aop.framework.ProxyFactoryBean,这个类对应的切入点和通知提供了比较完整的控制能力,并可以生成指定的内容。
ProxyFactoryBean 的常用属性
属性名称 | 描 述 |
---|---|
target | 代理的目标对象 |
proxyInterfaces | 代理要实现的接口,如果有多个接口,则可以使用以下格式赋值:<list><value ></value><value ></value></list> |
proxyTargetClass | 是否对类代理而不是接口,设置为 true 时,使用 CGLIB 代理 |
interceptorNames | 需要植入目标的 Advice |
singleton | 返回的代理是否为单例,默认为 true(返回单实例) |
optimize | 当设置为 true 时,强制使用 CGLIB |
有两种方式
第一种:最基础的AOP配置
<?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">
<!-- 配置切入点-->
<bean id="buyPhoneImpl" class="com.cheng.spring.serviceImpl.BuyPhoneImpl"/>
<!-- 配置切入面:前置、后置、环绕通知 -->
<bean id="buyPhoneBeforeAdvice" class="com.cheng.spring.advice.BuyPhoneBeforeAdvice"/>
<bean id="buyPhoneAfterAdvice" class="com.cheng.spring.advice.BuyPhoneAfterAdvice"/>
<bean id="buyPhoneAroundAdvice" class="com.cheng.spring.advice.BuyPhoneAroundAdvice"/>
<!-- 将前置通知织入到切入点 -->
<bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理实现的接口 -->
<property name="proxyInterfaces" value="com.cheng.spring.service.BuyPhone"/>
<!-- 代理的目标对象 -->
<property name="target" ref="buyPhoneImpl" />
<!-- 用通知增强目标 -->
<property name="interceptorNames" value="buyPhoneAroundAdvice,buyPhoneBeforeAdvice,buyPhoneAfterAdvice" />
<!-- 如何生成代理,true:使用cglib,对于不是实现接口的类使用; 默认false :使用jdk动态代理,对于实现接口的类使用 -->
<property name="proxyTargetClass" value="false" />
</bean>
</beans>
第二种:使用p标签配置,需要在<beans>
中引用p标签
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置切入点-->
<bean id="buyPhoneImpl" class="com.cheng.spring.serviceImpl.BuyPhoneImpl"/>
<!-- 配置切入面:前置、后置、环绕通知 -->
<bean id="buyPhoneBeforeAdvice" class="com.cheng.spring.advice.BuyPhoneBeforeAdvice"/>
<bean id="buyPhoneAfterAdvice" class="com.cheng.spring.advice.BuyPhoneAfterAdvice"/>
<bean id="buyPhoneAroundAdvice" class="com.cheng.spring.advice.BuyPhoneAroundAdvice"/>
<!-- 将前置通知织入到切入点 -->
<!-- 织入过程通过spring代理类来进行实现:org.springframework.aop.framework.ProxyFactoryBean
p 表示对载入类属性的引用
p:proxyInterfaces 指定需要织入的服务接口
p:target-ref 指定织入的切入点
p:interceptorNames 指定织入的切面(通知),通知配置的顺序根据先后关系来确定的
p:proxyTargetClass 默认值为false,代理的是接口类,采用JDK进行动态代理
true:代理的不是接口类,使用CGLIB方式来进行代理。
false会自动判断,如果是要代理的是接口类就采用JDK进行动态代理,如果要代理的不是接口类就采用cglib代理
-->
<bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.cheng.spring.service.BuyPhone"
p:target-ref="buyPhoneImpl"
p:interceptorNames="buyPhoneBeforeAdvice,buyPhoneAfterAdvice,buyPhoneAroundAdvice"
p:proxyTargetClass="false"
/>
</beans>
Test主函数
package com.cheng.spring.test;
import com.cheng.spring.service.BuyPhone;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
//如何通过context模块加载配置文件,获取bean实例
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
//如果要启动主业务,我需要获取哪个bean的实例?
BuyPhone buyPhone = (BuyPhone)context.getBean("factoryBean");
buyPhone.giveMeAphone("此成", "123");
}
}
2.3 执行结果
可以看到只执行了buyPhone的一个方法,三个增强/通知也出现在控制台了,这便是AOP的面向切面的编程。
在这次执行中,环绕通知前置在前置通知前,环绕通知后置在后置通知后,是不是说明环绕通知的优先级高呢?
我们将buyPhoneAroundAdvice放到最后一个再运行试试
<bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.cheng.spring.service.BuyPhone"
p:target-ref="buyPhoneImpl"
p:interceptorNames="buyPhoneBeforeAdvice,buyPhoneAfterAdvice,buyPhoneAroundAdvice"/>
结果:
可以看到,优先级跟上一次运行的不同了,所以他们的优先级取决于p:interceptorNames配置的先后。
文毕,本文如有助,赞后吾更嗨~
下一篇:Spring学习(8)-AOP之ProxyFactoryBean、RegexMethodPointcutAdvisor、BeanNameAutoProxyCreator