Spring动态代理与AOP实现

Spring动态代理与AOP实现

    • 实验1
    • 实验2
    • 实验3

实验要求:
理解动态代理原理,掌握AOP编程的方法。

实验内容:
实验1:利用CGLIB实现动态代理,编写类MyAdvice,在该类中提供log()方法,控制台输出“正在进行日志编写”,被代理类UserDao ,在该类中提供add()方法,控制台输出“正在添加数据库记录”,编写代理类MyProxy,当访问代理类的add方法时,先访问log方法,再访问add方法,实现日志记录。
实验2:利用声明式(xml)和注解式两种方式分别实现Aspect 的AOP。利用实验1中的MyAdvice 类(作为切面),在校园卡充值程序中当访问Biz层的充值方法时(切点),织入log()方法,完成日志记录。Log方法要求记录访问时间、访问者的IP地址、访问方法的名称到数据库中。(LogDao)
实验3:根据自己的理解,解释Spring的AOP。

实验分析:

  • 实验1主要利用CGLIB实现动态代理,可以编写四个简单类进行实现。分别是切面类MyAdvice、被代理类UserDao、代理类MyProxy和测试类Test1。
  • 实验2需要利用声明式(xml)和注解式两种方式分别实现Aspect的AOP,并且需要用到之前的学生卡充值,可以参考利用Spring框架为自己的校园卡充值。此实验还需要连接数据库,可以采用Spring的数据库连接,也可以使用Java简易的JDBC,本次实验作者采用的就是JDBC连接,需要在数据库中建立两个表,分别为记录和充值功能。

实验操作所用工具(软件):
IntelliJ IDEA 2021.2.1

实验2需要建立三层结构,分别是:Dao层、Biz层、Aop层。

以此建立三层结构。即在工程文件下的src文件夹的main中的Java建立三个包(package)。在resource下编写配置文件。
在工程文件下的src文件夹的test中的Java建立test类。

代码实现:

实验1

MyAdvice类:

package com.cqust.aop;
//切面类:存在多个通知Advice(增强的方法)
public class MyAdvice {
    public void log(){
        System.out.println("正在进行日志编写");
    }
}

UserDao类:

package com.cqust.aop;
//目标类
public class UserDao {
    public void addUser(){
        System.out.println("正在添加数据库记录");
    }
}

MyProxy类:

package com.cqust.aop;

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 MyProxy implements MethodInterceptor {
    //代理方法
    public Object createProxy(Object target){
        //创建一个动态类对象
        Enhancer enhancer = new Enhancer();
        //确定需要增强的类,设置其父类
        enhancer.setSuperclass(target.getClass());
        //添加回调函数
        enhancer.setCallback(this);
        //返回创建的代理类
        return enhancer.create();
    }
    /**
     * proxy CGlib根据指定父类生成的代理对象
     * method 拦截的方法
     * args 拦截方法的参数数组
     * methodProxy 方法的代理对象,用于执行父类的方法
     */
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
       //创建切面类对象
        MyAdvice myAdvice = new MyAdvice();
        //前增强
        myAdvice.log();
        //目标方法执行
        Object obj = methodProxy.invokeSuper(proxy,args);
        //后执行
        myAdvice.log();
        return obj;
    }
}

Test1(测试类):

import com.cqust.aop.MyProxy;
import com.cqust.aop.UserDao;

public class Test1 {
    public static void main(String[] args){
        MyProxy myProxy = new MyProxy();
        UserDao userDao = new UserDao();
        UserDao userDao1 = (UserDao) myProxy.createProxy(userDao);
        userDao1.addUser();

    }
}

实验2

CardDao类:

package com.cqust.dao;

public interface CardDao {
    public int save();
}

CardDaoImpl类:

package com.cqust.dao;

import org.springframework.stereotype.Repository;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

@Repository
public class CardDaoImpl implements CardDao {
    @Override
    public int save() {
        int count = 0;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "1");
            Statement statement= connection.createStatement();
            String sql="insert into reCharge(cardId,money) values ('Hades','888.88')";
            count = statement.executeUpdate(sql);
            statement.close();
            connection.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return count;
    }
}

LogDao类:

package com.cqust.dao;

public interface LogDao {
    public int addLog(String name);
}

LogDaoImpl类:

package com.cqust.dao;

import org.springframework.stereotype.Repository;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.Statement;
import java.text.SimpleDateFormat;

@Repository
public class LogDaoImpl implements LogDao {

    @Override
    public int addLog(String name) {
        int count=0;
        try {
//date
            SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
            Date date = new Date(System.currentTimeMillis());
//ip4
            InetAddress ip4 = Inet4Address.getLocalHost();

            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "1");
            Statement statement= connection.createStatement();
            String sql="insert into addressing(date,ip_address,method_name) values ('"+date+"','"+ip4.getHostAddress()+"','"+name+"')";
            count = statement.executeUpdate(sql);
            statement.close();
            connection.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return count;
    }
}

CardBiz类:

package com.cqust.biz;


public interface CardBiz {
    public boolean reCharge();
}

CradBizImpl类:

package com.cqust.biz;

import com.cqust.dao.CardDao;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class CardBizImpl implements CardBiz {
    @Resource(name = "cardDaoImpl")
    private CardDao cardDao;

    @Override
    public boolean reCharge() {
        if(cardDao.save()>0){
            System.out.println("充值成功");
            return true;
        }
        System.out.println("充值失败");
        return false;
    }
}

Test2(测试类):

import com.cqust.biz.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test2 {
    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        CardBiz cardBiz = (CardBiz) context.getBean("cardBizImpl");
        cardBiz.reCharge();
    }
}

XML方式:
MyAdvice类:

package com.cqust.aop;

import com.cqust.dao.LogDaoImpl;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

//切面类:存在多个通知Advice(增强的方法)
@Component
public class MyAdvice {
    public void pointCut(){}
    @Resource(name="logDaoImpl")
    private LogDaoImpl logDaoImpl;
    
    public void log(){
        System.out.println("正在记录操作信息...");
        logDaoImpl.addLog("reCharge");
    }
}

applicationContext.xml(配置文件):


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="userDao" class="com.cqust.dao.CardDaoImpl"/>
    <bean id="logDao" class="com.cqust.dao.LogDaoImpl"/>
    <bean id="cardBiz" class="com.cqust.biz.CardBizImpl"/>
    <bean id="myAdvice" class="com.cqust.aop.MyAdvice"/>
	
	<context:component-scan base-package="com.cqust"/>
     
    <aop:config>
        
        <aop:pointcut id="pointCut" expression="execution(*
            com.cqust.aop.MyAdvice.*(..))"/>
        
        <aop:aspect ref = "myAdvice">
            
            <aop:before method="log" pointcut-ref="pointCut"/>
        aop:aspect>
    aop:config>
beans>

注解方式:
MyAdvice类:

package com.cqust.aop;

import com.cqust.dao.LogDaoImpl;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

//切面类:存在多个通知Advice(增强的方法)
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.cqust.biz.CardBizImpl.reCharge(..))")
    public void pointCut(){}
    @Resource(name="logDaoImpl")
    private LogDaoImpl logDaoImpl;

    @Before("pointCut()")
    public void log(){
        System.out.println("正在记录操作信息...");
        logDaoImpl.addLog("reCharge");
    }
}

applicationContext.xml(配置文件):


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.cqust"/>
    <aop:aspectj-autoproxy/>
beans>

实验3

  Spring用代理类包裹切面,把他们织入到Spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先变成调用伪装类,伪装类中就先执行了切面,再把调用转发给真正的目标bean。

  现在可以自己想一想,怎么搞出来这个伪装类,才不会被调用者发现(通过JVM的检查,JAVA是强类型检查,哪里都要检查类型)。

 1.实现和目标类相同的接口,我也实现和你一样的接口,反正上层都是接口级别的调用,这样我就伪装成了和目标类一样的类(实现了同一接口,咱是兄弟了),也就逃过了类型检查,到java运行期的时候,利用多态的后期绑定(所以spring采用运行时),伪装类(代理类)就变成了接口的真正实现,而他里面包裹了真实的那个目标类,最后实现具体功能的还是目标类,只不过伪装类在之前干了点事情(写日志,安全检查,事物等)。

  这就好比,一个人让你办件事,每次这个时候,你弟弟就会先出来,当然他分不出来了,以为是你,你这个弟弟虽然办不了这事,但是他知道你能办,所以就答应下来了,并且收了点礼物(写日志),收完礼物了,给把事给人家办了啊,所以你弟弟又找你这个哥哥来了,最后把这是办了的还是你自己。但是你自己并不知道你弟弟已经收礼物了,你只是专心把这件事情做好。

  顺着这个思路想,要是本身这个类就没实现一个接口呢,你怎么伪装我,我就压根没有机会让你搞出这个双胞胎的弟弟,那么就用第2种代理方式,创建一个目标类的子类,生个儿子,让儿子伪装我。

  2.生成子类调用,这次用子类来做为伪装类,当然这样也能逃过JVM的强类型检查,我继承的吗?当然查不出来了,子类重写了目标类的所有方法,当然在这些重写的方法中,不仅实现了目标类的功能,还在这些功能之前,实现了一些其他的(写日志,安全检查,事物等)。

  这次的对比就是,儿子先从爸爸那把本事都学会了,所有人都找儿子办事情,但是儿子每次办和爸爸同样的事之前,都要收点小礼物(写日志),然后才去办真正的事。当然爸爸是不知道儿子这么干的了。这里就有件事情要说,某些本事是爸爸独有的(final的),儿子学不了,学不了就办不了这件事,办不了这个事情,自然就不能收人家礼了。

  前一种兄弟模式(JDK动态代理),spring会使用JDK的java.lang.reflect.Proxy类,它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。

  后一种父子模式(CGLib动态代理),spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,spring织入通知,并且把对这个子类的调用委托到目标类。

  相比之下,还是兄弟模式好些,他能更好的实现松耦合,尤其在今天都高喊着面向接口编程的情况下,父子模式只是在没有实现接口的时候,也能织入通知,应当做一种例外。

程序运行结果:
实验1:
Spring动态代理与AOP实现_第1张图片

实验2:
Spring动态代理与AOP实现_第2张图片
Spring动态代理与AOP实现_第3张图片

实验总结:

  1. Spring是一个轻型容器,Spring整个系列的最最核心的概念当属IoC、AOP。可见AOP是Spring框架中的核心之一,在应用中具有非常重要的作用,也是Spring其他组件的基础。
  2. AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
  3. AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率,OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。
  4. AOP的源码中用到了两种动态代理来实现拦截切入功能:
    jdk动态代理:
    1)jdk动态代理是由java内部的反射机制来实现的,jdk动态代理的应用前提是目标类基于统一的接口。如果没有该前提,jdk动态代理不能应用。由此可以看出jdk动态代理有一定的局限性。
    2)JDK动态代理机制是委托机制,不需要以来第三方的库,只要要JDK环境就可以进行代理,动态实现接口类,在动态生成的实现类里面委托为hanlder去调用原始实现类方法。CGLib
    必须依赖于CGLib的类库,使用的是继承机制,是被代理类和代理类继承的关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。
    cglib动态代理: cglib动态代理底层则是借助asm来实现的,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。CGLib必须依赖于CGLib的类库,使用的是继承机制,是被代理类和代理类继承的关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。

对于Spring AOP这一概念,我相信每一个学习Spring的人都会有自己的理解。这种概念上的理解没有绝对的标准答案,仁者见仁智者见智。如果有理解不到位或者理解错的地方,欢迎广大猿友指正!
  
谢谢浏览!

你可能感兴趣的:(JavaEE实验,spring,java,intellij-idea)