实验内容:
实验1:利用CGLIB实现动态代理,编写类MyAdvice,在该类中提供log()方法,控制台输出“正在进行日志编写”,被代理类UserDao ,在该类中提供add()方法,控制台输出“正在添加数据库记录”,编写代理类MyProxy,当访问代理类的add方法时,先访问log方法,再访问add方法,实现日志记录。
实验2:利用声明式(xml)和注解式两种方式分别实现Aspect 的AOP。利用实验1中的MyAdvice 类(作为切面),在校园卡充值程序中当访问Biz层的充值方法时(切点),织入log()方法,完成日志记录。Log方法要求记录访问时间、访问者的IP地址、访问方法的名称到数据库中。(LogDao)
实验3:根据自己的理解,解释Spring的AOP。
实验分析:
实验操作所用工具(软件):
IntelliJ IDEA 2021.2.1
实验2需要建立三层结构,分别是:Dao层、Biz层、Aop层。
以此建立三层结构。即在工程文件下的src文件夹的main中的Java建立三个包(package)。在resource下编写配置文件。
在工程文件下的src文件夹的test中的Java建立test类。
代码实现:
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();
}
}
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>
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织入通知,并且把对这个子类的调用委托到目标类。
相比之下,还是兄弟模式好些,他能更好的实现松耦合,尤其在今天都高喊着面向接口编程的情况下,父子模式只是在没有实现接口的时候,也能织入通知,应当做一种例外。
实验总结:
对于Spring AOP这一概念,我相信每一个学习Spring的人都会有自己的理解。这种概念上的理解没有绝对的标准答案,仁者见仁智者见智。如果有理解不到位或者理解错的地方,欢迎广大猿友指正!
谢谢浏览!