0x00 Spring AOP

0x00 基本概念

AOP 即面向切面编程,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect”,即切面。简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。Spring 中 AOP 代理由 Spring 的 IOC 容器负责生成、管理,其依赖关系也由 IOC 容器负责管理。因此,AOP 代理可以直接使用容器中的其它 bean 实例作为目标,这种关系可由 IOC 容器的依赖注入提供。

AOP 的关键单元是切面,或者说关注点,即为我们想实现特定业务功能的方法。一些切面可能有集中的代码,但是有些可能被分散或者混杂在一起,例如日志或者事务。这些分散的切面被称为横切关注点。横切关注点是贯穿整个应用程序的关注点。像日志、安全和数据转换。

AOP 的最大意义其实就是在不改变原来代码的前提下,也不对源代码做任何协议接口要求。而实现了类似插件的方式,来修改源代码,给源代码插入新的执行代码。

0x01 通知类型

通知(advice)是切面的具体逻辑实现。有以下 5 种形式:

  • 前置通知(Before Advice):在连接点之前执行的 Advice,不过除非抛出异常,否则没有能力中断执行流。使用@Before注解使用这个 Advice。
  • 返回后通知(After Returning Advice):在连接点正常结束之后执行的 Advice。例如一个方法没有抛出异常并正常返回。通过@AfterReturning注解来使用。
  • 抛出(异常)后执行通知(After Throwing Advice):如果一个方法通过抛出异常来退出的话,这个 Advice 就会被执行。通过@AfterThrowing注解来使用。
  • 后置通知(After Advice):无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些 Advice。通过@After注解使用。
  • 环绕通知(Around Advice):决定是否调用目标方法,而且能够获得到目标方法的返回值,而且还能改变这些返回值。通过@Around注解使用。

0x01 代理模式

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。其主要特点是在不改变原有类的前提下,在原有类某些方法执行前后,插入执行任意代码。所以代理模式需要写新的类对原有的类进行包装,有如下三种实现方式:

0x00 静态代理

需要增强原有类的哪个方法,就需要对在代理类中包装哪个方法。在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。

众所周知,Java 中的HashMap不是线程安全的,但是一经Collections.synchronizedMap的包装,它就变成线程安全的了,其实现原理就是使用静态代理为HashMap的运行加了一层代理类,在代理类中使用锁包装了线程不安全的方法。Collections.synchronizedMap代码如下:

1
2
3
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}

而这里的SynchronizedMap即为Collections里面的一个静态类,也就是用于包装线程不安全的HashMap的静态代理类,我们来看这个代理类的代码,我只挑选了一些关键的方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L;

private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize

SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
}

SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}

public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}

public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}

// ....
}

我们可以看到,其使用一个线程不安全的Map来构造这个SynchronizedMap,然后为其每一个操作都加了锁。这就是静态代理以增强原有类的功能。

静态代理的缺点是代理对象需要与目标对象实现一样的接口,所以会产生很多代理类,为日后的维护增添麻烦。

0x01 动态代理

动态代理就是基于反射的代理,对象和方法都是传入的变量,以此动态调用被代理对象的任何方法,JDK 实现代理,只需要使用newProxyInstance方法即可。我们来看一个例子:

接口 IUserDao.java

1
2
3
public interface IUserDao {
void save();
}

需要代理的目标对象 UserDao.java

1
2
3
4
5
public class UserDao implements IUserDao {
public void save() {
System.out.println("---- Already saved!----");
}
}

代理工厂类 ProxyFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ProxyFactory {
private Object target;

public ProxyFactory(Object target){
this.target=target;
}

// generate proxy instance
public Object getProxyInstance(){
// invoke newProxyInstance method
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Start transaction");
// invoke target object method and get the return value
Object returnValue = method.invoke(target, args);
System.out.println("Commit transaction");
return returnValue;
}
}
);
}
}

测试类 App.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class App {
public static void main(String[] args) {
// original object
IUserDao target = new UserDao();
System.out.println(target.getClass());

// get a proxy instance for target object
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass());

// invoke method
proxy.save();
}
}

Proxy.newProxyInstance的定义时这样的:

1
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

对于InvocationHandler而言,其就只有一个invoke方法:

1
public Object invoke(Object proxy, Method method, Object[] args)

第二个参数Method method将会带有当前调用的方法的名称等信息,我们可以通过这些信息来选择性地对被代理类中的方法进行代理操作。

0x02 CGLIB 代理

静态代理和动态代理都要求目标对象是实现一个特定接口的目标对象,但有的时候一个对象是一个单独的对象,并没有实现任何接口,这个时候就可以使用以目标对象子类的方式类实现代理,即 CGLIB 代理,也叫做子类代理。显然,代理的类不能被标记为 final,否则报错。

使用 CGLIB 代理时需要引入 CGLIB 的 jar 包,如果你用了 Spring,其核心中已经包括了这个包,所以可以直接引入包spring-core,代码实例:

没有实现任何接口的目标对象类 UserDao.java

1
2
3
4
5
public class UserDao {
public void save() {
System.out.println("---- Already saved!----");
}
}

CGLIB 代理工厂 ProxyFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ProxyFactory implements MethodInterceptor {
// the target object needed to be proxied
private Object target;

public ProxyFactory(Object target) {
this.target = target;
}

// create a proxy object instance
public Object getProxyInstance(){
// tool instance
Enhancer en = new Enhancer();
// set super class of the target object
en.setSuperclass(target.getClass());
en.setCallback(this);
// create proxy instance
return en.create();
}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Start transaction");
// invoke the super class method
Object returnValue = method.invoke(target, args);
System.out.println("Commit transaction");
return returnValue;
}
}

测试类 App.java

1
2
3
4
5
6
7
8
public class App {
@Test
public void test(){
UserDao target = new UserDao();
UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
proxy.save();
}
}

0x02 Spring IoC

0x00 IoC 的基本思想(目的)

IoC 即 Inversion of Control,控制反转。通常,每个对象在使用他的合作对象时,自己需要使用像new Object()这样的语句来完成合作对象的申请工作。这样会造成对象间的耦合度高。IoC 的思想是:Spring 容器来实现这些相互依赖对象的创建、协调工作。对象只需要关系业务逻辑本身就可以了。从这方面来说,对象如何得到他的协作对象的责任被反转了(IoC、DI)。

所有的类都会在 Spring 容器中登记,告诉 Spring 你是个什么东西,你需要什么东西,然后 Spring 会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 Spring 来控制,也就是说控制对象生存周期的不再是引用它的对象,而是 Spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被 Spring 控制,所以这叫控制反转。

0x01 依赖注入 DI(方式)

IoC 的一个任务是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过 DI(Dependency Injection,依赖注入)来实现的。比如对象 A 需要操作数据库,以前我们总是要在 A 中自己编写代码来获得一个 Connection 对象,有了 Spring 我们就只需要告诉 Spring,A 中需要一个 Connection,至于这个 Connection 怎么构造,何时构造,A 不需要知道。A 需要依赖 Connection 才能正常运行,而这个 Connection 是由 Spring 注入到 A 中的,这即为依赖注入。

0x03 Spring Bean

0x00 bean 的作用域

bean 一共有如下 5 种作用域:

  • singleton(单例):在 IoC 容器中仅存在一个 bean 实例,以单例的形式存在,默认值。
  • prototype(原型):每次请求都会创建一个新的 bean 实例,即每次调用getBean()时,相当于执行new XXBean()。
  • request:每次 HTTP 请求都会创建一个新的 bean,该 bean 仅在当前 HTTP request 内有效,该作用域仅适用于WebApplicationContext环境。
  • session:每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效,仅适用于WebApplicationContext环境。
  • global-session:所有的 session 共享一个 bean 实例。

我们可以在xml配置文件中使用scope标签来设置 bean 的作用域:

1
2
3
<bean id="..." class="..." scope="singleton">
...
</bean>

在 xml 中我们还可以使用lazy-init=“true”标签来延迟加载 bean,这样,只有在第一次获取 bean 时,才会初始化 bean。

同样在 Spring boot 中我们亦可使用注解的形式来设置其作用域:

1
2
3
4
5
@Service
@Scope("singleton")
public class ServiceImpl{
// ...
}

0x01 bean 的加载流程(填充)

0x04 Spring MVC

0x00 工作流程

客户端发送请求 -> 前端控制器DispatcherServlet接受客户端请求 -> 找到处理器映射HandlerMapping解析请求对应的 Handler -> HandlerAdapter会根据 Handler 来调用真正的处理器开始处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象 -> 前端控制器DispatcherServlet渲染数据 -> 将得到视图对象返回给用户。

0x01 设置重定向和转发

在返回值前面加"forward:"就可以让结果转发,譬如"forward:user.do?name=method4"。

在返回值前面加"redirect:"就可以让返回值重定向,譬如"redirect:http://www.baidu.com"。