Spring框架的精髓就是他的IOC和AOP了,面试中也会经常问道,所以在这里整理一下:
首先我们要明确是将什么控制去反转,如何反转的:
当我们不使用Spring的时候,我们通常创建对象都是通过自己手动new一个,并调用该对象的类的构造方法进行初始化。这样无形之中增强了各个层之间的耦合性,举个例子:
比方说我们使用MySQL作为我们的DAO层,先抛开工厂不说,我们是不是在业务层写一个有参的构造方法,传递我们的DAO层对象,然后再new出来;
这就意味着我们的业务层和持久层具有很强的耦合性,一旦你的持久层改为Oracle,或者SQL Sever 是不是你的持久层也必须去修改大量代码;
那么我们使用IOC的时候,就可以不用自己去new一个对象,也就是把我们控制对象的创建的这个权力,反转到Spring的Bean工厂给我们创建,我们直接使用就可以了。
其实依赖注入和控制反转是相辅相成的,正因为我们使用IOC来创建各个层的一些对象,所以我们要通过配置的方式,将这些对象的普通属性和关联属性以注入的方式传给A该对象。
也就是说,当我们调用者去调用被调用者的某些方法时,他们之间产生了某种依赖关系,但是被调用者的对象,又是通过Spring的IOC创建,那么就必须把他注入到待用者中去。
一个晦涩难懂的词,举个例子:
当我们写好代码,准备交付甲方爸爸的时候,突然接到更改需求,需要再某某方法之前加个身份验证,调用完之后再添加日志功能,怎么办,头都烂了。 改源代码?不可能的,为了某个方法,很可能会出现其他方法大面积的出错,那我们使用Spring中AOP面向切面编程,便可以完美解决了:
首先介绍一些名词:
- 切面 Advice : 也就是你需要新增的方法,本例中的身份验证,添加日志就是切面;
- 切面类 :就是包含切面的类
- 切入点 Pointcut: 就是你所添加切面的那个方法
- 织入 Weave: 就是你的切面加入到PointCut的那个过程就叫织入;如果你在PointCut之前织入Advice,这个Advice就叫BeforeAdvice,相反的就叫AfterAdvice
。
拿本例来说 ,身份验证就是BeforeAdvice ,添加日志就是AfterAdvice,某某方法就是PointCut,
也就是说我们可以利用AOP,在不修改代码的情况下,去给你的pointcut去织入Advice,配置如下:
//ref就是你的切面类
//为所有权限下的指定包下的该类中以add开头的所有方法为PointCut
//方法名为checkSecurity的方法为beforeAdvice
首先说说什么是代理模式:
就是当你调用某个类的方法,去创建该类对象时,并不是创建真正的对象,而是这个类的代理对象,这是之所以能够实现AOP的原因,实际上很多框架都是使用的代理模式,这样提高效率,节省空间。
静态代理其实很简单,
他需要你自己写一个类,去实现和目标类相同的接口;
在这个类中还需要定义目标对象,可以交由通过IOC来实现;
之后去实现接口中的方法,进行重写,比如先执行某方法,在执行目标类对象的该方法;
这样你就可以去创建静态代理类对象,调用静态代理类的方法,去实现"织入"。
public class UserManagerImplProxy implements UserManager {
private UserManager userManager;
public void addUser(String username, String password) {
checkSecurity();
this.userManager.addUser(username, password);
}
private void checkSecurity() {
System.out.println("----------checkSecurity()---------------");
}
}
//////////
UserManager userManager = (UserManager)factory.getBean("UserManagerImplProxy");
userManager.addUser("admin", "123");
Spring真正的代理模式就是使用的jdk的动态代理。
我们需要写一个类:
但是你实现的不是目标类的接口,而是一个叫做InvocationHandler的这么个东西;
有一个指向目标类的Object类型的变量 ;
实现接口中的方法,直接上代码:
public class SecurityHandler implements InvocationHandler {
private Object targetObject; //需要他指向目标类对象
public Object newProxy(Object targetObject) {
this.targetObject = targetObject;
System.out.println("----------newProxy()---------------");
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
this);
//return后的方法,可以理解为得到目标类的代理类对象的方法,注意他的三个参数
(目标类的类加载器,目标类实现的接口,以及调用该方法的this指针)
这个this指针就是你这个类的对象了;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
checkSecurity(); //这是你加在目标类对象之前的方法
Object ret = null;
try {
ret = method.invoke(this.targetObject, args); //通过方法反射去执行目标类的该方法
}catch(Exception e) {
e.printStackTrace();
throw new java.lang.RuntimeException(e);
}
return ret;
}
private void checkSecurity() {
System.out.println("----------checkSecurity()---------------");
}
}
////////////////////测试类/////////////////
public class Client {
public static void main(String[] args) {
//创建实现InvocationHandler 接口类的对象
SecurityHandler handler = new SecurityHandler();
//执行该对象的newProxy方法,得到目标类的代理类
UserManager userManager = (UserManager)handler
.newProxy(new UserManagerImpl());
//执行代理类的addUser();
//注意,这里他是代理类,他会先调用this.invoke方法,也就是handler的invoke方法
//这样就可以做到"织入"了
userManager.addUser("admin", "123");
userManager.deleteUser(1);
}
}
当我们使用静态代理的时候,我们需要知道目标类的类名,方法名
但是很多情况下,我们无法得知,我们只能在程序运行中知道,这时候就得使用动态代理,更加灵活
但是我们发现一点,就是我们的目标类必须实现某个接口,如果某个类没有实现接口呢?
他的原理也很简单,他是你目标类的一个子类,并且会重写你目标类的所有方法,
有一个Object类型的变量 target
在你调用某个方法时,想调用子类的某个方法,在调用父类的该方法。
注意正因为他和目标类是继承关系,那么你的目标类就最好不要有final类型的方法,否则无法实现。