您的位置:首页 > 编程语言 > Java开发

Spring实践(二)AOP的底层实现机制

2017-02-13 15:59 357 查看
上一篇通过模拟spring的IOC 机制来理解控制反转、依赖注入,本篇同样模拟一下spring的第二大特性AOP(Aspect Oriented Programming)。

本篇将介绍如下内容:

1、AOP的应用场景

2、生成一个简单的工程案例

        3、 AOP 需求分析

        4、用JDK的动态反射来描述实现原理

        5、用spring的aop 配置来简化AOP 实现

一、AOP 的场景

我喜欢学习一个技术点的时候,考虑一下这个技术点应用场景,这样对加深学习记忆以及学习效果比较好。一般来说,我们都习惯于垂直的进行程序设计和应用开发,但是有时候,我们用到一些比如公共的日志、安全的时候,需要在业务对象里面,插入这些公共的调用。比如,在一个对象的方法,调用前后,我们希望打印一下日志,或者计算一下这个调用的开始和结束时间。

二、简单举例

先看一下下面这个简单例子,基本上,呈现的是普通的一个基于接口开发的特征:



注意:这里面StudentSimulationDB 是模拟数据持久化,用来对本例做数据支撑的。

具体这些类的源码如下:

1、首先是实体类Student.java 描述的是实体对象Student的属性和基本操作

package com.study.entity;

/*
* this is a simple entity class, descripe Student;
*/
public class Student {

String Name="";
String Sex="";
String Birth="";

public String getName() {
return Name;
}

public void setName(String name) {
Name = name;
}

public String getSex() {
return Sex;
}

public void setSex(String sex) {
Sex = sex;
}

public String getBirth() {
return Birth;
}

public void setBirth(String birth) {
Birth = birth;
}

public String toString(){
return "Name="+this.Name+";Sex="+this.Sex+";Birthday="+this.Birth;

}

}

2、接口StudentDAO.java  是对实体对象DAO操作的接口,为了便于扩展采用面向接口编程的方式,设计为接口
package com.study.dao;

import com.study.entity.Student;

/*
* this interface define entity class student's dao(data access operation) interface
*/
public interface StudentDAO {

//学生操作,新增学生
boolean addStudent(Student student);

//学生操作,删除学生
boolean delStudent(Student student);

//学生操作,修改学生信息
boolean modifyStudent(Student student);

//学生操作,查询学生信息,查询到返回学生对象,否则返回null
Student queryStudent( String StudentName);

}

3、接口StudentDAOImpl.java 是接口实现类,接口实现类可以有多个,分别实现不同的业务逻辑,这样才能体现灵活性
package com.study.dao.impl;

import com.study.dao.StudentDAO;
import com.study.entity.Student;

/*
* this is implement of StudentDAO;
*/
public class StudentDAOImpl implements StudentDAO {

@Override
public boolean addStudent(Student student) {
// TODO Auto-generated method stub
//add 操作,加入student对象到list中

return StudentSimulationDB.getInstance().add(student);

}

@Override
public boolean delStudent(Student student) {
// TODO Auto-generated method stub
return false;
}

@Override
public boolean modifyStudent(Student student) {
// TODO Auto-generated method stub
return false;
}

@Override
public Student queryStudent(String StudentName) {
// TODO Auto-generated method stub
//模拟从数据库中查询学生名, 数据库中只有一名 name 为 Tom的学生
return StudentSimulationDB.getInstance().querry(StudentName);
}

}

4、StudentSimulationDB 是模拟数据库的类,模拟持久化功能
package com.study.dao.impl;

import java.util.ArrayList;
import java.util.List;

import com.study.entity.Student;

//模拟数据库,目的是对student操作的时候,可以记录操作的内容
public class StudentSimulationDB {

public List<Student > listStudent=new ArrayList<Student>();

private static StudentSimulationDB instance = null;
private StudentSimulationDB(){}

public static StudentSimulationDB getInstance() {// 实例化引用
if (instance == null) {
instance = new StudentSimulationDB();
}
return instance;
}

//模拟数据库中增加一条学生记录
public boolean add(Student student){

//检查是否存在相同的学生,学生姓名最为唯一关键字
for(Student inStudent:listStudent){
if(student.getName().equals(inStudent.getName())){
System.out.println("DB had existed this Student!!!");
return false;
}
}

listStudent.add(student);
return true;

}

//如果在模拟数据库中存在,那么范围student的信息,否则返回null
public Student querry(String studentName){

for(Student inStudent:listStudent){
if(studentName.equals(inStudent.getName())){
return inStudent;
}
}
return null;

}

}


5、 Student 应用类StudentService,在这个类里面,用的是StudentDAO, 而在具体运行时,可以指定实现类,这样就能体现出面向接口编程的灵活性、扩展性
package com.study.student.service;

import com.study.dao.StudentDAO;
import com.study.entity.Student;

/*
* this class descripe StudentApi
*/
public class StudentService {

//private StudentDAO studentDAO = new StudentDAOImpl();
private StudentDAO studentDAO ;

public StudentDAO getStudentDAO() {
return studentDAO;
}

public void setStudentDAO(StudentDAO studentDAO) {
this.studentDAO = studentDAO;
}

public boolean addStudent(Student student) {

return this.studentDAO.addStudent(student);

}

public String queryStudent(String studentName) {

Student retStudent = this.studentDAO.queryStudent(studentName);
if (null == retStudent)
return "null";
else
return retStudent.toString();

}

}

6、代码结构如下图所示



三、AOP需求分析

上面例子中,如果对于复杂调用,经常会要求在调用add和querry的开始和结束,需要打印日志和开始时间、结束时间。 我们往往有如下做法:

1、 修改StudentDAOImpl,在函数开始和结束前添加需求。

这样操作的问题在于: 如果这样的类和方法很多,每个都要这么添加,是非常烦的一件事,而且没有多大意思。而且如果再变化一下需求,比如时间格式要求变化,还需要一个个修改过去。

2、不允许修改StudentDAOImpl的情况下,我们通过继承StudentDAOImpl类,重写相应的方法:

@Override

XXXXXX 方法(){

        XXXXXX ;    // 新增逻辑

        super( xxxx);

        XXXXXX ;    // 新增逻辑

        }

同样这也无法解决1中,关于需求变化的需求。

3、组合的方式,我们将StudentDAO 与 StudentDAOImpl 组合到我们的类里面:

package com.study.dao.proxy;

import org.apache.log4j.Logger;

import com.study.dao.StudentDAO;
import com.study.dao.impl.StudentDAOImpl;
import com.study.entity.Student;

public class StudentProxy implements StudentDAO {
private StudentDAO studentdao= new StudentDAOImpl();
static Logger logger = Logger.getLogger(StudentProxy.class);

@Override
public boolean addStudent(Student student) {
// TODO Auto-generated method stub
// add 操作,加入student对象到list中

boolean ret = false;
logger.info("addStudent method start, student info=>"+ student.toString());
ret = studentdao.addStudent(student);
logger.info("addStudent method end.");

return ret;
}

@Override
public boolean delStudent(Student student) {
// TODO Auto-generated method stub
return false;
}

@Override
public boolean modifyStudent(Student student) {
// TODO Auto-generated method stub
return false;
}

@Override
public Student queryStudent(String StudentName) {
// TODO Auto-generated method stub
// 模拟从数据库中查询学生名, 数据库中只有一名 name 为 Tom的学生
logger.info("queryStudent method start, querry prarm=>"+StudentName);
Student student;
student = studentdao.queryStudent(StudentName);
logger.info("queryStudent method end.");
return student;
}

}

关于log4j包的引入和log4j.properties 文件的配置,这里就略去了。 我们通过编写junit 测试类,来测试这个proxy,看看junit 里面test 结果
StudentServiceTest 代码如下: (用的是junit4)

package com.study.student.service;

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;

import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;

import com.study.dao.StudentDAO;
import com.study.dao.proxy.StudentProxy;
import com.study.entity.Student;

public class StudentServiceTest {

public static StudentService studentService;
public static StudentDAO studentDAO;

//注意这里用beforeClass而不是before,表示全部测试函数调用前,调用一次
@BeforeClass
public static void init() throws Exception{

studentService= new StudentService();
studentService.setStudentDAO( new StudentProxy());

}

@Test
public void addStudentTest() throws Exception{

Student studentObj = new Student();
studentObj.setName("Tom");
studentObj.setSex("Male");
studentObj.setBirth("19740508");
assertTrue(studentService.addStudent(studentObj));

Student studentObj2 = new Student();
studentObj2.setName("Jerry");
studentObj2.setSex("Female");
studentObj2.setBirth("19780615");
assertTrue(studentService.addStudent(studentObj2));

}

@After
public void queryStudentTest( ) throws Exception {

assertThat(studentService.queryStudent("Tom"), is("Name=Tom;Sex=Male;Birthday=19740508"));

assertThat(studentService.queryStudent("Jack"), is("null"));

}

}

测试结果如下:



日志打印情况:

[2017-02-13 11:20:00]       INFO [main] (StudentProxy.java::19) - addStudent method start, student info=>Name=Tom;Sex=Male;Birthday=19740508

[2017-02-13 11:20:00]       INFO [main] (StudentProxy.java::21) - addStudent method end.

[2017-02-13 11:20:00]       INFO [main] (StudentProxy.java::19) - addStudent method start, student info=>Name=Jerry;Sex=Female;Birthday=19780615

[2017-02-13 11:20:00]       INFO [main] (StudentProxy.java::21) - addStudent method end.

[2017-02-13 11:20:00]       INFO [main] (StudentProxy.java::42) - queryStudent method start, querry prarm=>Tom

[2017-02-13 11:20:00]       INFO [main] (StudentProxy.java::45) - queryStudent method end.

[2017-02-13 11:20:00]       INFO [main] (StudentProxy.java::42) - queryStudent method start, querry prarm=>Jack

[2017-02-13 11:20:00]       INFO [main] (StudentProxy.java::45) - queryStudent method end.

同样这种实现,也无法解决有很多类时,需要进行组装和修改的问题。

四、AOP需求的jdk reflect 动态实现

在JDK 1.4版本后,提供了调用一个方法的时候,动态添加处理逻辑的功能,下面看一下实现:

DynaStudentProxy.java 是动态反射类,里面的invoke 方法,实现了需要添加在 StudentDAOImpl 对象中方法调用时的处理逻辑,本例是打印日志

package com.study.dao.proxy;

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

import org.apache.log4j.Logger;

public class DynaStudentProxy implements InvocationHandler {

static Logger logger = Logger.getLogger(DynaStudentProxy.class);

/**
* 目标对象,如StudentDAOImpl对象
*/
private Object target;

/**
* 动态生成方法被处理过后的对象 (写法固定)
*
*/
public Object bind(Object inTarget) {
this.target = inTarget;
return Proxy.newProxyInstance(
this.target.getClass().getClassLoader(), this.target
.getClass().getInterfaces(), this);
}

/**
* 目标对象中的每个方法会被此方法送去JVM调用,也就是说,要目标对象的方法只能通过此方法调用,
* 此方法是动态的,不是手动调用的
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
try {
// 执行原来的方法之前记录日志
logger.info( method.getName() + " Method Start, args=>"+args[0]);

// JVM通过这条语句执行原来的方法(反射机制)
result = method.invoke(this.target, args);

// 执行原来的方法之后记录日志
logger.info(method.getName() + " Method end");

} catch (Exception e) {
e.printStackTrace();
}
// 返回方法返回值给调用者
return result;
}
}

然后在应用StudentServiceTest.java 中,动态的绑定StudentDAOImpl,而不是set ,这样的逻辑就变成,先调用proxy中的业务逻辑在执行具体impl方法中的内容。
package com.study.student.service;

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;

import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;

import com.study.dao.StudentDAO;
import com.study.dao.impl.StudentDAOImpl;
import com.study.dao.proxy.DynaStudentProxy;
import com.study.entity.Student;

public class StudentServiceTest {

public static StudentService studentService;
public static StudentDAO studentDAO;

//注意这里用beforeClass而不是before,表示全部测试函数调用前,调用一次
@BeforeClass
public static void init() throws Exception{

studentService= new StudentService();
StudentDAOImpl aImpl=new StudentDAOImpl();
DynaStudentProxy mProxy = new DynaStudentProxy();

//用动态绑定的方法替换set方法,使得调用对象的业务逻辑发生变化
//这里指在调用impl的add 和querry 之前,打印日志
//studentService.setStudentDAO(aImpl);
studentService.studentDAO = (StudentDAO) mProxy.bind(aImpl);


}

@Test
public void addStudentTest() throws Exception{

Student studentObj = new Student();
studentObj.setName("Tom");
studentObj.setSex("Male");
studentObj.setBirth("19740508");
assertTrue(studentService.addStudent(studentObj));

Student studentObj2 = new Student();
studentObj2.setName("Jerry");
studentObj2.setSex("Female");
studentObj2.setBirth("19780615");
assertTrue(studentService.addStudent(studentObj2));

}

@After
public void queryStudentTest( ) throws Exception {

assertThat(studentService.queryStudent("Tom"), is("Name=Tom;Sex=Male;Birthday=19740508"));

assertThat(studentService.queryStudent("Jack"), is("null"));

}

}


五、spring aop 实现

回想一下上一篇的IOC,spring用配置的方法就解决了IOC的功能,AOP同样,spring也是通过配置文件就可以解决,上面第四点可以说是spring aop的底层原理。

1、首先引入spring,并且新增配置文件applicationContext_aop.xml,同时,为了体现spring aop 可以进行配置多个切面逻辑,编写了2个Handler,分别是 LogHandler和TimeRecHandler即日志和时间记录处理。

2、下面先看一下这两个切面处理逻辑类:

 日志记录类:

package com.study.dao.handler;

import org.apache.log4j.Logger;

public class LogHandler {
static Logger logger = Logger.getLogger(LogHandler.class);

public void LogBefore() {
logger.info("Method invoke start...");
}

public void LogAfter() {
logger.info("Method invoke end!");
logger.info("");
}

}


   时间记录类:

package com.study.dao.handler;

import org.apache.log4j.Logger;

public class TimeRecHandler {
static Logger logger = Logger.getLogger(LogHandler.class);

public void printTime() {
logger.info("CurrentTime = " + System.currentTimeMillis());
}

}

3、配置aop的xml
在applicationContext_aop.xml里面,我们进行如下配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="studentDAOImpl" class="com.study.dao.impl.StudentDAOImpl" />
<bean id="timeRecHandler" class="com.study.dao.handler.TimeRecHandler" />
<bean id="logHandler" class="com.study.dao.handler.LogHandler" />
<bean id="StudentService" class ="com.study.student.service.StudentService" >
<property name="StudentDAO" ref="studentDAOImpl"/>
</bean>

<aop:config>
<aop:aspect id="log" ref="logHandler" order="1">
<aop:pointcut id="printLog" expression="execution(* com.study.dao.impl.StudentDAOImpl.*(..))" />
<aop:before method="LogBefore" pointcut-ref="printLog" />
<aop:after method="LogAfter" pointcut-ref="printLog" />
</aop:aspect>
<aop:aspect id="time" ref="timeRecHandler" order="2">
<aop:pointcut id="addTime" expression="execution(* com.study.dao.impl.StudentDAOImpl.*(..))" />
<aop:before method="printTime" pointcut-ref="addTime" />
<aop:after method="printTime" pointcut-ref="addTime" />
</aop:aspect>

</aop:config>
</beans>
4、拷贝StudentServiceTest =》 StudentServiceTest2 
只修改里面的init 方法

//注意这里用beforeClass而不是before,表示全部测试函数调用前,调用一次
@BeforeClass
public static void init() throws Exception{

BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext_aop.xml");
studentService= (StudentService) factory.getBean("StudentService");


}

执行StudentServiceTest2,执行结果如下:



最后附上整个工程的结构以及依赖lib

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: