Spring AOP讲解

三、Spring AOP

1. AOP前奏

1.1 需求

  • 日志,在程序执行期间追踪正在发生的活动
  • 验证,程序执行期间处理合法数据

1.2 代码

UserDao接口以及实现类

public interface UserDao {
    void insertUser();
    void updateUser();
    void deleteUser(int id);
    List<String> selectUser();
}

public class UserDaoImpl implements UserDao {
    @Override
    public void insertUser() {
        //在方法种主要业务逻辑代码开始之前,加入日志记录
        System.out.println("日志:用户新增...");

        System.out.println("通过JDBC实现用户新增...");
    }

    @Override
    public void updateUser() {
        System.out.println("日志:用户修改...");

        System.out.println("通过JDBC实现用户修改...");
    }

    @Override
    public void deleteUser(int id) {
        System.out.println("日志:用户删除...");

        System.out.println("通过JDBC实现用户删除...");
    }

    @Override
    public List<String> selectUser() {
        System.out.println("日志:用户查询...");

        System.out.println("通过JDBC实现用户查询...");
        return null;
    }
}

测试

public class ProxyTest {
    @Test
    public void test(){
        UserDao userDao = new UserDaoImpl();

        userDao.insertUser();

        userDao.updateUser();

        userDao.deleteUser(10);

        List<String> list =  userDao.selectUser();
        System.out.println(list);
    }
}

1.3 问题

  • 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点
  • 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块

2. 代理模式

使用代理模式解决上述问题

Spring AOP讲解_第1张图片

代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上

2.1 静态代理

每个业务接口都需要有一个对应的代理类,并实现业务接口

public class UserDaoProxy implements UserDao {
    private UserDao userDao;

    public UserDaoProxy(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void insertUser() {
        System.out.println("日志记录:用户新增...");
        userDao.insertUser();
    }

    @Override
    public void updateUser() {
        System.out.println("日志记录:用户修改...");
        userDao.updateUser();
    }

    @Override
    public void deleteUser(int id) {
        System.out.println("日志记录:用户删除..." + id);
        userDao.deleteUser(id);
    }

    @Override
    public List<String> selectUser() {
        System.out.println("日志记录:用户查询...");
        return userDao.selectUser();
    }
}

测试

public class ProxyTest {
    @Test
    public void testStaticProxy(){
        UserDao userDao = new UserDaoProxy(new UserDaoImpl());

        userDao.insertUser();

        userDao.updateUser();

        userDao.deleteUser(10);

        List<String> list =  userDao.selectUser();
        System.out.println(list);
    }
}
  • 优点: 简单、易于理解
  • 缺点: 项目庞大后业务接口过多,造成代理类过多

2.2 JDK动态代理

JDK动态代理:采用Java反射机制中的动态代理,实现InvocationHandler接口

public class LoggerProxy implements InvocationHandler {
    /**
     * 执行目标(最终要执行业务逻辑类)方法
     * @param proxy 代理类对象
     * @param method 目标方法对象
     * @param args 目标方法的参数值列表
     * @return 目标方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //日志记录的代码
        System.out.println("代理日志:目标方法--前置" + method.getName() + "....");
        //执行目标方法(业务逻辑方法)
        Object result_val = null;
        try{
            result_val =  method.invoke(targetClass.newInstance(), args);
            System.out.println("代理日志:目标方法--返回" + method.getName() + "....");
        }catch (Exception e){
            System.out.println("代理日志:目标方法--异常" + method.getName() + "....");
        }
        System.out.println("代理日志:目标方法--后置" + method.getName() + "....");
        return result_val;
    }
    /**
     * 目标对象的Class类型
     */
    private Class targetClass;

    /**
     * 获取代理对象
     *
     * 注意:JDK中的动态代理,要求业务类必须是接口+实现类的形式
     */
    public Object getProxy(Class targetClass){
        this.targetClass = targetClass;
        return Proxy.newProxyInstance(targetClass.getClassLoader(),targetClass.getInterfaces(),this);
    }
}

测试

public class ProxyTest {
    @Test
    public void testDynamicProxy(){
        UserDao userDao = (UserDao) new LoggerProxy().getProxy(UserDaoImpl.class);
        System.out.println(userDao);
        userDao.insertUser();
        userDao.updateUser();
        userDao.deleteUser(10);
        List<String> list =  userDao.selectUser();
        System.out.println(list);
        System.out.println("---------------------------");
        PersonDao personDao = (PersonDao) new LoggerProxy().getProxy(PersonDaoImpl.class);
        personDao.insertPerson();
    }
}

2.3 Cglib动态代理

Cglib动态代理,实现cglib包下的MethodInterceptor接口

public class CglibProxy implements MethodInterceptor {
    /**
     *
     * @param obj CGLib动态生成的代理类对象
     * @param method 目标方法对象
     * @param args 目标方法的参数值列表
     * @param proxy 代理类对方法的代理对象
     * @return 目标方法的返回值
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Cglib动态代理...");
        //执行目标方法(业务逻辑方法)
        Object result_val = null;
        try{
            result_val =  method.invoke(targetClass.newInstance(), args);
        }catch (Exception e){
        }
        return result_val;
    }

    /**
     * 目标对象的Class类型
     */
    private Class targetClass;

    /**
     * 获取代理对象
     *
     * 注意:JDK中的动态代理,要求业务类必须是接口+实现类的形式
     */
    public Object getProxy(Class targetClass){
        //为目标对象target赋值
        this.targetClass = targetClass;
        Enhancer enhancer = new Enhancer();
        //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
        enhancer.setSuperclass(targetClass);
        //设置回调
        enhancer.setCallback(this);
        //创建并返回代理对象
        Object result = enhancer.create();
        return result;
    }
}

测试

public class ProxyTest {
    @Test
    public void testCglibProxy(){
        UserDao userDao = (UserDao) new CglibProxy().getProxy(UserDaoImpl.class);
        System.out.println(userDao);
        userDao.insertUser();
        userDao.updateUser();
        userDao.deleteUser(10);
        List<String> list =  userDao.selectUser();
        System.out.println(list);
    }
}

3. AOP简介

  • AOP(Aspect Orienten Programming)面向切面编程,是一种新的方法论,是对传统 OOP(Object-Oriented Programming)面向对象编程的补充
  • AOP的主要编程对象是切面(aspect),而切面模块化的横切关注点
  • 在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的对象(切面)里
  • AOP的好处:每个事物逻辑位于一个位置,代码不分散,便于维护和升级。业务模块更简洁,只包含核心业务代码

Spring AOP讲解_第2张图片

4. AOP术语

  • 切面aspect:横切关注点(跨越多个模块的功能),使其成为一个独立,应用于其他模块之中的模块。被模块化的特殊对象
  • 通知adivce:切面需要完成的工作
  • 目标target:被通知的对象
  • 代理proxy:向目标对象通知之后创建的对象
  • 连接点joinPoint:程序执行的某个特殊位置
    ​比如:某个方法在被调用时,方法执行之前,方法执行之后,方法返回值之后,方法抛出异常之后
  • 切点pointCut:由众多连接点组成的

通知类型

  • 前置通知:在方法执行之前实施的通知
  • 后置通知:在方法执行之后实施的通知
  • 返回通知:在方法返回结束之后实施的通知
  • 异常通知:在方法出现异常并抛出异常之后实施的通知
  • 环绕通知:围绕着目标方法实施通知,实际上就是以上所有通知的综合

5. AOP的开发步骤

5.1 加入相应的jar包

Spring AOP讲解_第3张图片

maven依赖配置

<dependencies>
  
  <dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-coreartifactId>
    <version>4.3.18.RELEASEversion>
  dependency>
  
  <dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-beansartifactId>
    <version>4.3.18.RELEASEversion>
  dependency>
  
  <dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-contextartifactId>
    <version>4.3.18.RELEASEversion>
  dependency>
  
  <dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-expressionartifactId>
    <version>4.3.18.RELEASEversion>
  dependency>
  
  <dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-aopartifactId>
    <version>4.3.18.RELEASEversion>
  dependency>
  
  <dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-aspectsartifactId>
    <version>4.3.18.RELEASEversion>
  dependency>
  <dependency>
    <groupId>junitgroupId>
    <artifactId>junitartifactId>
    <version>4.12version>
    <scope>testscope>
  dependency>
dependencies>

5.2 命名空间以及标签规范

在spring的配置文件中加入aop命名空间以及aop标签规范

xmlns:aop="http://www.springframework.org/schema/aop"

http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd

5.3 编写切面类

定义一个普通Java类,在其中添加功能方法

/**
 * 日志切面类:日志记录功能
 */
public class LogAspect {

    /**
     * 前置通知:在目标方法执行之前实现的功能
     */
    public void beforeMethod(){
        System.out.println("AOP日志记录:前置通知......");
    }
}

切面完整代码

/**
 * 日志切面类:日志记录功能
 */
public class LogAspect {

    /**
     * 前置通知:在目标方法执行之前实现的功能
     * 参数:JoinPoint连接点对象,可获取当前要执行的目标方法的信息
     */
    public void beforeMethod(JoinPoint joinPoint){
        //通过连接点对象获取方法的名称
        String methodName = joinPoint.getSignature().getName();
        //方法的参数值列表
        Object[] args = joinPoint.getArgs();
        System.out.println("AOP日志记录:前置通知......" + methodName + Arrays.toString(args));
    }
    /**
     * 后置通知:在目标方法执行之后实现的功能
     */
    public void afterMethod(JoinPoint joinPoint){
        //通过连接点对象获取方法的名称
        String methodName = joinPoint.getSignature().getName();
        //方法的参数值列表
        Object[] args = joinPoint.getArgs();
        System.out.println("AOP日志记录:后置通知......" + methodName + Arrays.toString(args));
    }

    /**
     * 返回通知:在目标方法有返回值之后实现的功能
     * 通过方法的参数获取目标方法的返回值数据
     */
    public void afterReturnMethod(JoinPoint joinPoint, Object resultValue){
        //通过连接点对象获取方法的名称
        String methodName = joinPoint.getSignature().getName();
        //方法的参数值列表
        Object[] args = joinPoint.getArgs();
        System.out.println("AOP日志记录:返回通知......" + methodName + Arrays.toString(args));
        System.out.println("方法的返回值为:" + resultValue);
    }

    /**
     * 异常通知:在目标方法抛出异常之后实现的功能
     * 通过方法参数获取目标方法抛出的异常对象
     */
    public void afterThrowMethod(JoinPoint joinPoint, Exception ex){
        //通过连接点对象获取方法的名称
        String methodName = joinPoint.getSignature().getName();
        //方法的参数值列表
        Object[] args = joinPoint.getArgs();
        System.out.println("AOP日志记录:异常通知......" + methodName + Arrays.toString(args));
        System.out.println("方法抛出的异常对象:" + ex);
    }

    /**
     * 环绕通知:综合以上所有的通知
     */
    public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println("AOP日志记录:环绕通知之前置......");
        try {
            //手动执行目标方法
            //proceed()就是在执行目标方法,其返回值为目标方法的返回值
            Object resultValue = proceedingJoinPoint.proceed();
            System.out.println("AOP日志记录:环绕通知之返回......");
        } catch (Throwable throwable) {
            System.out.println("AOP日志记录:环绕通知之异常......");
            throwable.printStackTrace();
        }
        System.out.println("AOP日志记录:环绕通知之后置......");
    }
}

5.4 配置AOP

  • 将目标类和切面类都配置到IOC容器中
  • 配置AOP:配置切点表达式,配置切面类,配置通知

<beans
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:util="http://www.springframework.org/schema/util"
  xmlns:p="http://www.springframework.org/schema/p"
  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/util
  http://www.springframework.org/schema/util/spring-util.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="userDao" class="com.newcapec.dao.UserDaoImpl"/>
  <bean id="personDao" class="com.newcapec.dao.PersonDaoImpl"/>
  <bean id="log" class="com.newcapec.aspect.LogAspect"/>

  
  <aop:config>
    
    <aop:pointcut id="exp1" expression="execution(* com.newcapec.dao.*Impl.*(..))"/>
    <aop:pointcut id="exp2" expression="execution(public void com.newcapec.dao.UserDaoImpl.insertUser())"/>
    <aop:pointcut id="exp3" expression="execution(* com.newcapec.dao.UserDaoImpl.findUser(..))"/>
    <aop:pointcut id="exp4" expression="execution(* com.newcapec.dao.PersonDaoImpl.insertPerson(..))"/>

    
    <aop:aspect ref="log">
      
      
      
      
      <aop:before method="beforeMethod" pointcut-ref="exp1"/>
      <aop:after method="afterMethod" pointcut-ref="exp2"/>
      <aop:after-returning method="afterReturnMethod" pointcut-ref="exp3" returning="resultValue"/>
      <aop:after-throwing method="afterThrowMethod" pointcut-ref="exp4" throwing="ex"/>
      
    aop:aspect>
  aop:config>
beans>

5.5 测试

public class AOPTest {

    @Test
    public void test(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserDao userDao = ac.getBean("userDao", UserDao.class);

        userDao.insertUser();
        System.out.println("------------------");

        userDao.updateUser();
        System.out.println("------------------");

        userDao.deleteUser(1);
        System.out.println("------------------");

        userDao.selectUser();
        System.out.println("------------------");

        PersonDao personDao = ac.getBean("personDao", PersonDao.class);
        personDao.insertPerson();

    }
}

注意:在目标方法出现异常后,返回通知不再执行。但是在目标方法没有出现异常时,异常通知不会执行

5.6 切面的优先级

标签中配置order属性,属性值为数字。数字越小优先级越高,该切面功能越先被执行

/**
 * 另一个切面类
 */
public class OtherAspect {

    public void beforeM(){
        System.out.println("OtherAspect的beforeM方法.....");
    }
}
<bean id="other" class="com.newcapec.aspect.OtherAspect"/>

<aop:config>
    
    <aop:aspect ref="log" order="2">
    aop:aspect>
    <aop:aspect ref="other" order="1">
        <aop:before method="beforeM" pointcut-ref="exp1"/>
    aop:aspect>
aop:config>

你可能感兴趣的:(spring,spring,aop,java)