Spring Aop

概念

  • aop (Aspect oriented programming) 面向切面编程,是oop(面向对象的一种补充)。
  • 它采取的是横向抽取机制,将重复的代码提取出来,在程序编译或者执行的时候,将提取出来的代码应用到需要执行的地方。

为什莫产生

  • 代码编写的时候,通常进行事务处理,日志记录等操作,在oop中可以通过组合和继承的方式来完成事务处理,日志操作等相关的行为(父类写一个方法,子类中重写这个方法)。但是如果实现了一个功能(如记录日志),在很多的子类中都会使用这个方法,但是如果这个方法出错或者需要修改,那末将需要修改所有的方法,这样会增加工作量,还会加大代码出错率。

AOP的框架

  • Spring Aop
  • Spring Aop 使用的是纯java实现,不需要进行编译构成和类加载器,在运行期间通过代理的方式向目标类植入增强的代码。
  • AsepctJ
  • 一个基于java的语言的AOp框架,在Spring2.0之后,spring加入了对AspectJ的支持,

AOP的术语

  • Aspect (切面):通常指的是横向插入系统功能(事务处理,日志记录)的类
  • Joinpoint(连接点):对对象的调用,也就是方法的调用或者是异常的抛出,
  • Pointcut(切入点):通常在程序中指的类名或者方法名,
  • Advice(通知/增强处理):在特定的切入点执行的增强处理,
  • Target Object(目标对象):所有被通知的对象,
  • proxy(代理):将通知应用到目标对象之后,被动态的创建对象。
  • Weaving(织入):将切面代码插入到目标对象之后,从而生成代理对象的过程。

Spring Aop两种方式

JDK动态代理

目标类必须实现一个或者多个接口

  • 过程
  • 创建接口UserDao
  • 创建接口的实现类UserDaoImpl
  • 创建切面类,包含各种通知(方法)
  • 创建代理类 implements InvocationHandler, 类中会重写方法invoke(),将通知方法写进去,声明目标类的接口,创建代理方法,return Proxy.newProxyInstance(类加载器,目标类的所有的接口,invoke())

UserDao

package com.yzb.chapter03.JDK;

public interface UserDao {
    public void addUser();
    public void deleteUser();
}

UserDaoImpl

package com.yzb.chapter03.JDK;

public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("添加用户");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}

MyAspect

package com.yzb.chapter03.JDK;
/*
* 切面类 可能存在多个通知,Advice
* */
public class MyAspect {
    public  void check_Permissions(){
        System.out.println("模拟检查权限");
    }
    public void log(){
        System.out.println("模拟记录日志");
    }
}

JdkProxy

package com.yzb.chapter03.JDK;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/*
* JDK代理类
* */
public class JdkProxy implements InvocationHandler {
    /*
    * 生命目标类接口
    * */
    private UserDao userDao;
    /*
    * 创建代理方法
    * */
    public Object createProxy(UserDao userDao){
        this.userDao = userDao;
        // 类加载器
        ClassLoader classLoader = JdkProxy.class.getClassLoader();
        //被代理对象的所有接口
        Class<?>[] interfaces = userDao.getClass().getInterfaces();
        //使用代理类,进行增强,返回的是代理后的对象
        return Proxy.newProxyInstance(classLoader,interfaces,this);
    }
    /*
    * 所有动态代理的方法,都交给invoke()方法去处理
    * proxy被代理后的对象
    * method 将要执行的方法信息(反射)
    * args 执行方法是需要的参数
    * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //声明切面
        MyAspect myAspect = new MyAspect();
        myAspect.check_Permissions();
        Object invoke = method.invoke(userDao, args);
        myAspect.log();
        return invoke;
    }
}

Test1

package com.yzb.chapter03.JDK;

public class Test1 {
    public static void main(String[] args) {
        //创建代理的对象
        JdkProxy jdkProxy = new JdkProxy();
        //创建目标对象
        UserDao userDao = new UserDaoImpl();
        //从代理对象中获取增强后的目标对象
        UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao);
        userDao1.addUser();
        userDao1.deleteUser();
    }
}

CGlib动态代理

  • 采用底层的字节码工作,对指定的目标类生成一个子类,并对子类进行增强。
  • 过程
  • 创建UserDao类
  • 创建MyAspect类(包含各种通知)
  • 创建MyCglib类, implement MethodInterceptor 接口,编写代理方法,核心类Enhancer,(通过new Enhancer创建一个动态类对象)(通过这个对象设置它的父类)(通过这个对象设置回调函数)(生成代理对象),在重写的方法中(前增强,目标类的方法执行,后增强)。
  • 测试类Test2

UserDao

package com.yzb.chapter03.CGLIB;

public class UserDao {

    public void addUser() {
        System.out.println("添加用户");
    }


    public void deleteUser() {
        System.out.println("删除用户");
    }
}

MyAspect

package com.yzb.chapter03.CGLIB;
/*
* 切面类 可能存在多个通知,Advice
* */
public class MyAspect {
    public  void check_Permissions(){
        System.out.println("模拟检查权限");
    }
    public void log(){
        System.out.println("模拟记录日志");
    }
}

MyCglib

package com.yzb.chapter03.CGLIB;


import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;


public class MyCglib implements MethodInterceptor {
    //代理方法
    public Object createProxy(Object target){
        //创建一个动态类对象,它是Cglib的核心类
        Enhancer enhancer = new Enhancer();
        //确定需要增强的类,设置其父类
        enhancer.setSuperclass(target.getClass());
        //添加回调函数
        enhancer.setCallback(this);
        //返回创建的代理类
        return enhancer.create();

    }



    //proxy Cglib根据指定父类生成代理对象
    //method 拦截方法
    //args 拦截方法的参数数组
    //methodProxy 方法的代理对象,用于执行父类的方法
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //声明切面类
        MyAspect myAspect = new MyAspect();
        //前增强
        myAspect.check_Permissions();
        //目标方法的执行
        Object invoke = methodProxy.invoke(proxy, args);
        //后增强
        myAspect.log();
        return invoke;
    }
}

Test2

package com.yzb.chapter03.CGLIB;

public class Test2 {
    public static void main(String[] args) {
        //创建目标对象
        UserDao userDao = new UserDao();
        //创建代理对象
        MyCglib myCglib = new MyCglib();
        //获取增强后的目标对象
       UserDao userDao1 = (UserDao) myCglib.createProxy(userDao);
       //执行方法
       userDao1.addUser();
       userDao1.deleteUser();
    }
}

代理类的Aop实现

在这里插入图片描述
可以使用ProxyFactoryBean,它是实现FactoryBean,FactoryBean是实例化一个Bean,而ProxyFactoryBean是为其他的Bean创建代理实例。

  • target:代理目标对象
  • proxyInterfaces: 代理要实现的接口,如果是多个接口,在赋值的时候使用
  • porxyTargetClass: 是否是类代理而不是接口代理,true时,使用CGlib代理
  • interceptorNames:需要织入的Advice
  • singleton:返回的代理是否时单例
  • optimize(最优化,充分使用):设置为true的时候,强制使用CGlib

UserDao

package com.yzb.chapter03.proxyBeanFactory;

public interface UserDao {
    public void addUser();
    public void deleteUser();
}

UserDaoImpl

package com.yzb.chapter03.proxyBeanFactory;

public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("添加用户");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}

MyAspect

package com.yzb.chapter03.proxyBeanFactory;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAspect implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
       check_Permissions();
        Object proceed = methodInvocation.proceed();
        log();
        return proceed;
    }
    public void check_Permissions(){
        System.out.println("模拟检查权限");
    }
    public void log(){
        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-4.3.xsd">
    <!--目标类-->
    <bean id="userDao" class="com.yzb.chapter03.proxyBeanFactory.UserDaoImpl"></bean>
    <!--切面类-->
    <bean id="myAspect" class="com.yzb.chapter03.proxyBeanFactory.MyAspect"></bean>
    <!--使用Spring代理工厂定义一个名称为userDaoProxy的代理对象-->
    <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--指定代理实现的接口-->
        <property name="proxyInterfaces" value="com.yzb.chapter03.proxyBeanFactory.UserDao"/>
        <!--指定目标对象-->
        <property name="target" ref="userDao"/>
        <!--指定切面,植入环绕通知-->
        <property name="interceptorNames" value="myAspect"/>
        <!--指定代理的方式,true的时候使用CGlib,false的时候使用jdk动态代理-->
        <property name="proxyTargetClass" value="true"/>
    </bean>

</beans>

ProxyFactoryBeanTest

package com.yzb.chapter03.proxyBeanFactory;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ProxyFactoryBeanTest {
    public static void main(String[] args) {
        String path = "com/yzb/chapter03/proxyBeanFactory/applicationContext.xml";
        ApplicationContext ap= new ClassPathXmlApplicationContext(path);
        UserDao userDao = (UserDao) ap.getBean("userDaoProxy");
        userDao.addUser();
        userDao.deleteUser();
    }
}

AspectJ

xml声明式的AspectJ

在这里插入图片描述

  • 所有的切面,通知,切入点都定义在
    在这里插入图片描述

配置切面

  • 将一个定义好的SpringBean 转换成一个切面Bean,
  • 元素的两个属性
  • id:切面的为一表示名称
  • ref:应用普通的SpringBean

配置切入点

  • 两个元素
  • id:作为切入点的唯一表示名称
  • expression:用于指定切入点的关联的切入点表达式
execution(* com.itheima.jdk.*.*(....))
第一个*表示目标方法的返回值的类型
第二个*表示该包下的所有的类
第三个*表示的所有类下的方法名
括号俩面的点表示目标方法中的参数

在这里插入图片描述

配置通知

  • pointcut : 该属性用于指定一个切点表达式,Spring将匹配该表达式的连接点时织入该通知,
  • pointcut-ref :该属性用于指定一个已经存在的切入点名称
  • method: 该属性指定一个方法名,指定将切面Bean中的该方法进行增强处理
  • throwing:只在中有效,用于指定一个形参名,通过该形参抛出目标方法的异常。
  • returning:在中有效,指定一个形参,通过该形参访问目标方法的返回值。

代码

UserDao

package com.yzb.chapter03.aspect;

public interface UserDao {
    public void addUser();
    public void deleteUser();
}

UserDaoImpl

package com.yzb.chapter03.aspect;

public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("添加用户");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}

MyAspect

package com.yzb.chapter03.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/*
* 切面类,在这个类中编写通知
* */
public class MyAspect {
    /*
    * 前置通知
    * */
    public void myBefore(JoinPoint joinPoint){
        System.out.println("前置通知:模拟执行权限检查。。。");
        System.out.println("目标类是:" + joinPoint.getTarget());
        System.out.println("被植入增强处理的目标方法是"+ joinPoint.getSignature().getName());
    }
    /*
    * 后置通知
    * */
    public void myAfterReturning(JoinPoint joinPoint,Object returnVal){
        System.out.println("后置通知,模拟记录日志");
        System.out.println("被植入增强处理的目标方法是"+ joinPoint.getSignature().getName());
    }
    /*
    * 环绕通知
    * ProceedJoinPoint是Joinpoint子接口,表示可以执行目标方法
    * 必须是Object类型的返回值
    * 必须接收一个参数,类型为ProceedJoinPoint
    * 必须throws Throwable
    * */
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //开始
        System.out.println("环绕开始,执行目标方法之前,模拟开启事务");
        //执行目标方法
        Object proceed = proceedingJoinPoint.proceed();
        //结束
        System.out.println("环绕结束,执行目标方法之后,模拟关闭事务。。。");
        return proceed;
    }
    //异常通知
    public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
        System.out.println("异常通知:" + "出错了" + e.getMessage());
    }
    //最终通知
    public void myAfter(){
        System.out.println("最终通知,模拟结束后的释放资源");
    }
}

applicaitonContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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-4.3.xsd
      http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-4.3.xsd
    http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
  <!--配置目标类-->
  <bean id="userDao" class="com.yzb.chapter03.aspect.UserDaoImpl"></bean>
  <!--配置切面类-->
  <bean id="myAspect" class="com.yzb.chapter03.aspect.MyAspect"></bean>
  <!--aop编程-->
  <aop:config>
    <!--配置切面-->
    <aop:aspect ref="myAspect">
      <!--配置切入点,通知增强那些方法-->
      <aop:pointcut id="myPointCut" expression="execution(* com.yzb.chapter03.aspect.*.*(..))"></aop:pointcut>
      <!--前置通知-->
      <aop:before method="myBefore" pointcut-ref="myPointCut"/>
      <!--后置通知,方法返回之后执行,获得放回值
      returning属性:设置后置通知的第二个参数的名称,类型是Object
      -->
      <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal"/>
      <!--环绕通知-->
      <aop:around method="myAround" pointcut-ref="myPointCut"/>
      <!--异常通知
      程序没有异常,则不执行增强
      throwing:用于设置第二个参数的名称,类型为Throwable
      -->
      <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
      <!--最终通知,一定执行-->
      <aop:after method="myAfter" pointcut-ref="myPointCut"/>
    </aop:aspect>
  </aop:config>

</beans>

TestXmlAspectJ

package com.yzb.chapter03.aspect;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestXmlAspectJ {
    public static void main(String[] args) {
        String path = "com/yzb/chapter03/aspect/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(path);
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        userDao.addUser();
    }
}

AspectJ的注解式声明

  • 注解解决了在配置文件中书写大量的配置文件的信息。
  • @Aspect: 定义一个切面
  • @Pointcut:定义一个切入点;用于定义一个切入点表达式,在使用的时候,需要定义一个包含名字和参数的方法签名表示切入点的名称,这个方法签名返回值式Void,且方法体为空。
  • @Before:前置通知,通常需要指定一个point/value的值
  • @AfterReturning:后置通知,通常需要指定point/value的值,和一个returning,这个可以存放一个参数用来访问执行方法后的返回值,
  • @Around:环绕通知,通常需要指定一个point/value
  • @AfterThrowing:异常通知,通常需要指定point/vlaue,和Throwable e的值,e.getMessage()得到错误信息。
  • @After:最终通知,通常需要指定point/value;
  • @DeclareParents:用于定义引介通知,

代码

UserDao

package com.yzb.chapter03.aspectJ;

import org.springframework.stereotype.Component;


public interface UserDao {
    public void addUser();
    public void deleteUser();
}

UserDaoImpl

package com.yzb.chapter03.aspectJ;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("添加用户");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}

MyAspect

package com.yzb.chapter03.aspectJ;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/*
* 切面类,在这个类中编写通知
* */
@Aspect
@Component
public class MyAspect {
    //定义切入点
    @Pointcut("execution(* com.yzb.chapter03.aspectJ .*.*(..))")
    public void myPointCut(){

    }
    /*
    * 前置通知
    * */
    @Before("myPointCut()")
    public void myBefore(JoinPoint joinPoint){
        System.out.println("前置通知:模拟执行权限检查。。。");
        System.out.println("目标类是:" + joinPoint.getTarget());
        System.out.println("被植入增强处理的目标方法是"+ joinPoint.getSignature().getName());
    }
    /*
    * 后置通知
    * */
    @AfterReturning( value = "myPointCut()",returning ="returnVal" )
    public void myAfterReturning(JoinPoint joinPoint,Object returnVal){
        System.out.println("后置通知,模拟记录日志");
        System.out.println("被植入增强处理的目标方法是"+ joinPoint.getSignature().getName());
    }
    /*
    * 环绕通知
    * ProceedJoinPoint是Joinpoint子接口,表示可以执行目标方法
    * 必须是Object类型的返回值
    * 必须接收一个参数,类型为ProceedJoinPoint
    * 必须throws Throwable
    * */
    @Around("myPointCut()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //开始
        System.out.println("环绕开始,执行目标方法之前,模拟开启事务");
        //执行目标方法
        Object proceed = proceedingJoinPoint.proceed();
        //结束
        System.out.println("环绕结束,执行目标方法之后,模拟关闭事务。。。");
        return proceed;
    }
    //异常通知
    @AfterThrowing(value = "myPointCut()",throwing = "e")
    public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
        System.out.println("异常通知:" + "出错了" + e.getMessage());
    }
    //最终通知
    @After("myPointCut()")
    public void myAfter(){
        System.out.println("最终通知,模拟结束后的释放资源");
    }
}

ApplicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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-4.3.xsd
      http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-4.3.xsd
    http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <!--指定需要扫描的包,式注解生效-->
    <context:component-scan base-package="com.yzb.chapter03.aspectJ"/>
    <!--启动基于aspectJ的声明的支持-->
    <aop:aspectj-autoproxy/>

</beans>

Test5

package com.yzb.chapter03.aspectJ;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test5 {
    public static void main(String[] args) {
        String path = "com/yzb/chapter03/aspectJ/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(path);
       UserDao userDao = (UserDao) applicationContext.getBean("userDao");
       userDao.addUser();
    }
}

结果图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值