logo头像

一路过来,不过游牧自己。。

SSM之Spring(六)


Ioc是Spring的一个重要概念,在Spring中,还有另外一个重要思想:AOP(面向切面的编程)!

一、AOP简介

1、AOP:Aspect Oriented Programming(面向切面的编程)
2、aop在Spring中的作用:提供声明式服务,允许用户自定义切面
3、AOP由来:在不改变原有代码的情况下增加新的功能,通过代理的方式来实现
传统的编程都是自上而下,纵向编程,如图所示:
传统编程
AOP模式:AOP就是在某一个点从横向切过来,加了一部分功能,是横向的编程,例如:
AOP模式
4、AOP的优点,和静态代理有异曲同工之妙:
a)使得真实角色处理的业务更加纯粹,不再去关注一些公共的事情
b)公共的业务由代理完成,实现了业务的分工
c)公共业务发生拓展时变得更加集中和方便
5、关于一些名词的解释:
a)关注点:增加的某个业务 如:日志,安全,事务,缓存等
b)切面:某个关注点的模块化,一般就是将这部分功能抽出来作为一个类
c)连接点:一个方法的执行
d)通知:在切面的某个特定的连接点上执行特定的动作
e)织入:把切面链接到其他应用程序的过程

二、使用Spring来实现AOP

这里讲三种实现AOP的方式,分别是:通过Spring API来实现、自定义实现AOP和使用注解去实现AOP

1、通过Spring API去实现

下面通过一个代码例子来说明一下如何去利用API去实现AOP:
首先我们先去写一个接口,把公共部分提取出来:

1
2
3
4
5
6
public interface UserService {
public void add();
public void update(int a);
public void delete();
public void search();
}

然后我们去写实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class UserServiceImpl implements UserService {

public void add() {
System.out.println("添加用户");

}

public void update(int a) {
System.out.println("修改用户");

}

public void delete() {
System.out.println("删除用户");

}

public void search() {
System.out.println("查找用户");

}

}

为了体现AOP,从横切面切入这种思想,我们先得定义这么个东西,这个东西才能切入进来:

1
2
3
4
5
6
7
8
9
10
public class BeforeLog implements  MethodBeforeAdvice{

@Override
public void before(Method method, Object[] arg1, Object target)
throws Throwable {

System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行");
}

}

从名称可以清楚的看到,这是一个方法前的一个log日志, before(Method method, Object[] arg1, Object target)里面的method,是指切入的方法,arg1是指切入的参数,target是指切入的对象!
之后我们得去写这样一个配置文件,这个配置文件告诉我们要怎样去切入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="cn.ServiceImpl.UserServiceImpl"/>
<bean id="Beforelog" class="cn.log.BeforeLog"/>
<aop:config>
<aop:pointcut expression="execution(* cn.ServiceImpl.*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="Beforelog" pointcut-ref="pointcut"/>
</aop:config>
</beans>

如上所示,我们要知道切入点,定义为pointcut,这个切入点是expression,cn.ServiceImpl..(..)这个事指作用范围,cn.ServiceImpl的所有类的所有方法,···是指所有的参数,就是说这里面的所有方法都可以用!
后面的advisor是指切入的东西,advice-ref=”Beforelog”指切入的是Beforelog,pointcut-ref=”pointcut”,pointcut是指切入到哪里!
这样就什么都有了!
最后我们再写一个Test调用一下这个函数:

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService)ac.getBean("userService");
userService.update(2);
}
}

这是指调用的是userService这个bean,这时候调用bean,就会执行aop,返回一个对象之后,执行方法时就会将AOP效果显示出来!
结果就是:

1
2
cn.ServiceImpl.UserServiceImpl的update方法被执行
修改用户

前面的那句:cn.ServiceImpl.UserServiceImpl的update方法被执行
是before方法执行的,是AOP插入的方法的信息的打印!
“修改用户”就是update方法的一个展示!

2、自定义实现AOP

这里我们定义的类就不实现什么接口什么东西的了,所以我们先定义一个Log类

1
2
3
4
5
6
7
8
public class Log {
public void before(){
System.out.println("方法执行前");
}
public void after(){
System.out.println("方法执行后");
}
}

这样看,这就有两种方法,我们的想法就是将这两个方法切入到方法的前后,怎样去做呢,其实最主要知道的是怎样去写这个配置文件:

1
2
3
4
5
6
7
8
9
10
    <bean id="userService" class="cn.ServiceImpl.UserServiceImpl"/>
<bean id="log" class="cn.log.Log"/>
<aop:config>
<aop:aspect ref="log">
<aop:pointcut expression="execution(* cn.ServiceImpl.*.*(..))" id="pointcut"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>

这样,利用before,after参数定义了这么个东西实现的位置,实在前实现log的before参数,在之后实现after参数,这样就会得到这样的结果:

1
2
3
方法执行前
修改用户
方法执行后

这样的切面方法可以使我们更加灵活的定义我们的方法!

3、利用注解实现AOP

这是我觉着目前最容易理解和最方便使用的一种方式,话不多说,自己看:
首先写配置文件:

1
2
3
<bean id="userService" class="cn.ServiceImpl.UserServiceImpl"/>
<bean id="log" class="cn.log.Log"/>
<aop:aspectj-autoproxy/>

aop:aspectj-autoproxy/这是一个自动装配aop的过程,但是他怎么知道要装配什么呢?我们必须要写注解,注解写在哪呢,写在要切入的方法前,如下:

1
2
3
4
5
6
7
8
9
10
11
@Aspect
public class Log {
@Before("execution(* cn.ServicelImpl.*.*(..))")
public void before(){
System.out.println("方法执行前");
}
@After("execution(* cn.ServicelImpl.*.*(..))")
public void after(){
System.out.println("方法执行后");
}
}

这里的@Aspect是指这是一个切面!@Before(“execution( cn.ServicelImpl..*(..))”)表示装配到哪里的方法之前!
然后Test:

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService)ac.getBean("userService");
userService.add();
}
}

输出结果是:

1
2
3
方法执行前
添加用户
方法执行后

感觉是不是很简单明了,没有那么多复杂的结构,我们还可以加一个环绕通知:

1
2
3
4
5
6
7
8
9
10

@Around("execution(* cn.ServicelImpl.*.*(..))")
public Object aroud(ProceedingJoinPoint jp) throws Throwable{
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法
Object result = jp.proceed();
System.out.println("环绕后");
return result;
}

套路都是一样的,但是,这里的ProceedingJoinPoint是什么意思呢?那是因为:如果不用这个,那么就会不去执行函数,而直接执行环绕前和环绕后这两个输出函数,输出结果没有函数结果,所以,加的jp.proceed()就是让他执行函数!

总之,AOP采用的是分离的一种思想,分离程度越高,表示越难,设计要求也就越高!
Spring AOP本质:是动态代理的一个框架,我们手动写代理还是很麻烦的,所以我们就把这部分抽离出来,然后采用AOP配置去切入,这样就可以在不改变原有代码的情况下,增加一些新功能!

多思考,所走路,越努力,越幸运!
————-YoungerFary

微信打赏

赞赏是不耍流氓的鼓励