Spring AOP

Posted 司机刹一脚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring AOP相关的知识,希望对你有一定的参考价值。

Spring AOP 

  AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
  日志记录,性能统计,安全控制,事务处理,异常处理等等

AOP与OOP区别

  OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
  而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。

AOP相关术语

  目标对象target:指的是需要被增强的对象,由于spring aop是通过代理模式实现,从而这个对象永远是被代理对象。
  连接点(join point):所谓连接点是指那些被拦截到的点,在spring中这些点指的是方法,因为spring只支持方法类型的连接点
  切入点(pointcut):简单说切入点是指我们要对哪些连接点进行拦截的定义
  通知(advice):所谓通知是指拦截到连接点之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知

Advice 定义了在 pointcut 里面定义的程序点具体要做的操作

  引介introduction:引介是一种特殊的通知,在不修改类代码的前提下,introduction可以在运行期为类动态地添加一些方法或属性
  切面aspect:是切入点和通知的结合
  织入weaving:织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期,类装载期,运行期进行。

Spring采用动态织入,而aspectj采用静态织入

  代理Proxy:一个类被AOP织入增强后,就产生一个结果代理类

AOP底层:

  JDK动态代理:在运行 ,在JVM内部动态生成class字节码对象(Class对象)

Jdk动态代理只针对于接口操作

newProxyInstance的三个参数:

  第一个参数:目标类的类加载器对象
  第二个参数:目标类的实现接口的Class[]
  第三个参数:InvocationHandler它是一个接口,它的作用是是代理实例的调用处理程序 实现的接口,接口中定义了一个方法

下面举个例子:

public class ProxyFactory implements InvocationHandler{
    private Object target;
    //在创建对象时,传入代理目标对象
    public ProxyFactory(Object target){
        this.target = target;
    }
    //创建代理对象
    public Object createProxy() {
        //使用proxy创建代理对象
        /*
         * 准备类加载器
         * 准备实现接口的class[]
         * 实现invocationHandler
         * */
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class [] interfaces = target.getClass().getInterfaces();
        return Proxy.newProxyInstance(classLoader, interfaces, this);
    }
    //在代理对象上处理方法并返回结果
    /**
     * 参数1就是代理对象
     * 参数2就是调用方法的method对象
     * 参数3调用方法的参数
     * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("日志操作");
        
        return method.invoke(target, args);
    }
}
@Service(value="userServiceImpl")
public class UserServiceImpl implements UserService {

    @Override
    public void login(String username, String password) {
        System.out.println("登录操作!.....");
    }

    @Override
    public void regist() {
        System.out.println("注册操作!.....");
    }

}
<context:component-scan base-package="com.learn"></context:component-scan>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class TestProxy {
    //准备目标对象
    @Autowired
    private UserService us;
    @Test
    public void test1() {
        //创建代理工厂
        ProxyFactory pf = new ProxyFactory(us);
        //返回代理对象赋值给目标对象
        us = (UserService) pf.createProxy();
        us.regist();
    }
}

 

CGLIB动态代理

  CGLIB(Code Generation Library)是一个开源项目,它可以在运行期扩展Java类与实现Java接口。

CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类

注意:jdk的动态代理只可以为接口去完成操作,而cglib它可以为没有实现接口的类去做代理,也可以为实现接口的类去做代理。

举个例子:

public class CGLIBFactory implements MethodInterceptor{
    //准备目标对象
    private Object target;
    //构造方法
    public CGLIBFactory(Object target) {
        this.target = target;
    } 
    //创建代理对象
    public Object createProxy() {
        //创建Enhancer
        Enhancer enhancer = new Enhancer();
        //传递目标对象的class
        enhancer.setSuperclass(target.getClass());
        //设置回调操作
        enhancer.setCallback(this);
        return enhancer.create();
        
    }
    @Override
    public Object intercept(Object arg0, Method method, Object[] arg2, MethodProxy arg3) throws Throwable {
        System.out.println("日志操作");
        
        return method.invoke(target, arg2);
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class TestCGLB {
    //准备非接口目标对象
    @Autowired
    private UserServiceImpl usi;
    @Test
    public void test() {
        //创建代理类对象,传递目标对象
        CGLIBFactory cf = new CGLIBFactory(usi);
        //获得代理类对象重新赋值给usi
        usi = (UserServiceImpl) cf.createProxy();
        usi.regist();
    }
}

setCallback传递的参数是Callback类型,我们使用的是MethodInterceptor
注意:cglib它可以为没有实现接口的类做代理,也可以为接口类做代理.

spring采用的是哪一种动态机制:

  如果目标对象,有接口,优先使用jdk动态代理
  如果目标对象,无接口,使用cglib动态代理。

Spring AOP编程

  在传统的spring aop开发中它支持增强(advice)有五种:

1. 前置通知 目标方法执行前增强 org.springframework.aop.MethodBeforeAdvice
2. 后置通知 目标方法执行后增强 org.springframework.aop.AfterReturningAdvice
3. 环绕通知 目标方法执行前后进行增强 org.aopalliance.intercept.MethodInterceptor
4. 异常抛出通知 目标方法抛出异常后的增强 org.springframework.aop.ThrowsAdvice
5. 引介通知 在目标类中添加一些新的方法或属性(不做介绍)org.springframework.aop.IntroductionInterceptor

 


下面举个最古老的spring AOP

//准备目标对象
public class OrderServiceImpl implements OrderService {

    @Override
    public void add() {
        System.out.println("add方法执行了.....");
    }

    @Override
    public void update() {
        System.out.println("update方法执行了.......");
    }

}
<?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"
    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">
    <!-- 目标target -->
    <bean id="orderService" class="com.learn.service.impl.OrderServiceImpl"></bean>        
    <!-- 通知advice -->
    <bean id="orderServiceAdvice" class="com.learn.utils.OrderHelper"></bean>
    <!-- 定义切点 -->
    <bean id="orderServicePointCut" class="org.springframework.aop.support.NameMatchMethodPointcut">
        <property name="mappedNames">
            <list>
                <!-- 配置需要拦截的方法 -->
                <value>add</value>
                <value>update</value>
            </list>
        </property>
    </bean>
    <!-- 配置切面aspect=pointCut+advice -->
    <bean id="orderServiceAspect" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="orderServiceAdvice"></property>
        <property name="pointcut" ref="orderServicePointCut"></property>
    </bean>
    <!-- 配置代理 -->
    <bean id="orderServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="orderService"></property>
        <property name="interceptorNames" value="orderServiceAspect"></property>
        <property name="proxyInterfaces" value="com.learn.service.OrderService"></property>
    </bean>
</beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class Aop1Test {
    @Autowired
    @Qualifier("orderServiceProxy")
    private OrderService os;
    @Test
    public void test() {
        os.update();
    }
}

在原有的基础上修改的Spring AOP编程

//准备目标对象
public class OrderServiceImpl implements OrderService {

    @Override
    public void add() {
        System.out.println("add方法执行了.....");
    }

    @Override
    public void update() {
        System.out.println("update方法执行了.......");
    }

}
<?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"
    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"
        >
    <!-- 配置目标对象 -->
    <bean id="orderService" class="order"></bean>
    <!-- 配置通知 -->
    <bean id="orderServiceAdvice" class="com.learn.utils.OrderHelper"></bean>
    <!-- aop来描述切面和切点 -->
    <aop:config>
        <!-- 定义切点 -->
        <aop:pointcut expression="execution(* com.learn.service..*(..))" id="orderServicePointCut"/>
        <!-- 配置切面 -->
        <aop:advisor advice-ref="orderServiceAdvice" pointcut-ref="orderServicePointCut"/>
    </aop:config>
</beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class aop2Test {
    @Autowired
    @Qualifier("orderService")
    private OrderService os;
    @Test
    public void test() {
        os.update();
    }
}

基于aspectJ切点传统开发

@Service("customerService")
public class CustomerServiceImpl implements CustomerService{

    @Override
    public void add() {
        System.out.println("add.........");
    }

    @Override
    public String login() {
        System.out.println("login........");
        return "你好";
    }

}
<?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"
    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"
        >
    <!-- target -->
    <bean id="userService" class="com.learn.service.impl.UserServiceImpl"></bean>
    <!-- advice -->
    <bean id="userServiceAdvice" class="com.learn.utils.UserHelper"></bean>
    <!-- aspectj配置切面 -->
    <aop:config proxy-target-class="true">
        <aop:aspect ref="userServiceAdvice">
            <aop:pointcut expression="execution(* com.learn.service..*(..))" id="delPointCut"/>
            <aop:before method="before" pointcut-ref="delPointCut"/>
            <aop:before method="before1" pointcut-ref="delPointCut"/>
            <aop:after-returning method="afterReturning" pointcut-ref="delPointCut"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="delPointCut"/>
        </aop:aspect>
    </aop:config>
</beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class AspectjTest {
    @Autowired
    @Qualifier("userService")
    private UserService us;
    @Test
    public void test() {
        us.regist();
    }
}

spring框架默认情况下,会对有接口的类使用proxy代理。没有接口的类使用cglib代理
Proxy-target-class的值默认是false,它代表有接口使用proxy代理
问题:如果现在对目标要使用cglib代理,只需要将proxy-target-class设置为true.

 

<aop:config>来声明要对aop进行配置
<aop:pointcut>它是用于声明切点(简单说就是对哪些方法进行拦截)
<aop:advisor> 定义传统的aop的切面,传统的aop切面它只能包含一个切点与一个增强
<aop:aspect>定义aspectj框架的切面.,它可以包含多个切点与多个通知

关于切点表达式的写法:
关于execution语法常用:

1. execution(public * *()) 所有的public的方法
2. execution(* cn.itheima.aop.*(..)) 所有的aop包下的所有类的方法(不包含子包)
3. execution(* cn.itheima.aop..*(..)) 所有的aop包及其子包下的所有类的方法
4. execution(* cn.itheima.aop.IOrderService.*(..)) IOrderService接口中定义的所有方法
5. execution(* cn.itheima.aop.IOrderService+.*(..)) 匹配实现特定接口所有类的方法
6. execution(* save*(..)) 区配所有的以save开头的方法

AspectJ框架它定义的通知类型有6种

1. 前置通知Before 相当于BeforeAdvice
2. 后置通知AfterReturning 相当于AfterReturningAdvice
3. 环绕通知 Around 相当于MethodInterceptor
4. 抛出通知AfterThrowing 相当于ThrowAdvice
5. 引介通知DeclareParents 相当于IntroductionInterceptor
6. 最终通知After 不管是否异常,该通知都会执行相比spring 的传统AOP Advice多了一个最终通知


下面举例用注解配置aop

@Service("customerService")
public class CustomerServiceImpl implements CustomerService{

    @Override
    public void add() {
        System.out.println("add.........");
    }

    @Override
    public String login() {
        System.out.println("login........");
        return "你好";
    }

}
//声明一个实体
@Component("customerServiceAdvice")
//声明为一个bean的切面
@Aspect
public class AnnotationCustomerHelper {
    //声明前置通知
    @Before("execution(* com.learn.service..*(..))")
    public void before() {
        System.out.println("前置通知");
    }
    //声明后置通知
    @AfterReturning(value="execution(* com.learn.service..*(..))",returning="value")
    public void afterReturning(JoinPoint jp,Object value) {
        System.out.println("后置通知,目标方法的返回是="+value);
    }
    //声明环绕通知
    @Around(value="execution(* com.learn.service..*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("环绕前置通知");
        Object value = pjp.proceed();
        System.out.println("环绕后置通知");
        return value;
    }
    //声明异常抛出通知
    @AfterThrowing(value="execution(* com.learn.service..*(..))",throwing="exception")
    public void afterThrowing(JoinPoint jp,Throwable exception) {
        System.out.println("出问题了"+exception);
    }
    //声明最终通知
    @After("execution(* com.learn.service..*(..))")
    public void after() {
        System.out.println("最终通知");
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class AnnotationAopTest {
    @Autowired
    @Qualifier("customerService")
    private CustomerService cs;
    @Test
    public void test1() {
        //cs.add();
        cs.login();
    }
}

 


































以上是关于Spring AOP的主要内容,如果未能解决你的问题,请参考以下文章

JAVA之AOP

Spring AOP

Spring源码高级笔记之——Spring AOP应用

2018.12.24 Spring中的aop演示

Spring框架 AOP

Spring的AOP面向切面编程