Spring核心之AOP

Spring-AOP 代理,增强
目标类 + 额外功能 = 代理类
比如:日志处理/事务处理/异常处理/性能分析…Redis 非关系型数据

  1. 通过spring AOP的代理功能,给代码增加额外的通用功能
  2. 业务逻辑就专心的处理实际需求,通用的增强功能独立出来
  3. 代理的专业术语
    额外功能叫 增强
    目标类叫 切入点 或 切面
    将增强 加入到 切入点,叫编织
    AOP是面向切面编程的一种实现

Spring AOP 知识点思维导图

在这里插入图片描述

什么是AOP?

AOP(Aspect-oriented Programming) , 名字与OOP(Object-oriented programming) 仅差一个字母, 其实它是对OOP编程的一种补充. 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是通过动态代理实现的, 所以我们需要了解下动态代理: https://blog.csdn.net/litianxiang_kaola/article/details/85335700

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">
&lt;!--声明bean--&gt;
&lt;bean id="accountService" class="org.service.impl.AccountServiceImpl"&gt;&lt;/bean&gt;
&lt;bean id="accountAdvice" class="org.aspect.AccountAdvice"&gt;&lt;/bean&gt;

&lt;!--配置代理工厂--&gt;
&lt;bean id="accountProxy" class="org.springframework.aop.framework.ProxyFactoryBean"&gt;
    &lt;!--目标接口--&gt;
    &lt;property name="interfaces" value="org.service.IAccountService"/&gt;
    &lt;!--目标对象--&gt;
    &lt;property name="target" ref="accountService"/&gt;
    &lt;!--增强--&gt;
    &lt;property name="interceptorNames" value="accountAdvice"/&gt;
&lt;/bean&gt;

</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">
&lt;!--声明bean--&gt;
&lt;bean id="accountService" class="org.service.impl.AccountServiceImpl"&gt;&lt;/bean&gt;
&lt;bean id="accountAdvice" class="org.aspect.AccountAdvice"&gt;&lt;/bean&gt;

&lt;!--配置切面--&gt;
&lt;bean id="accountAspect" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"&gt;
    &lt;!--增强--&gt;
    &lt;property name="advice" ref="accountAdvice"/&gt;
    &lt;!--切入点--&gt;
    &lt;property name="pattern" value="org.service.impl.AccountServiceImpl.transfer.*"/&gt;
&lt;/bean&gt;


&lt;!--配置代理--&gt;
&lt;bean id="accountProxy" class="org.springframework.aop.framework.ProxyFactoryBean"&gt;
    &lt;!--目标对象--&gt;
    &lt;property name="target" ref="accountService"/&gt;
    &lt;!--切面--&gt;
    &lt;property name="interceptorNames" value="accountAspect"/&gt;
&lt;/bean&gt;

</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>
&lt;!--配置切面--&gt;
&lt;bean id="accountAspect" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"&gt;
    &lt;!--增强--&gt;
    &lt;property name="advice" ref="accountAdvice"/&gt;
    &lt;!--切入点--&gt;
    &lt;property name="pattern" value="org.service.impl.AccountServiceImpl.transfer.*"/&gt;
&lt;/bean&gt;

&lt;!--配置自动代理: 自动扫描所有切面类, 并为其生成代理--&gt;
&lt;bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/&gt;

</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.*(..))

 
 
  • 1
  • 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>
&lt;!--切面--&gt;
&lt;aop:config&gt;
    &lt;aop:aspect ref="accountAdvice"&gt;
        &lt;!--切入点表达式--&gt;
        &lt;aop:pointcut expression="execution(* org.service.impl.AccountServiceImpl.*(..))" id="myPointCut"/&gt;
        &lt;!--环绕增强--&gt;
        &lt;aop:around method="myAround" pointcut-ref="myPointCut"/&gt;
    &lt;/aop:aspect&gt;
&lt;/aop:config&gt;

</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">
&lt;!--注解扫描--&gt;
&lt;context:component-scan base-package="org.tyshawn"&gt;&lt;/context:component-scan&gt;
&lt;!--自动代理 --&gt;
&lt;aop:aspectj-autoproxy&gt;&lt;/aop:aspectj-autoproxy&gt;

</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
                                </div><div data-report-view="{&quot;mod&quot;:&quot;1585297308_001&quot;,&quot;dest&quot;:&quot;https://blog.csdn.net/litianxiang_kaola/article/details/79123698&quot;,&quot;extend1&quot;:&quot;pc&quot;,&quot;ab&quot;:&quot;new&quot;}"><div></div></div>
            <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-e0530931f4.css" rel="stylesheet">
                            </div>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值