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"