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

浅谈Spring AOP

2016-09-22 17:02 253 查看

浅谈Spring AOP

介绍

AOP(Aspect Oriented Programming)即面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.

这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程.面向切面编程(AOP)是从动态角度考虑程序运行过程.


举例:
遍布在整个系统的权限校验、日志输出这两种服务其实和我们开发的功能是相互独立的,不存在交集因此可以将这两种服务当作切面来看待。


引入AOP举例

需求描述:UserManager服务类的所有操作前都要先检查权限


UserManager

/**
*
* 需求描述:<br>
* 1、想在UserManager操作任何方法前都检查下用户权限<br>
* 2、之后可能会增加其它类似需求或删除部分需求<br>
*
* @author xuyi
* @时间: 2016年9月22日 上午9:32:56
* @功能:
* @类描述:
* @春风十里不如你
*/
public interface UserManager
{
public void addUser(String username);
public void delUser(int userId);
public void updateUser(int userId, String username);
public String selectUser(int userId);
}


UserManagerImpl

/**
* @author xuyi
* @时间: 2016年9月22日 上午9:32:49
* @功能:
* @类描述:
* @春风十里不如你
*/
public class UserManagerImpl implements UserManager
{

public void addUser(String username)
{
System.out.println("----- addUser -----");
}
public void delUser(int userId)
{
System.out.println("----- delUser -----");
}
public void updateUser(int userId, String username)
{
System.out.println("----- updateUser -----");
}
public String selectUser(int userId)
{
System.out.println("----- selectUser -----");
return "jack";
}
}


解决方案一(最容易想到的方法)

最容易想到的方法也是最简单的方法就是在在每个调用方法前调用权限检查方法


UserManagerImpl Code

/**
* 需求描述:UserManager的所有操作前都要检查权限.<br>
* 最容易想到的方法也是最简单的方法就是在在每个调用方法前调用权限检查方法。<br>
*
* 缺点:<br>
* 1、目标类中出现很多的代码修改(有隐患)并且代码冗余修改维护不易
* 2、本着对修改关闭和对扩展开发的原则,不推荐使用该方式
*
* @author xuyi
* @时间: 2016年9月22日 上午9:32:49
* @功能:
* @类描述:
* @春风十里不如你
*/
public class UserManagerImpl implements UserManager
{

public void addUser(String username)
{
checkSecurity();
System.out.println("----- addUser -----");
}
public void delUser(int userId)
{
checkSecurity();
System.out.println("----- delUser -----");
}
public void updateUser(int userId, String username)
{
checkSecurity();
System.out.println("----- updateUser -----");
}
public String selectUser(int userId)
{
checkSecurity();
System.out.println("----- selectUser -----");
return "jack";
}
// 检查权限操作
private void checkSecurity()
{
System.out.println("---- 权限检查 ----");
}

}


缺点:
1、目标类中出现很多的代码修改(有隐患)并且代码冗余修改维护不易
2、本着对修改关闭和对扩展开发的原则,不推荐使用该方式。


解决方案二(代理模式)

进一步考虑使用静态代理模式,不修改之前的目标代码。


UserManagerImplProxy Code

/**
* 需求描述:UserManager的所有操作前都要检查权限.
* 进一步可以想到的是使用静态代理模式
*
* @author xuyi
* @时间: 2016年9月22日 上午9:44:13
* @功能:
* @类描述:
* @春风十里不如你
*/
public class UserManagerImplProxy implements UserManager
{

// 目标实际对象引用
private UserManager userManager;
public UserManagerImplProxy(UserManager userManager)
{
this.userManager = userManager;
}
public void addUser(String username)
{
checkSecurity();
userManager.addUser(username);
}
public void delUser(int userId)
{
checkSecurity();
userManager.delUser(userId);
}
public void updateUser(int userId, String username)
{
checkSecurity();
userManager.updateUser(userId, username);
}
public String selectUser(int userId)
{
checkSecurity();
return userManager.selectUser(userId);
}
// 检查权限操作
private void checkSecurity()
{
System.out.println("---- 权限检查 ----");
}

}


优点:
1、权限控制放在代理对象中处理
2、不用修改目标代码只扩展了代理对象

缺点:
1、相对静态编译期就知道代理对象,如果有多个服务对象需要这种功能那么回导致出现很多的代理对象。


解决方案三(基于JDK动态代理)

基于JDK的动态代理实现,运行期才注入方法。


SecurityHandler Code

/**

* 需求描述:UserManager的所有操作前都要检查权限.

* 进一步可以想到的是使用JDK提供的动态代理

*

* 通过实现InvocationHandler来定义一个权限的代理对象

*

* @author xuyi

* @时间: 2016年9月22日 上午10:04:27

* @功能:

* @类描述:

* @春风十里不如你

*/

public class SecurityHandler implements InvocationHandler

{

// 目标对象
private Object targetObject;

// 创建代理对象
public Object createProxy(Object targetObject)
{
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),
this);
}

public Object invoke(Object object, Method method, Object[] args) throws Throwable
{
// 检查权限
checkSecurity();
// 执行目标方法
Object result = method.invoke(targetObject, args);
return result;
}

// 检查权限操作
private void checkSecurity()
{
System.out.println("---- 权限检查 ...----");
}
}


优点:
1、代码简洁维护和修改比较简单,符合OCP原则
2、在运行期动态生成代理对象,更加灵活可处理更多的服务对象

缺点:
JDK的动态代理是有要求的,被代理的服务对象必须实现接口,否则无法生存代理对象。(鼓励面向接口编程)


解决方案四(基于Spring AOP编程)

由于直接使用JDK的动态代理编码比较麻烦,Spring已经提供了更加友好的编码方式。


UserService Code

public interface UserService
{
public void addUser(String name);
public void delUser(int userId);
public String selectUser(int userId);
}


UserServiceImpl Code

@Service(value = "userService")
public class UserServiceImpl implements UserService
{
public void addUser(String name)
{
System.out.println("----addUser---");
}
public void delUser(int userId)
{
System.out.println("----delUser---");
}
public String selectUser(int userId)
{
System.out.println("----selectUser---");
return "jack";
}
}


基于注解开发AOP

切面类DBAspect Code

/**
* 横切关注面抽象成一个类
*
* @author xuyi
* @时间: 2016年9月22日 上午11:03:57
* @功能:
* @类描述:
* @春风十里不如你
*/
@Component
@Aspect
public class DBAspect
{

// 前置方法
@Before("executionMethod()")
public void openTransaction()
{
System.out.println("打开事务...");
}
// 后置方法
@After("executionMethod()")
public void closeTransaction()
{
System.out.println("关闭事务...");
}
// Pointcut表达式
@Pointcut("execution(* com.xuyi.learn.learnspring.aop.aopannotation.UserService.add*(..))")
private void executionMethod()
{
}

}


MainApp Code

public class MainApp
{
public static void main(String[] args)
{
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"classpath:config/applicationContext.xml");

UserService userService = applicationContext.getBean("userService", UserService.class);

userService.addUser("xuyi");
System.out.println("=========");
userService.delUser(10);

}
}


配置文件说明

<!-- 开启Spring组件自动扫描功能 -->
<context:component-scan base-package="com.xuyi.learn.learnspring" />

<!-- 开启Aspectj对AOP注解的支持 -->
<aop:aspectj-autoproxy />


基于XML配置文件开发AOP

TransactionAspect Code

public class TransactionAspect
{
public void openTransaction()
{
System.out.println("开启事务...");
}
public void closeTransaction()
{
System.out.println("关闭事务...");
}
}


配置文件说明

<!-- 配置事务切面 -->
<bean id="transactionAspect" class="com.xuyi.learn.learnspring.aop.aopxml.TransactionAspect"></bean>

<!-- 配置aop -->
<aop:config>
<aop:aspect id="aspect" ref="transactionAspect">

<aop:pointcut
expression="execution(* com.xuyi.learn.learnspring.aop.aopxml.PersonService.add*(..))"
id="pointcutMethod" />
<aop:before method="openTransaction" pointcut-ref="pointcutMethod" />
<aop:after method="closeTransaction" pointcut-ref="pointcutMethod" />

</aop:aspect>
</aop:config>


备注

AOP编程的核心在于找出横切关注点,将横切关注点抽象为java类。以及配置execution表达式。


Spring AOP学习

AOP主要概念: ###

Cross Cutting Concern(横切关注点):
是一种独立服务,遍布在系统的处理流程之中(可以理解可以单独抽取出的服务)

Aspect(横切面):
对横切性关注点的模块化(可以简单理解成一个java类)

Advice(就简单理解成方法吧):
对横切性关注点的具体实现(在Spring 中可以理解为横切面的具体方法)

Pointcut(切入点):
它定义了Advice应用到哪些JoinPoint上,对Spring来说是方法调用

JoinPoint(加入点):
Advice在应用程序上执行的点或时机,Spring只支持方法的JoinPoint,这个点也  可以使属性修改,如:Aspecj可以支持

Weave(织入):
将Advice应用到Target Object上的过程叫织入,Spring支持的是动态织入

Target Object(目标对象):
Advice被应用的对象

Proxy:
Spring AOP默认使用JDK的动态代理,它的代理是运行时创建,也可以使用CGLIB


开发步骤:(基于注解的AOP实现)

1.Spring 的依赖jar 包导入(别忘记AOP 和AspectJ )
2.将横切关注点模块化,建立一个java类
3.采用注解指定该java类为Aspect
4.采用注解定义Advice(模块化类的方法) 和Pointcut;可以放在一起写
5.配置文件中配置启用AspectJ对Annotation的支持,并且将目标类和Aspect类配 置到IOC容器中
6.开发客户端试用


Spring对AOP的支持

1、如果目标对象实现了接口,在默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,也可以强制使用CGLIB生成代理实现AOP
3、如果目标对象没有实现接口,那么必须引入CGLIB包,spring会在JDK的动态代理和CGLIB代理之间切换


如何强制使用CGLIB生成代码?

加入CGLIB库,SPRING_HOME/lib/cglib/*.jar
加入如下配置,强制使用CGLIB代理

<!-- 开启Aspectj对AOP注解的支持 -->
<!-- 基于类生成代理对象(即强制使用CGLIB动态代理) -->
<aop:aspectj-autoproxy proxy-target-class="true"/>


JDK动态代理和CGLIB代理的区别?

JDK动态代理只能对实现了接口的类生成代理对象
CGLIB代理既可以代理接口也可以代理类某些场景下是会用到的

其实要使用AOP是很简单的,对于AOP我们更应该掌握它的思想。


实际开发中我们自己去编写AOP的机会是很少的,Spring的声明式事务是对AOP最典型的应用。结合Mybatis3.x、Hibernate框架使用。


切入点表达式

任意公共方法的执行:
execution(public * *(..))

任何一个以“set”开始的方法的执行:
execution(* set*(..))

AccountService 接口的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))

定义在service包里的任意方法的执行:
execution(* com.xyz.service.*.*(..))

定义在service包或者子包里的任意方法的执行:
execution(* com.xyz.service..*.*(..))

在service包里的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service.*)

在service包或者子包里的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service..*)

实现了 AccountService 接口的代理对象的任意连接点(在Spring AOP中只是方法执行):
this(com.xyz.service.AccountService)

备注:更加详细的配置请参考文档


总结

Spring AOP的应用并不难,我们更应该掌握和深入理解AOP实现原理以及AOP出现的缘由。代理模式和动态代理实现。


参考

1、https://en.wikipedia.org/wiki/Aspect-oriented_programming

2、https://www.ibm.com/developerworks/cn/java/j-lo-asm30/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring aop 编程