切面编程

最近在学习 Spring 框架,写这篇文章也算是对近段学习的一个总结,本文主要从三种代理模式写起,静态代理、动态代理和 Cglib 代理,然后到Spring AOP 的配置及使用,包括 XML 方式配置和注解两种实现方式

一、代理模式

代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象的功能扩展

功能需求: 现在假设有个 UserDao 接口,有保存动作 save() 方法,UserDaoImpl 实现 UserDao接口 , 但是在执行 save() 方法的时候动态植入两句打印

1. 静态代理

静态代理方式处理的,目标对象必须实现接口,并且代理对象要实现跟目标对象一样的接口

public interface UserDao {
	public void save();
}
public class UserDaoImpl implements UserDao {

	@Override
	public void save() {
		System.out.println("执行保存动作");
	}
}
/**
 * 静态代理类
 * @author liuchao
 */
public class UserProxy implements UserDao {

	private UserDao _userDao;

	public UserProxy(UserDao userDao) {
		_userDao = userDao;
	}

	@Override
	public void save() {
		//静态代理方式实现开始植入
		System.out.println("开启事务");

		_userDao.save();

		//静态代理方式实现结束植入
		System.out.println("提交事务");
	}
}
import org.junit.Test;
/**
 * 测试类
 * @author liuchao
 */
public class UserTest {

	@Test
	public void testSave() {

		//1. 创建目标对象
		UserDao userDao = new UserDaoImpl();

		//2. 创建代理工厂对象
		UserProxy factory = new UserProxy(userDao);

		//3. 执行代理工厂保存方法
		factory.save();
	}
}
//控制台打印结果
开启事务
执行保存动作
提交事务

2. 动态代理

动态代理是 JDK 自带的,所以也叫做 JDK 代理,也需要目标对象实现接口,也是只能对目标对象实现接口里面的方法进行拦截。

调用 Proxy 类的静态方法 newProxyInstance 即可,该方法会返回代理类对象

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h ) 接收的三个参数依次为:

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

/**
 * 动态代理工厂类
 * @author liuchao
 */
public class ProxyFactory {

	// 生成动态代理对象
	public static Object newProxyInstance(Object target) {
		// InvocationHandler
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
				new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

						System.out.println("开启事务");

						Object result = method.invoke(target, args);

						System.out.println("提交事务");

						return result;
					}
				});
	}
}
import org.junit.Test;
/**
 * 测试类
 * @author liuchao
 */
public class UserTest {

	@Test
	public void testSave() {

		UserDao userDao = new UserDaoImpl();

		//生成代理对象 (动态创建一个实现该接口的匿名内部类)
		UserDao proxyInstance = (UserDao)ProxyFactory.newProxyInstance(userDao);

		//执行保存方法
		proxyInstance.save();
	}
}
//控制台打印
开启事务
执行保存动作
提交事务

3. Cglib 代理

第三方框架实现的对基本代理模式的一个拓展,该代理植入不需要目标对象实现接口,内部通过动态生成目标对象子类的方式来实现对目标对象方法拦截

前提条件:

所以接下来重新定义的 UserDaoImpl 类不必实现任何接口

public class UserDaoImpl {

	public void save() {
		System.out.println("执行保存动作");
	}
}

代理工厂类必须实现 MethodInterceptor 接口,因为方法拦截是通过实现该接口里面的 intercept (..) 方法。

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
 * Cglib代理工厂类
 * @author liuchao
 */
public class ProxyFactory implements MethodInterceptor {

	private Object target;

	public ProxyFactory(Object target) {
		this.target = target;
	}

	//实例化代理对象
	public Object getProxyInstance() {
		//1. 工具类
		Enhancer en = new Enhancer();
		//2. 设置父类
		en.setSuperclass(this.target.getClass());
		//3. 设置回调,会调用下面的intercept(..)方法
		en.setCallback(this);
		//4. 创建子类对象
		return en.create();
	}

	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

		System.out.println("开启事务");

		Object result = method.invoke(this.target, args);

		System.out.println("提交事务");

		return result;
	}
}
import org.junit.Test;
/**
 * 测试类
 * @author liuchao
 */
public class UserTest {

	@Test
	public void testSave() {
		//1. 创建目标对象
		UserDaoImpl userDao = new UserDaoImpl();

		//2. 生成代理对象
		UserDaoImpl proxyInstance = (UserDaoImpl) new ProxyFactory(userDao).getProxyInstance();

		//3. 执行保存方法
		proxyInstance.save();

		System.out.println("代理对象类名: " +  proxyInstance.getClass().getSimpleName());
		System.out.println("代理对象父类名: " + proxyInstance.getClass().getSuperclass().getSimpleName());
	}
}
//控制台打印
开启事务
执行保存动作
提交事务
代理对象类名: UserDaoImpl$$EnhancerByCGLIB$$5467e6b5
代理对象父类名: UserDaoImpl

二、Spring AOP 编程

需要导入的 jar 包

//Spring核心包
	spring-core-4.0.6.RELEASE.jar
	spring-expression-4.0.6.RELEASE.jar
	spring-beans-4.0.6.RELEASE.jar
	commons-logging-1.1.3.jar

//后期通过注解方式实现需要
	spring-context-4.0.6.RELEASE.jar

//Aop部分架包
	spring-aop-4.0.6.RELEASE.jar
	aspectjweaver-1.8.7.jar //下载地址: http://central.maven.org/maven2/org/aspectj/aspectjweaver/1.8.7/
	aopalliance-1.0.jar //下载地址: http://central.maven.org/maven2/aopalliance/aopalliance/1.0/

基本概念

1. XML 方式实现

为了代码清晰省略了 UserDao 和 UserDaoImpl,直接在 UserService 层进行一个简单的数据保存模拟

import org.aspectj.lang.JoinPoint;

/**
 * 自定义一个切面类
 * @author liuchao
 */
public class Aspect {

	public void beforeAction(JoinPoint joinPoint) {
		System.out.println("开启事务");

		System.out.println("开启事务, 连接点 方法名: " + joinPoint.getSignature().getName());

		Object[] args = joinPoint.getArgs();
		if (args != null) {
			for (Object object : args) {
				System.out.println("开启事务, 方法参数: " + object.toString());
			}
		}
	}

	public void afterAction(JoinPoint joinPoint) {
		System.out.println("提交事务");

		System.out.println("提交事务, 连接点 方法名: " + joinPoint.getSignature().getName());

		Object[] args = joinPoint.getArgs();
		if (args != null) {
			for (Object object : args) {
				System.out.println("提交事务, 方法参数: " + object.toString());
			}
		}
	}
}
/**
 * 业务层
 * @author liuchao
 */
public class UserService {

	public void save() {
		System.out.println("持久层对象调用保存方法进行数据保存");
	}
}
/**
 * 视图控制器层
 * @author liuchao
 */
public class UserAction {
	//Spring IOC注入, 通过Set方式注入
	private UserService userService;
	public void setUserService(UserService userService) {
		this.userService = userService;
	}

	public void saveAction() {
		userService.save();
	}
}

Spring XML 文件配置

<?xml version="1.0" encoding="UTF-8"?>
<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/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="userService" class="com.e_aop_xml.UserService"></bean>

        <bean id="userAction" class="com.e_aop_xml.UserAction">
        	<property name="userService" ref="userService"></property>
        </bean>

        <!-- 切面类 -->
        <bean id="aspect" class="com.e_aop_xml.Aspect"></bean>

        <!-- Aop配置 -->
        <aop:config>
        	<!-- 切点定义: 定义一个切入点表达式, 指定那些方法需要进行切面编程 -->
        	<aop:pointcut expression="execution(* com.e_aop_xml.UserService.*(..))" id="pt"/>
        	<!-- 切面 -->
        	<aop:aspect ref="aspect">
        		<!-- 目标方法前置调用 -->
        		<aop:before method="beforeAction" pointcut-ref="pt"/>
        		<aop:after method="afterAction" pointcut-ref="pt"/>
        	</aop:aspect>
        </aop:config>

</beans>
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * 测试类
 * @author liuchao
 */
public class UserTest {

	@Test
	public void testSaveAction() {
		@SuppressWarnings("resource")
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/e_aop_xml/bean.xml");
		UserAction userAction = (UserAction) context.getBean("userAction");
		userAction.saveAction();
	}
}
//控制台输出
开启事务
开启事务, 连接点 方法名: save
持久层对象调用保存方法进行数据保存
提交事务
提交事务, 连接点 方法名: save

2. 注解方式实现

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 自定义一个切面类
 * @author liuchao
 */
@Component
@org.aspectj.lang.annotation.Aspect
public class Aspect {

	@Before("execution(* com.f_aop_anno.UserService.*(..))")
	public void beforeAction(JoinPoint joinPoint) {
		System.out.println("开启事务, 连接点 方法名: " + joinPoint.getSignature().getName());
	}

	//自定义一个切点, 然后引用
	@Pointcut("execution(* com.f_aop_anno.UserService.*(..))")
	public void pointCut() {}

	@After("pointCut()")
	public void afterAction(JoinPoint joinPoint) {
		System.out.println("提交事务, 连接点 方法名: " + joinPoint.getSignature().getName());
	}
}
import org.springframework.stereotype.Service;
/**
 * 业务层
 * @author liuchao
 */
@Service
public class UserService {

	public void save() {
		System.out.println("持久层对象调用保存方法进行数据保存");
	}
}
import javax.annotation.Resource;

import org.springframework.stereotype.Controller;

/**
 * 视图控制器层
 * @author liuchao
 */
@Controller(value="userAction")
public class UserAction {

	@Resource
	private UserService userService;

	public void saveAction() {
		userService.save();
	}
}

Spring XML 文件配置

<?xml version="1.0" encoding="UTF-8"?>
<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/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">

        <!-- 开启注解扫描 -->
        <context:component-scan base-package="com.f_aop_anno"></context:component-scan>

        <!-- 启用@AsjectJ支持, 需要配置下面一句: -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * 测试类
 * @author liuchao
 */
public class UserTest {

	@Test
	public void testSaveAction() {
		@SuppressWarnings("resource")
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/f_aop_anno/bean.xml");
		UserAction userAction = (UserAction) context.getBean("userAction");
		userAction.saveAction();
	}
}
//控制台打印
开启事务, 连接点 方法名: save
持久层对象调用保存方法进行数据保存
提交事务, 连接点 方法名: save