文章目录
- OCP开闭原则
- 依赖倒置原则
- 控制反转
- 依赖注入DI
- Set方法注入
- 构造注入
- Sping特点
- 代理模式
- 代理模式中的角色
- 动态代理
- JDK动态代理
- newProxyInstance() 的三个参数
- JDK实现代理的步骤
- 第一步:创建目标对象
- 第二步:创建代理对象
- 第三步:调用代理对象的代理方法
- CGlib动态代理
OCP开闭原则
OCP(Open-Closed Principle):开放封闭原则
OCP是面向对象设计中的重要原则之一,其核心思想是:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
也就是说,如果在进行功能扩展的时候,添加额外的类是没有问题的,但是因为功能扩展而修改之前运行正常的程序,就是忌讳的,不被允许的。
因为一旦修改之前运行正常的程序,就会导致项目整体要进行全方位的重新测试,这是相当麻烦的过程。导致这样问题的主要原因是:代码和代码之间的耦合度太高了。
依赖倒置原则
DIP(Dependence Inveersion Principle):依赖导致原则
DIP倡导程序要面向抽象编程,面向接口编程,不要面向具体编程,不要依赖于具体事项。
让上层不再依赖下层,下面改动了,上面的代码不会受到牵连。
这样可以大大降低程序的耦合度,耦合度低了,扩展力就强了,同时代码复用性也会增强。
(软件七大开发原则都是在为解耦合服务)
思考
1.如何做到依赖倒置?
2.依赖又倒置给谁,谁来实现和维护?
Spring框架就很好的做到了这一点,在Spring框架中,spring帮我们new对象,并且将new出来的对象赋值到属性上,并且帮助我们维护对象和对象之间的的关系。
这一点就称之为控制反转IOC
控制反转
IOC
IOC(Inversion of Control):控制反转
上文提到的"依赖倒置"其实说的就是一种"控制反转"的思想。
IOC是面向对象编程中的一种设计思想,可以用来降低代码之间的耦合度,符合依赖倒置原则。
控制反转的核心是:
将对象的创建权交出去,将对象与对象之间关系的管理权交出去,由第三方容器来负责创建和维护。
而Spring框架就是一个实现了IoC思想的框架。
依赖注入DI
DI(Dependency Injection):依赖注入
DI是Spring对控制反转的具体实现
通常,依赖注入的实现又包括两种方式:
● set方法注入
● 构造方法注入
Set方法注入
set注入基于set方法实现:
底层会通过反射机制调用属性对应的set方法然后给属性赋值。这种方式要求属性必须对外提供set方法。
public class UserService {
private UserDao userDao;
// 使用set方式注入,必须提供set方法。
// 反射机制要调用这个方法给属性赋值的。
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao" ref="userDaoBean"/>
</bean>
</beans>
构造注入
核心原理:通过调用构造方法来给属性赋值。
Sping特点
Spring是一个无侵入式的针对Bean的生命周期进行管理的轻量级的开源框架。是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。
1. 轻量
a. 从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。
并且Spring所需的处理开销也是微不足道的。
b. Spring是非侵入式的:Spring应用中的对象不依赖于Spring的特定类。
2. 控制反转
a. Spring通过一种称作控制反转(IoC)的技术促进了松耦合。
当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。
你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
3. 面向切面
a. Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。
应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
4. 容器
a. Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,
你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),
你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。
然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
5. 框架
a. Spring可以将简单的组件配置、组合成为复杂的应用。
在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。
Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
代理模式
应用背景
西游记中高老庄收猪八戒那一集中,猪八戒背着高小姐,背着背着怎么变成了背着孙猴子了。原来是孙悟空变成了高小姐,替高小姐和猪八戒成亲了。这其实就是一个代理模式:孙悟空代替了高小姐和猪八戒成亲,既保护了高小姐又完成了八戒非要和高小姐成亲的愿望。这就是非常典型的代理模式保护机制。
在成亲的过程中,八戒并不知道眼前的高小姐是孙悟空,而孙悟空却知道他是代替高小姐和猪八戒成亲的,孙悟空既知道高小姐,又知道猪八戒。
代理模式中有一个非常重要的特点:
对于客户端程序来说,使用代理对象时就像在使用目标对象一样。
代理模式中的角色
代理模式中的角色:
● 代理类:(代理主题)孙悟空
● 目标类:(真实主题)高小姐
● 代理类和目标类的公共接口(抽象主题):客户端(八戒)在使用代理类时就像在使用目标类,
不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口。
代理模式的类图:
动态代理
动态代理:
程序运行阶段,在内存中动态生成代理类的方式叫做动态代理,目的是为了减少代理类的数量,解决代码复用的问题。
JDK动态代理
由JDK的java.lang.reflect包下的Proxy类的newProxyInstance()来实现,只能代理接口。
newProxyInstance() 的三个参数
newProxyInstance源码
package java.lang.reflect;
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
@SuppressWarnings("removal")
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
newProxyInstance()是java.lang.reflect.Proxy中的方法
这是JDK提供的一个类(所以称为JDK动态代理)。主要是通过这个类在内存中生成代理类的字节码。
newProxyInstance 翻译为:新建代理对象
也就是说,通过调用这个方法可以创建代理对象。
本质上,这个Proxy.newProxyInstance()方法的执行,做了两件事:
第一件事:在内存中动态的生成了一个代理类的字节码class。
第二件事:new对象了。通过内存中生成的代理类这个代码,实例化了代理对象。
其中newProxyInstance()方法有三个参数:
● 第一个参数:"ClassLoader loader" 类加载器。
在内存中生成的字节码class文件,加载类就需要类加载器,执行代码得先加载到内存当中,所以这里需要指定类加载器。
并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。
● 第二个参数:"Class<?>[] interfaces" 接口类型。
代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
● 第三个参数:"InvocationHandler h" 调用处理器。
InvocationHandler 被翻译为:调用处理器,是一个接口。
这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。
在调用处理器接口中编写的就是:增强代码。具体需要增强什么还是得自己写的,不可能JDK也能猜到你需要增强什么帮你自动生成。
显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
JDK实现代理的步骤
第一步:创建目标对象
// 创建目标对象:也就是具体的业务实现类
OrderService target = new OrderServiceImpl();
第二步:创建代理对象
创建代理对象:TimerInvocationHandler(),该代理对象需要实现InvocationHandler接口
1. 为什么强行要求你必须实现InvocationHandler接口?
因为一个类实现接口就必须实现接口中的方法。
以下这个方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。
注意:invoke方法不是我们程序员负责调用的,是JDK负责调用的。
2. invoke方法什么时候被调用呢?
当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。
3. invoke方法的三个参数:
invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数。
我们可以在invoke方法的大括号中直接使用。
第一个参数:Object proxy 代理对象的引用。这个参数使用较少。
第二个参数:Method method 目标对象上的目标方法。(要执行的目标方法就是它。)
第三个参数:Object[] args 目标方法上的实参。
invoke方法执行过程中,使用method来调用目标对象的目标方法。
public class TimerInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
public TimerInvocationHandler(Object target) {
// 赋值给成员变量。
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这个接口的目的就是为了让你有地方写增强代码。
//System.out.println("增强1");
long begin = System.currentTimeMillis();
// 调用目标对象上的目标方法
// 方法四要素:哪个对象,哪个方法,传什么参数,返回什么值。
Object retValue = method.invoke(target, args);
//System.out.println("增强2");
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
// 注意这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
return retValue;
}
}
第三步:调用代理对象的代理方法
客户端程序
public class Client {
//客户端程序
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler(target));
// 上面代码通过一个工具类的封装,就简洁了。
OrderService proxyObj = (OrderService) ProxyUtil.newProxyInstance(target);
// 调用代理对象的代理方法
// 注意:调用代理对象的代理方法的时候,如果你要做增强的话,目标对象的目标方法得保证执行。
proxyObj.generate();
proxyObj.modify();
proxyObj.detail();
String name = proxyObj.getName();
System.out.println(name);
}
}
封装ProxyUtil工具类
public class ProxyUtil {
/**
* 封装一个工具方法,可以通过这个方法获取代理对象。
* @param target
* @return
*/
public static Object newProxyInstance(Object target){
// 底层是调用的还是JDK的动态代理。
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler(target));
}
}
CGlib动态代理
CGLIB(Code Generation Library)是一个第三方的开源项目。
是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。
CGLIB需要引入第三方依赖
<!--cglib依赖-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor
代理类
public class TimerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 前面增强
long begin = System.currentTimeMillis();
// 怎么调用目标对象的目标方法呢?
Object retValue = methodProxy.invokeSuper(target, objects);
// 后面增强
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
return retValue;
}
}
客户端程序
public class Client {
public static void main(String[] args) {
// 创建字节码增强器对象
// 这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类。
Enhancer enhancer = new Enhancer();
// 告诉CGLIB父类是谁。告诉CGLIB目标类是谁。
enhancer.setSuperclass(UserService.class);
// 设置回调(等同于JDK动态代理当中的调用处理器。InvocationHandler)
// 在CGLIB当中不是InvocationHandler接口,是方法拦截器接口:MethodInterceptor
enhancer.setCallback(new TimerMethodInterceptor());
// 创建代理对象
// 这一步会做两件事:
// 第一件事:在内存中生成UserService类的子类,其实就是代理类的字节码。
// 第二件事:创建代理对象。
// 父类是UserService,子类这个代理类一定是UserService
UserService userServiceProxy = (UserService) enhancer.create();
// 建议大家能够把CGLIB动态代理生成的代理对象的名字格式有点印象。
// 根据这个名字可以推测框架底层是否使用了CGLIB动态代理
System.out.println(userServiceProxy);
// 调用代理对象的代理方法。
boolean success = userServiceProxy.login("admin", "123");
System.out.println(success ? "登录成功" : "登录失败");
userServiceProxy.logout();
}
}