在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
正如上图所示,UserDao和BookDao都需要开启和提交事务,而它们是必须的(通用的)且又和核心业务不相关,如何提高代码的复用性和开发效率,我们来看下图:
我们将与业务代码不相关(或某功能的代码)从类中剥离出来,把它们定义在另一个类中,在使用是可以利用AOP对需要增强的代码进行织入,这个就是AOP的目的。
AOP的实现是基于代理实现的,代理又有静态代理和动态代理,静态代理不能适应变化,动态代理利用反射机制可以适应变化。
Spring默认采取的动态代理机制实现AOP,当动态代理不可用时(代理类无接口)会使用CGlib机制。
Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。
静态代理:
静态代理根据OCP原则是在代理对象和目标对象实现共同的接口,并且代理对象持有目标对象的引用。
模拟实现:DAO接口:
public interface IUserDao {
void list();
void add();
}
DAO实现类:
@Repository
public class UserDao implements IUserDao {
@Override
public void list() {
System.out.println("user.....list");
}
@Override
public void add() {
System.out.println("user....add");
}
}
增强功能类:
public class LogInfo {
public static void logInfo(String operation){
System.out.println("执行了"+operation+"操作!");
}
}
静态代理类:代理DAO实现并实现增强功能(添加日志功能)
@Component(value = "userProxyDao")
public class UserProxyDao implements IUserDao {
@Resource
private IUserDao userDao;
@Override
public void list() {
LogInfo.logInfo("list");
this.userDao.list();
}
@Override
public void add() {
LogInfo.logInfo("add");
this.userDao.add();
}
}
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/beans.xml")
public class UserDaoTest {
@Resource(name = "userProxyDao")
private IUserDao userDao;
@Test
public void test(){
this.userDao.add();
this.userDao.list();
}
}
输出如下:
以上的静态代理的问题时,当有多个业务类时,每个类都要创建一个代理类,显然这是不切实际的。我们可以通过Java的java.lang.reflect.InvocationHandler接口实现动态代理。
动态代理:通过代理对象来创建业务对象,在代理对象中完成对业务的增强(业务)的处理。
基于JDK的动态代理类:
package edu.song.aop.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 动态代理类
*/
public class ProxyLog implements InvocationHandler {
private ProxyLog(){}
private Object target;
/**
* 生成代理对象的方法
* @param object(要代理的对象)
* @return (目标对象---》代理对象)
*/
public static Object getInstance(Object object){
ProxyLog proxyLog= new ProxyLog();
proxyLog.target=object;
/**
* 创建代理实例:
* 参数1:代理对象的类加载器
* 参数2:代理对象的接口
* 参数3:实现InvocationHandler的对象
* 返回的就是代理对象
*/
Object result= Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),proxyLog);
return result;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//要增加(强)的功能代码
LogInfo.logInfo(method.getName()+"!");
//回调
Object invoke = method.invoke(target, args);
return invoke;
}
}
DAO:
@Repository
public class UserDao implements IUserDao {
@Override
public void list() {
System.out.println("user...dao..list");
}
@Override
public void add() {
System.out.println("user..dao..add");
}
}
Service:
@Service
public class UserService implements IUserService{
@Resource//(name = "proxyLogDao")
private IUserDao userDao;
@Override
public void list() {
System.out.println("user service....list");
this.userDao.list();
}
@Override
public void add() {
System.out.println("user service.....add");
this.userDao.add();
}
}
注意以上业务类代码的注释部分
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:annotation-config/>
<context:component-scan base-package="edu.song.aop"/>
<bean id="proxyLogDao" class="edu.song.aop.util.ProxyLog" factory-method="getInstance">
<constructor-arg ref="userDao"/>
</bean>
</beans>
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/beans.xml")
public class UserDaoTest {
@Resource
private IUserService userService;
@Test
public void test(){
userService.add();
userService.list();
}
}
输出如下:
以上输出并没有使用代理类,我们将业务代码修改如下:
@Service
public class UserService implements IUserService{
@Resource(name = "proxyLogDao")
private IUserDao userDao;
我们还可以在代理中设置条件等来完成特有功能,修改代理类如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("list")) {
//只有list方法要增加(强)的功能代码
LogInfo.logInfo(method.getName() + "!");
}
//回调
Object invoke = method.invoke(target, args);
return invoke;
}
spring框架的AOP实现提供了两种方式:分别是xml的配置和annotation的配置。接下来我们自定义一个注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogAnnotation {
String value() default "";
}
接口上添加自定义注解:
public interface IUserDao {
@LogAnnotation("#### 列表 #####")
void list();
void add();
}
修改代理类,添加对注解的处理:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.isAnnotationPresent(LogAnnotation.class)){
LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
LogInfo.logInfo(annotation.value());
}
//回调
Object invoke = method.invoke(target, args);
return invoke;
}