AOP 知识点思维导图
什么是AOP?
AOP(
Aspect-oriented Programming
) , 名字与OOP(Object-oriented programming
)
仅差一个字母, 其实它是对OOP编程的一种补充. AOP翻译过来叫面向切面编程, 核心就是这个切面. 切面表示从业务逻辑中分离出来的横切逻辑, 比如性能监控, 日志记录, 权限控制等, 这些功能都可以从核心业务逻辑代码中抽离出来. 也就是说, 通过AOP可以解决代码耦合问题, 让职责更加单一.
aop作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开。
aop使用场景
aop框架种类
-
AspectJ
-
JBoss AOP
-
Spring AOP
使用aop可以做的事情有很多。
-
性能监控,在方法调用前后记录调用时间,方法执行太长或超时报警。
-
缓存代理,缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。
-
软件破解,使用AOP修改软件的验证类的判断逻辑。
-
记录日志,在方法执行前后记录系统日志。
-
工作流系统,工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离,并动态挂接业务。
-
权限验证,方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕捉。
观察一下传统编码方式与使用aop的区别
我们来通过代码来理解下概念, 这里有一个转账业务:
public interface IAccountService {
//主业务逻辑: 转账
void transfer();
}
public class AccountServiceImpl implements IAccountService {
@Override
public void transfer() {
System.out.println("调用dao层,完成转账主业务.");
}
}
现在有一个需求是, 在转账之前需要验证用户身份, 一般情况下我们直接就去修改源代码了:
public class AccountServiceImpl implements IAccountService {
@Override
public void transfer() {
System.out.println("对转账人身份进行验证.");
System.out.println("调用dao层,完成转账主业务.");
}
}
但作为一个"有经验"的Java开发, 我们知道 “身份验证” 这个业务完全是可以剥离出来的, 所以使用下代理设计模式的思想:
public class AccountProxy implements IAccountService {
//目标对象
private IAccountService target;
public AccountProxy(IAccountService target) {
this.target = target;
}
/**
* 代理方法,实现对目标方法的功能增强
*/
@Override
public void transfer() {
before();
target.transfer();
}
/**
* 身份验证
*/
private void before() {
System.out.println("对转账人身份进行验证.");
}
}
public class Client {
public static void main(String[] args) {
//创建目标对象
IAccountService target = new AccountServiceImpl();
//创建代理对象
AccountProxy proxy = new AccountProxy(target);
proxy.transfer();
}
}
AOP的实现原理就是代理设计模式, 上面这个是静态代理, 但Spring AOP是通过动态代理实现的
AOP并不是由Spring独创, 在Spring之前就有了AOP开源框架—AspectJ,
而且AspectJ的AOP功能要比Spring更强大, 所以在Spring的后期版本中就集成了AspectJ. 但我们还是有必要了解下
Spring的AOP功能.
AOP术语
- Targe
目标对象 - Joinpoint
连接点, 所有可能被增强的方法都是连接点. - Pointcut
切入点, 将被增强的方法. - Advice
增强, 从主业务逻辑中剥离出来的横切逻辑. - Aspect
切面, 切入点加上增强就是切面. - Weaving
织入, 把切面应用到目标对象上的过程. - Proxy
代理对象, 被增强过的目标对象.
Advice常见类型
- 前置增强
org.springframework.aop.MethodBeforeAdvice
, 在目标方法执行前实施增强. - 后置增强
org.springframework.aop.AfterReturningAdvice
, 在目标方法执行后实施增强. - 环绕增强
org.aopalliance.intercept.MethodInterceptor
, 在目标方法执行前后都实施增强. - 异常抛出增强
org.springframework.aop.ThrowsAdvice
, 在方法抛出异常后实施增强. - 引入增强
org.springframework.aop.IntroductionInterceptor
, 对类进行增强, 即在目标类中添加一些新的方法和属性.
Spring AOP 之编程式
我们用编程式来感受下Spring AOP. 下面例子使用的是环绕增强.
(1) 业务类
public interface IAccountService {
//主业务逻辑: 转账
void transfer();
}
public class AccountServiceImpl implements IAccountService {
@Override
public void transfer() {
System.out.println("调用dao层,完成转账主业务.");
}
}
(2) 增强
public class AccountAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
before();
Object result = invocation.proceed();
after();
return result;
}
private void before() {
System.out.println("Before");
}
private void after(){
System.out.println("After");
}
}
(3) 测试
public class Test {
public static void main(String[] args) {
//创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
//配置目标对象
proxyFactory.setTarget(new AccountServiceImpl());
//配置增强
proxyFactory.addAdvice(new AccountAdvice());
IAccountService proxy = (IAccountService) proxyFactory.getProxy();
proxy.transfer();
}
}
结果:
Before
调用dao层,完成转账主业务.
After
Spring AOP 之声明式
Spring AOP 之声明式就是使用配置文件来声明各种bean.
(1) 业务类
public interface IAccountService {
//主业务逻辑: 转账
void transfer();
}
public class AccountServiceImpl implements IAccountService {
@Override
public void transfer() {
System.out.println("调用dao层,完成转账主业务.");
}
}
(2) 增强
public class AccountAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
before();
Object result = invocation.proceed();
after();
return result;
}
private void before() {
System.out.println("Before");
}
private void after(){
System.out.println("After");
} }
(3) 配置文件
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--声明bean-->
<bean id="accountService" class="org.service.impl.AccountServiceImpl"></bean>
<bean id="accountAdvice" class="org.aspect.AccountAdvice"></bean>
<!--配置代理工厂-->
<bean id="accountProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--目标接口-->
<property name="interfaces" value="org.service.IAccountService"/>
<!--目标对象-->
<property name="target" ref="accountService"/>
<!--增强-->
<property name="interceptorNames" value="accountAdvice"/>
</bean>
</beans>
(4) 测试
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-service.xml");
IAccountService proxy = (IAccountService) context.getBean("accountProxy");
proxy.transfer();
}
}
结果:
Before
调用dao层,完成转账主业务.
After
Spring 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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--声明bean-->
<bean id="accountService" class="org.service.impl.AccountServiceImpl"></bean>
<bean id="accountAdvice" class="org.aspect.AccountAdvice"></bean>
<!--配置切面-->
<bean id="accountAspect" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--增强-->
<property name="advice" ref="accountAdvice"/>
<!--切入点-->
<property name="pattern" value="org.service.impl.AccountServiceImpl.transfer.*"/>
</bean>
<!--配置代理-->
<bean id="accountProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--目标对象-->
<property name="target" ref="accountService"/>
<!--切面-->
<property name="interceptorNames" value="accountAspect"/>
</bean>
</beans>
这里的切面配置的是基于正则表达式的 RegexpMethodPointcutAdvisor
, 表示拦截所有以 “transfer” 开头的方法. 除此之外, Spring AOP 还有以下配置:
org.springframework.aop.support.DefaultPointcutAdvisor
匹配继承了该类的切面.org.springframework.aop.support.NameMatchMethodPointcutAdvisor
根据方法名称进行匹配.org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor
用于匹配静态方法.
Spring AOP 之自动代理
让用户去配置一个或少数几个代理, 似乎还可以接受, 但随着项目的扩大, 代理配置会越来越多, 这个时候再让你手动配置, 那整个人都不好了.
不过不用担心, Spring AOP 为我们提供了自动生成代理的功能.
在声明式的代码基础上修改下配置文件和测试类:
(1) 配置文件
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--声明bean-->
<bean id="accountService" class="org.service.impl.AccountServiceImpl"></bean>
<bean id="accountAdvice" class="org.aspect.AccountAdvice"></bean>
<!--配置切面-->
<bean id="accountAspect" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--增强-->
<property name="advice" ref="accountAdvice"/>
<!--切入点-->
<property name="pattern" value="org.service.impl.AccountServiceImpl.transfer.*"/>
</bean>
<!--配置自动代理: 自动扫描所有切面类, 并为其生成代理-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
</beans>
(2) 测试
public class Test{
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-service.xml");
IAccountService proxy = (IAccountService) context.getBean("accountService");
proxy.transfer();
}
}
结果:
Before
调用dao层,完成转账主业务.
After
Spring + AspectJ
在企业开发中几乎不使用Spring自身的AOP功能, 而是用AspectJ代替,
Spring在后期自己集成了AspectJ也间接证明了AspectJ的强大, 我们下面来了解下 Spring + AspectJ.
AspectJ 增强类型
- 前置增强
注解:@Before, 配置: < aop:before>
- 后置增强
注解:@After, 配置: < aop:after>
- 环绕增强
注解:@Around, 配置: < aop:around>
- 异常抛出增强
注解:@AfterThrowing, 配置: < aop:after-throwing>
- 引入增强
注解:@DeclareParents, 配置: < aop:declare-parents>
切入点表达式
execution(* org.service.impl.AccountServiceImpl.*(..))
execution()表示拦截方法, 括号中可定义需要匹配的规则.
- 第一个 “*” 表示方法的返回值是任意的.
- 第二个 “*” 表示匹配该类中所有的方法.
(…) 表示方法的参数是任意的.
可以看到, AspectJ的切入点表达式要比Spring AOP的正则表达式可读性更强.
Spring AspectJ实例—基于配置
(1) 业务类
public interface IAccountService {
//主业务逻辑: 转账
void transfer();
}
public class AccountServiceImpl implements IAccountService {
@Override
public void transfer() {
System.out.println("调用dao层,完成转账主业务.");
}
}
(2) 增强
public class AccountAdvice{
//前置增强
public void myBefore(JoinPoint joinPoint){
before();
}
//后置增强
public void myAfter(JoinPoint joinPoint) {
after();
}
//环绕增强
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
before();
Object result = joinPoint.proceed();
after();
return result;
}
//抛出异常增强
public void myThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("抛出异常增强: " + e.getMessage());
}
private void before() {
System.out.println("Before");
}
private void after(){
System.out.println("After");
}
}
(3) 配置
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--声明bean-->
<bean id="accountService" class="org.service.impl.AccountServiceImpl"></bean>
<bean id="accountAdvice" class="org.aspect.AccountAdvice"></bean>
<!--切面-->
<aop:config>
<aop:aspect ref="accountAdvice">
<!--切入点表达式-->
<aop:pointcut expression="execution(* org.service.impl.AccountServiceImpl.*(..))" id="myPointCut"/>
<!--环绕增强-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
(4) 测试
public class Test{
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-service.xml");
IAccountService proxy = (IAccountService) context.getBean("accountService");
proxy.transfer();
}
}
结果:
Before
调用dao层,完成转账主业务.
After
Spring AspectJ实例—基于注解
(1) 业务类
public interface IAccountService {
//主业务逻辑: 转账
void transfer();
}
@Component
public class AccountServiceImpl implements IAccountService {
@Override
public void transfer() {
System.out.println("调用dao层,完成转账主业务.");
}
}
(2) 切面
这里就不再叫增强了, 因为有了切入点和增强, 叫切面更好.
@Component
@Aspect
public class AccountAspect{
//切入点
@Pointcut("execution(* org.tyshawn.service.impl.AccountServiceImpl.*(..))")
private void pointCut(){};
//前置增强
@Before("pointCut()")
public void myBefore(JoinPoint joinPoint){
before();
}
//后置增强
@After("pointCut()")
public void myAfter(JoinPoint joinPoint) {
after();
}
//环绕增强
@Around("pointCut()")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
before();
Object result = joinPoint.proceed();
after();
return result;
}
//抛出异常增强
@AfterThrowing(value = "pointCut()", throwing = "e")
public void myThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("抛出异常增强: " + e.getMessage());
}
private void before() {
System.out.println("Before");
}
private void after(){
System.out.println("After");
}
}
(3) 配置
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--注解扫描-->
<context:component-scan base-package="org.tyshawn"></context:component-scan>
<!--自动代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
(4) 测试
public class Test{
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-service.xml");
IAccountService proxy = (IAccountService) context.getBean("accountServiceImpl");
proxy.transfer();
}
}
结果:
Before
Before
调用dao层,完成转账主业务.
After
After