本篇将介绍如下内容:

1、AOP的应用场景

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

        3、 AOP 需求分析

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

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

一、AOP 的场景

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

二、简单举例

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

spring data 底层 spring底层实现_spring

注意:这里面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、代码结构如下图所示

spring data 底层 spring底层实现_Java_02

三、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"));	
		
	}
	
	
}


测试结果如下:

spring data 底层 spring底层实现_spring_03

日志打印情况:

[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,执行结果如下:

spring data 底层 spring底层实现_java_04


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

spring data 底层 spring底层实现_spring_05