背景
当我们的项目中需要给某些方法添加日志记录时,这些需要记录的方法散布在30多个类中.解决这个问题最直接的
方法是:创建一个超类/接口,然后让所有的日志功能的类继承它。如果开发期间需求变动,那么就要修改就会散布
在30多个类中,这样大量的修改,无疑会增加出错的几率,并且加大系统维护的难度。AOP的出现解决了这个问题。
问题:写多个重复的权限验证
解决方案:利用AOP横切,将权限验证抽离出来
介绍
AOP为Aspect OrientedProgramming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功
能的统一维护的一种技术。AOP 的核心构造是方面,它将那些影响多个类的行为(日志或事务)封装到可重用的模块中。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提
高了开发的效率。
OOP与AOP
OOP针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。OOP允许你定
义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,
而与它所散布到的对象的核心功能毫无关系。这种散布在各处的无关的代码被称为横切代码,在OOP设计中,它导致
了大量代码的重复,而不利于各个模块的重用。
AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行
为封装到一个可重用模块。
实现原理
AOP的实现原理即为动态代理,AOP框架自动创建的AOP代理
1.静态织入(AspectJ)
在代码的编译阶段植入Pointcut的内容 ,即静态代理的实现方式。优点是性能好
2.动态代理(Spring)
代码执行阶段,在内存中截获对象,动态的插入Pointcut的内容,优点是不需要额外的编译,但是性能比静态织入要
低。与 AspectJ相同的是,Spring AOP同样需要对目标类进行增强,也就是生成新的 AOP代理类;与 AspectJ不同的
是,Spring AOP无需使用任何特殊命令对 Java源代码进行编译,它采用运行时动态地、在内存中临时生成“代理类”
的方式来生成 AOP代理。
Spring的 AOP代理由 Spring的 IoC容器负责生成、管理,其依赖关系也由 IoC容器负责管理。因此,AOP代理可
以直接使用容器中的其他 Bean实例作为目标,这种关系可由 IoC容器的依赖注入提供
具体实现
1、日常使用
<pre name="code" class="csharp"><span style="font-size:18px;">client:
public class Client {
public static void main(String[] args) {
//使用IOC容器来管理对象,加载配置文件applicationContextDaily.xml
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContextDaily.xml");
//获得具体的实例
UserManager userManager = (UserManager)factory.getBean("userManager");
//调用相应的方法
userManager.addUser("张三", "123");
}
}</span>
<span style="font-size:18px;">
用户管理接口:
public interface UserManager {
//添加用户
public void addUser(String username, String password);
//删除用户
public void delUser(int userId);
//查询用户
public String findUserById(int userId);
//修改用户
public void modifyUser(int userId, String username, String password);
}</span>
<span style="font-size:18px;">
用户管理的具体实现:
public class UserManagerImpl implements UserManager {
public void addUser(String username, String password) {
//每个方法必须写 checkSecurity();
checkSecurity();
System.out.println("----Add方法:"+username);
}
public void delUser(int userId) {
//每个方法必须写 checkSecurity();
checkSecurity();
System.out.println("---del方法:"+userId);
}
public String findUserById(int userId) {
//每个方法必须写 checkSecurity();
checkSecurity();
System.out.println("---Find方法:"+userId);
return "张三";
}
public void modifyUser(int userId, String username, String password) {
//每个方法必须写 checkSecurity();
checkSecurity();
System.out.println("---Modify方法:"+userId);
}
private void checkSecurity() {
System.out.println("-------checkSecurity-------");
}
}</span>
<span style="font-size:18px;">
配置文件:applicationContextDaily.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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">xsd</span>
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="userManager" class="com.bjpowernode.daily.UserManagerImpl"/>
</beans></span>
结果:
存在的问题:
如果每个方法都需要使用checkSecurity()方法时,带来的问题:一重复的代码增多;二如果需求改变,所涉及的
代码需要全部修改。
2、静态代理:AspectJ(采用注解形式)
静态代理解决了上面高频率改代码的问题。
<span style="font-size:18px;">依赖包配置:
* SPRING_HOME/dist/spring.jar
* SPRING_HOME/lib/log4j/log4j-1.2.14.jar
* SPRING_HOME/lib/jakarta-commons/commons-logging.jar
* SPRING_HOME/lib/aspectj/*.jar</span>
<span style="font-size:18px;">
引入代理类SecurityHandler,让它去执行相同的模块checkSecurity()方法
@Aspect
public class SecurityHandler {
/**
* 定义Pointcut,Pointcut的名称为addAddMethod(),此方法没有返回值和参数
* 该方法就是一个标识,不进行调用
*/
@Pointcut("execution(* add*(..))")
private void addAddMethod(){};
/**
* 定义Advice,表示我们的Advice应用到哪些Pointcut订阅的Joinpoint上
*/
@Before("addAddMethod()")
//@After("addAddMethod()")
private void checkSecurity() {
System.out.println("-------checkSecurity-------");
}
}</span>
<span style="font-size:18px;">
配置文件:
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<!-- 启用AspectJ对Annotation的支持 -->
<aop:aspectj-autoproxy/>
<bean id="userManager" class="com.bjpowernode.spring.UserManagerImpl"/>
<bean id="securityHandler" class="com.bjpowernode.spring.SecurityHandler"/>
</beans>
</span>
<span style="font-size:18px;">
client:
public class Client {
public static void main(String[] args) {
//使用IOC容器来管理对象,加载配置文件applicationContextDaily.xml
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
//获得具体的实例
UserManager userManager = (UserManager)factory.getBean("userManager");
//调用相应的方法
userManager.addUser("张三", "123");
}
}</span>
<span style="font-size:18px;">
用户管理接口:
public interface UserManager {
//添加用户
public void addUser(String username, String password);
//删除用户
public void delUser(int userId);
//查询用户
public String findUserById(int userId);
//修改用户
public void modifyUser(int userId, String username, String password);
}</span>
<span style="font-size:18px;">
用户管理具体实现:
public class UserManagerImpl implements UserManager {
public void addUser(String username, String password) {
System.out.println("----Add方法:"+username);
}
public void delUser(int userId) {
System.out.println("---del方法:"+userId);
}
public String findUserById(int userId) {
System.out.println("---Find方法:"+userId);
return "张三";
}
public void modifyUser(int userId, String username, String password) {
System.out.println("---Modify方法:"+userId);
}
}</span>
存在问题:
代码重复问题依然存在,SecurityHandler中每个方法需要重复设置
3、动态代理:Sping AOP
解决了静态代理不能解决的代码重复问题,系统运行时动态获得需要的对象
依赖包:
* SPRING_HOME/dist/spring.jar
* SPRING_HOME/lib/log4j/log4j-1.2.14.jar
* SPRING_HOME/lib/jakarta-commons/commons-logging.jar
* SPRING_HOME/lib/aspectj/*.jar
配置文件:
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="userManager" class="com.bjpowernode.spring.UserManagerImpl"/>
<bean id="securityHandler" class="com.bjpowernode.spring.SecurityHandler"/>
<aop:config>
<!--获得公共模块的引用-->
<aop:aspect id="securityAspect" ref="securityHandler">
<!-- 公共模块应用在哪些方法上 -->
<aop:pointcut id="addAddMethod" expression="execution(* com.bjpowernode.spring.*.add*(..)) || execution(* com.bjpowernode.spring.*.del*(..))"/>
<!-- 指定公共模块出现方法的位置 -->
<aop:before method="checkSecurity" pointcut-ref="addAddMethod"/>
</aop:aspect>
</aop:config>
</beans>
//公共模块
public class SecurityHandler {
private void checkSecurity() {
System.out.println("-------checkSecurity-------");
}
}
用户管理接口:
public interface UserManager {
//添加用户
public void addUser(String username, String password);
//删除用户
public void delUser(int userId);
//查询用户
public String findUserById(int userId);
//修改用户
public void modifyUser(int userId, String username, String password);
}
</pre><pre code_snippet_id="633594" snippet_file_name="blog_20150401_15_1971169" name="code" class="csharp">用户管理具体实现:
public class UserManagerImpl implements UserManager {
public void addUser(String username, String password) {
System.out.println("----Add方法:"+username);
}
public void delUser(int userId) {
System.out.println("---del方法:"+userId);
}
public String findUserById(int userId) {
System.out.println("---Find方法:"+userId);
return "张三";
}
public void modifyUser(int userId, String username, String password) {
System.out.println("---Modify方法:"+userId);
}
}
结果:
注:
方面/切面:Aspect
公共模块,比如事务、日志,相当于我们上面的公共方法checkSecurity()
连接点:JoinPoint
表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处
理异常等等,Spring只支持方法执行连接点,在AOP中表示为“在哪里做”;相当于我们程序运行时,使用配置
文件来初始化目标对象和代理对象。
切入点:PointCut
它定义了Advice应用到哪些JoinPoint上,即我们上例中的增、删、改、查方法
增强:Advice
Advice 定义了在 pointcut里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别
是在每个joinPoint 之前、之后还是代替执行的代码。
目标对象(TargetObject):
需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被增强的对象。由于Spring AOP 通过代
理模式实现,从而这个对象永远是被代理对象,在AOP中表示为“对谁做”;
AOP代理(AOPProxy):
AOP框架使用代理模式创建的对象,从而实现在连接点处插入增强,就是通过代理来对目标对象应用切面。
织入(Weaving):
织入是一个过程,是将公共模块应用到目标对象从而创建出AOP代理对象的过程。
AOP实现部分:
定义普通业务组件。
定义切入点,一个切入点可能横切多个业务组件。
定义增强处理,增强处理就是在 AOP框架为普通业务组件织入的处理动作。
总结
Jboss 4.0 完全采用 AOP 的思想来设计的 EJB 容器,它已经通过了J2EE 的认证,并且在工业化应用中证明是
一个优秀的产品。相信在不远的将来,会出现更多采用 AOP 思想设计的产品和行业应用。面向切面编程为我们带来
了新的想法、新的思想,需要我们慢慢的吸收。