您的位置:首页 > 运维架构

什么是aop?

2016-02-15 10:54 691 查看
这个命题其实是讲了的,但是之前没有做,发现一些面试会问到,结合自己之前的学习经历。简单把这个问题描述一下。

aop是跟oop相对应的一个概念。分别是aspect oriented programming 和 object oriented programming。也即面向切面编程和面向对象编程。那面向对象是我们一个很熟悉的概念了,【在面向对象之前还有面向过程的编程,这个概念这里面就不厘了】。画个示意图,希望能够阐述这样一个概念。



那么对于前面的就是得到一个对象,或者得到一个完整的过程,直接对他进行操作。【事实上前面儿这个图画的也不理想】

对于后面的图来说,面向的是整个过程的一个剖儿面。

比如说下面的存储过程。

原来的逻辑是 一个数据库的存储操作,现在,我们要在这个 完成的逻辑上面添加一些日志,比如说,在存储之前,我们让日志信息记录说,UserDaoImpl请求添加一条记录到数据库中,在存储操作完成以后,让日志信息再打印一个 UserDaoImpl已经成功添加了一条数据。

对于这个完整过程来说,原来的逻辑是完完全全没有问题的。但是如果在一个大型系统中出了问题,如果在每个地方都有相应的提示信息的话,可以帮助我们快速定位到问题从而顺利解决。所以如果打印到“UserDaoImpl请求添加一条记录到数据库中”,然后报了错,下面那句话没有执行,是不是就很方便可以定位到存储时候出了问题。【这里只是举个例子说明织入也就是aop这个概念。之前dao都没出过问题,加上了切面反而出了问题,那还要这个切面干毛线?这个假设简直不能再糟糕,笑哭】【想到一个可能的逻辑,囧,比如在oracle里面的年龄字段写了check 18 to 60,而在前端的校验只定义成了0-150合理,这样在junit测试里面没有把例子跑全,或者自己只写了一个简单的测试用例比如数字都是23,34,59。导致在集成测试的时候有一个78被放行,这样就有可能出问题,在修改的时候可能回去数据库里面更改check 或者 去js验证里面,更改放行数字。终于圆回来了,反正总之现在的需求就是加一个aop!!!】

好了问题引入完毕,先来扯点儿蛋。

aop的知识点是从代理模式引入的。

那么什么是代理,为什么要代理。代理是怎么一回事儿?

代理分为两种形式,静态代理,动态代理。

假设有一个接口UserDao,【这个包名我就很喜欢】

package com.letben.dao;

/**
* userdao的 接口
* @author Administrator
*
*/
public interface UserDao {
public void saveUser();
}


还有一个它的实现类UserDaoImpl。

package com.letben.dao;
/**
* 这个地方用来实现userdao的 真实逻辑
* @author Administrator
*/
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("存储用户到数据库");
}
}


那原来这两个就能完成存储逻辑。

现在需求改变我们要加一个代理,实现在存储之前之后打印日志。那么新写一个UserDaoStaticProxy

package com.letben.dao;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* 同样是userdao的一个 实现类,没有更多的业务逻辑。只是为了在 实现逻辑的前后 打印日志
* @author Administrator
*/
public class UserDaoStaticProxy implements UserDao {
//得到打印日志的对象,它属于util包
Logger logger = Logger.getLogger(UserDaoStaticProxy.class.getName());
UserDao userDao;
public UserDaoStaticProxy(UserDao userDao){
this.userDao = userDao;
}
@Override
public void saveUser() {
logger.log(Level.INFO, "存储用户之前");
userDao.saveUser();
logger.log(Level.INFO,"存储用户之后");
}
}


然后添加一段测试代码:

package com.letben.dao;
public class Test {
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
UserDao userDaoApplication = new UserDaoStaticProxy(userDao);
userDaoApplication.saveUser();
}
}


运行结果:

二月 15, 2016 9:50:34 上午 com.letben.dao.UserDaoStaticProxy saveUser
信息: 存储用户之前
存储用户到数据库
二月 15, 2016 9:50:35 上午 com.letben.dao.UserDaoStaticProxy saveUser
信息: 存储用户之后


在测试代码中就能够理解是为什么了。我们首先创建了一个UserDaoImpl也就是最开始用来完成存储业务的类。然后又创建了UserDaoImpl来增加日志信息。这样aop的概念,就比较好解释了,就是在这个逻辑不改变的情况下,多那么一点点东西,让整个流程更加清楚、完善。

但是静态代理毕竟可复用性太差,要重新写java代码,这就比较糟糕。

那动态代理的部分,就是利用框架,来实现这样一种需求的嵌入。这还有两种形式,一种是xml的配置形式,还有一种是 注解的形式。

形式一:xml配置形式

创建web工程。

导入jar包。【没有的在下面写邮箱吧,或者小纸条我也行。】

目录结构。



1、那我们其实真正做的就是后来,导入了一些专用的jar包。



2、添加一个切面包。以及文件:AspectDemo.java。

package com.letben.aspect;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 切面类
* @author Administrator
*/
public class AspectDemo {
Logger log = Logger.getLogger(AspectDemo.class.getName());
/**
*  在某断点之前添加通知,我们这里就是 给 添加方法 之前设置的 所以 这个 方法的名字,也是 这么取名的
*/
public void beforeSaveUser(){
log.log(Level.INFO, "存储前");
}
/**
* 在saveUser方法 调用之后 执行此方法
*/
public void afterSaveUser(){
log.log(Level.INFO,"存储后");
}
public void aroundSaveUser(ProceedingJoinPoint point){
log.log(Level.INFO,"环绕-前");
try {
point.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
log.log(Level.INFO,"环绕-后");
}
public void exceptionAboutSave(Exception e){
//在这里并不会有结果,因为 上一个 已经 处理了异常,并且 我们 的存储方法 是正确的 不存在 输入错误 这回事。。所以 为了巩固 我们再写一个 面向切面代理出错版本。
log.log(Level.WARNING,"出现异常");
}
}


3、在beans.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"
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">

<!-- 要有 对应 实现逻辑的 userDao -->
<bean id="userDao" class="com.letben.dao.UserDaoImpl"></bean>

<!-- 切面类 对应的 实体对象 -->
<bean id="aspectDemo" class="com.letben.aspect.AspectDemo"></bean>
<!-- aop的配置 -->
<aop:config>

<!-- 选定某一个类为切面 -->
<aop:aspect ref="aspectDemo">
<!-- 切入点表达式,就是 我们为了做切入,一定要有一个切入点,用来表示 在某一个地方开始 执行我们的某些程序-->
<!-- 文档中 6.2.3.4里面的各个 示例 -->
<!-- 解释:任意的修饰符  【空格】 这个 包下的 这个 方法里面的所有方法(这里面带着所有的参数)-->
<aop:pointcut expression="execution(* com.letben.dao.UserDao.*(..))" id="point" />

<!-- 通知类型+插入方法+切入点 =连接点 -->
<aop:before method="beforeSaveUser" pointcut-ref="point" />
<!--
上面这句话有两种书写方式:
<aop:before method="beforeSaveUser" pointcut="execution(* com.letben.dao.UserDao.*(..))">
要么直接 写切入点,要么采用引入的方式 写 切入点
-->
<aop:after method="afterSaveUser" pointcut-ref="point"/>
<aop:around method="aroundSaveUser" pointcut-ref="point"></aop:around>

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


这样就完成了动态代理的xml形式的书写。

注解方式:

注解编程里面,配置就比较简单。

jar包还是那些。

beans.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"
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">

<!-- 启动注解编程 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

<bean id="userDao" class="com.letben.dao.impl.UserDaoImpl"></bean>

<!-- 因为 当前注解类 并不需要 给谁使用,所以 直接注册 并不需要 别的什么逻辑 -->
<bean class="com.letben.aspect.AspectForUserDao"/>

</beans>


切面类:

package com.letben.aspect;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* 声明这是一个 注解类
* @author Administrator
*/
@Aspect
public class AspectForUserDao {
Logger logger = Logger.getLogger(AspectForUserDao.class.getName());
/**
* 前置 方法 里面需要一个 切入点
*/
@Before("execution(* com.letben.dao.UserDao.*(..))")
public void test1(){
logger.log(Level.INFO,"之前");
}
/**
* 在7.0里面 那个 value 不能加上。但是 在 6.0里面这个 参数 是需要的
* @Around(value="exection(*com.letben.dao.UserDao.*(..))")这是不正确的写法 在7.0里面
*/
@After("execution(* com.letben.dao.UserDao.*(..))")
public void test2(){
logger.log(Level.INFO,"之后");
}
/**
* 不,不是 那个 value 也不知道 是哪里,这个 注解编程 很奇怪,就是 很奇怪。我也不知道 那里 写错了,但是 就是 报了 一些 处理了20min的异常就在这个 写注解的地方
* @param point
*/
@Around(value="execution(* com.letben.dao.UserDao.*(..))")
public void test3(ProceedingJoinPoint point){
logger.log(Level.INFO,"环绕-前");
try {
point.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
logger.log(Level.INFO,"环绕-后");
}
}


所以动态的代理就是 我们所说的aop也就是织入了一个逻辑。织入这个词,感觉翻译起来还是很贴切的。就是在完整的东西上加进去一些东西,让他更加完美。上面是对日志的织入。

下面有事务的织入。略有不同。

只说修改的地方了,原来的那个工程还是有点儿小。。。

对于事务这样的操作,想来应该是放到service层里面最合适。比如存取钱的操作同时完成。不应该封装到dao层理面,而应该是 服务层理面,但是其实,这样的操作也可在dao里面写,但是框架提供了这样的方式,可以让我们进行事务操作。所以我们来应用一下。

【完整样例代码:欢迎写邮箱,或者小纸条我,主要十几个包里面的十几个类,复制粘贴确实容易让人没有兴趣看下去。但是都是前面儿的一些知识点的总结。】

serviceImpl 里面添加:

/**
* 事务这样的操作都写在 业务层 而非 持久化层
*/
public void tryTransaction(){
UserPo user1 = getUserById(2);
UserPo user2 = getUserById(3);
user1.setUserAge(39);
userDao.updateUser(user1);
user2.setUserAge(Integer.parseInt("23"));
userDao.updateUser(user2);
}


在beans.xml里面新增事务工厂:

<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 事务管理器 管理的是哪一个 数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的代理工厂 -->
<bean id="proxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 给事务代理工厂一个 事务管理器  -->
<property name="transactionManager" ref="transactionManager"></property>
<!-- 告诉代理工厂 的代理权限——要管理那些接口 -->
<property name="proxyInterfaces">
<list><!-- 列表给值 底层 list和 set 是一个 类型的 -->
<value>com.letben.service.UserService</value>
</list>
</property>
<!-- 告诉代理工厂要管理的 实现类 -->
<property name="target" ref="UserService"></property>
<!-- 告诉事务管理器 要管理的规则 和方法 -->
<property name="transactionAttributes">
<props><!-- 参数给值  底层 props 和 map 是一个类型的 -->
<!-- 下面这个 key对应的是实现类里面的方法。符合通配符的使用规则* -->
<prop key="tryTransaction">PROPAGATION_REQUIRED</prop><!-- 需要传播 -->
</props>
</property>
</bean>


这样就完成了 带有工厂的新增事务的配置方式。

当然还有两种。一种是不使用事务工厂的方式,还有一种是自动代理的方式。

当然这种织入不仅有日志的添加,事务的处理,还包括权限的检查等等多个方面。所谓切面编程就是在不影响原来事情处理逻辑的基础上,添加内容,让这个流程变得更加完善合情理合逻辑。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: