不使用MVC的时候系统存在的缺陷
一个Servlet都负责了那些工作?
- 负责了接收数据
- 负责了核心的业务处理
- 负责了数据表中的CRUD
- 负责了页面的数据展示
- …
分析银行转账项目存在那些问题?
- 代码的复用性太差。(代码的重用性太差)
- 因为没有进行“职能分工”,没有独立组件的概念,所以没有办法进行代码复用
- 代码和代码之间耦合度太高,扩展力太差。
- 耦合度太高,导致了代码很难扩展。
- 操作数据库的代码和业务混杂在一起,很容易出错,编写代码的时候容易出错,无法专注业务逻辑的编写。
MVC架构模式
1、系统为什么要分层?
- 希望专人干专事。各司其职。职能分工要明确。
- 这样可以让代码耦合度降低扩展力增强,组件的可复用性增强。
2、系统架构中有一个非常著名的模式:MVC结构模式
-
M(Model:业务模型层,数据处理/业务处理)
- Dao层:专门为做数据的CRUD抽取出来的一个类,Dao层又叫做数据持久层。
- service层:专门抽取处理的一个业务类,只写业务,在service中调用Dao层对象。在service类中有一个私有的Dao属性(数据库访问对象)满足has a的关系,service对象通过数据库访问对象操作数据库的中表中的数据。
-
V(View:视图/展示):负责展示的组件
- jsp
- html
- …
-
C(Controller:控制器)
-
在Controller中调用service对象。在Controller类中有一个service私有的属性(业务对象)满足has a的关系,controller对象通过通过调用业务对象完成用户的业务。
- 在service中调用Dao。
-
在Controller中调用view。
-
-
C:是核心,是控制器,是司令官。
-
M:业务模型层,处理业务,处理数据的一个秘书。
-
V:负责页面展示的一个秘书。
-
MVC一个司令官调度两个秘书。去做这件事。
3、Model包含什么?
- pojo、bean、domain:封装数据用的。
- service:抽取出来的业务处理类
- dao:抽取出来的数据持久化层。
4、三层架构和MVC的关系:
- 表示层/展示层/web层:展示层包括控制器和view,包含MVC架构模式中的V和C
- Controller:MVC中的控制器
- 一般是Servlet
- view:MVC中的视图层
- JSP
- HTML
- …
- Controller:MVC中的控制器
- 业务逻辑层:MVC中的Model中的业务处理层(service)
- service:处理业务逻辑的,一般是一个XxxService类名
- 持久化层:MVC中的Model中的持久化层(Dao)
- Dao:将数据持久化保存进数据库
5、持久化层中包含的技术?
- JDBC
- MyBatis
- …
6、SSM:spring、springMVC、MyBatis
- Spring:项目大管家,负责整个项目所都对象的创建以及维护对象和对象之间的关系。
- SpringMVC:将MVC架构模式体现的非常完美。在这个框架的基础之上进行开发,一定是用了MVC架构模式的。
- MyBaits:持久层框架
7、展示层和业务逻辑层和持久化层的调用关系
- 展示层调用业务逻辑层,
- 业务逻辑层调用持久化层,
8、什么是Dao
- DAO的英文全称是:Data Access Object(数据访问对象)
- DAO实际上是一种设计模式,属于JavaEE的设计模式之一。(不是23中设计模式)
- DAO只负责数据库表的CRUD,没有任何业务逻辑在里面。
- 没有任何业务逻辑,只负责表中数据增删改查的对象,有一个特殊的称谓Dao对象(Dao对象是由Dao类实例化得到的)。
- Dao类的命名规范:
- 如果这个Dao对象专门用来处理t_user表的话,Dao类可以叫做UserDao
- 如果这个Dao对象专门用来处理t_student表的话,Dao类可以叫做StudentDao
- 一般情况下:一张表会对应一个DAO类,DAO对象专门用来CRUD的对象。
- pojo对象:POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans。
- 以下的三个对象说的是一个东西,看个人习惯:都是为了封装数据的对象。
- pojo对象
- Bean对象
- domain对象:领域模型
- 以下的三个对象说的是一个东西,看个人习惯:都是为了封装数据的对象。
- 使用Dao对象对数据库中的数据进行增删改查操作,一般先将数据库中的数据封装成一个Java对象这样便于Java程序对数据的操作。
- 一般一个表对应一个JavaBean(封装数据的Java对象,又叫做pojo对象)。
- JavaBean的属性建议使用引用数据类型,因为从数据库中查出来的可能返回一个null,如果是基本数据类型,将null赋值给基本数据类型就会抛异常,尽量使用基本类型的包装类。
- 数据库中的一个字段对应JavaBean中的一个属性。
- JavaBean的属性建议使用引用数据类型,因为从数据库中查出来的可能返回一个null,如果是基本数据类型,将null赋值给基本数据类型就会抛异常,尽量使用基本类型的包装类。
- 一条数据对应一个JavaBean对象,一条记录对象一个pojo对象。
- 有的人也会把这种专门封装数据的对象,称为领域模型对象(domain对象)
- 一般一个表对应一个JavaBean(封装数据的Java对象,又叫做pojo对象)。
- Dao类中的方法名很固定:
- insert
- deleteBy…
- update
- selectBy…
- selectAll
9、什么是Service
- service翻译为:业务。是MVC架构模式中Model中的service层。
- service类的命名规范:XxxService
- 学生的业务类就叫做:StudentService
- 用户的业务类就叫做:UserService
- 业务类的方法命名规范:
- 方法命名一定要体现出要处理的是什么业务。(方法名和业务挂钩)
- 一个业务类中包含多个处理业务的方法,一般一个业务对应一个方法。
- 在该类中编写纯业务代码。(只专注业务,不写别的。不和其他代码混合在一块)
- 事务一定实在Service层进行控制的。
- 一般是一个业务方法对应一个完整的事务。
try语句的资源自动管理机制
-
JDK7 特性之 try-with-resource 资源的自动管理
-
该try-with资源语句是try声明了一个或多个资源声明。一个资源是程序与它完成后,必须关闭的对象。该try-with资源语句确保每个资源在发言结束时关闭。
-
代码:
@Test public void test2() throws IOException { String filepath = "D:\\gui-config.json"; try ( //任何实现的java.lang.AutoCloseable对象(包括实现的所有对象)java.io.Closeable都可以用作资源。 FileReader fileReader = new FileReader(filepath); BufferedReader br = new BufferedReader(fileReader) ) { String curline = null; while ((curline = br.readLine()) != null) { System.out.println(curline); } } } // FileReader 和 BufferedReader 均实现了 AutoCloseable 接口
ThreadLocal源码分析
-
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
- 相当于一个Map集合一个线程只能对应一份数据。
-
一个ThreadLocal就是一个大Map集合,集合的key就是当前线程对象,集合的value就是我们向集合对象中存储的数据。key不用我们自己存储,key是由JVM来管理的,这样就可以把线程和数据关联起来。
-
ThreadLocal类的常用方法:
T get(); // 返回当前线程对应的数据,T是泛型类型,就是往ThreadLocal中绑定的数据的类型 void set(T value); // 设置当前线程对应的数据,绑定数据,一个线程只能绑定一份数据,这份数据可以是数组、集合等,如果再绑定一次数据第二次绑定的数据会覆盖第一次绑定的数据。 void remove(); // 删除当前线程对应的数据
不同功能的类在不同的包
- 三层架构:层与层之间应当使用接口进行衔接。
- 三层架构的目录规范:
- pojo包:中存放普通JavaBean,用来保存数据的Java类【这个包也可以使用domain包或者bean包替代】
- web包:中存放控制器Controller,用来调度两个秘书的司令官
- web目录:webapp的根目录中存放view,展示层的页面
- dao包:dao包中存放以下两项内容【持久化层】
- 数据库访问的接口
- impl包:中存放实现了数据库访问接口的实现类
- service包:中存放以下两项内容【业务处理曾】
- 业务处理的接口
- impl包:中存放了实现了业务处理接口的实现类
使用了MVC架构模式之后存在的两大问题
- 在service层控制事务,service层的事务控制代码看着有点别扭,以后能不能不写?可以使用动态代理机制解决这个问题。
- 目前虽然面向接口编程了,但是并没有完全解决对象和对象之间的依赖关系,怎么办?以后使用spring和IoC容器来解决这个问题。
- 对象的创建我不管了,交给spring容器
- 对象和对象之间的关系我也不管了,交给spring容器
动态代理机制
-
什么是代理
- 代购
- 中介
- 以中介为例,中介和代理做的事情是一致的
- 出去打工为什么要找中介?
- 因为中介是专业的,可选的工作种类多。
- 也可能工厂不让个人去找工厂,中介在中间起到一个担保的作用。对个人对厂商多是友好的。
- 出去打工为什么要找中介?
- 以中介为例,中介和代理做的事情是一致的
- …
- 等等这些都是代理
-
什么是代理模式?
- 代理模式是指,为其他对象提供了一种代理以控制对这个对象的访问。
- 在某些情况下一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。
- 换句话说,是用代理对象,是为了在不修改目标对象的基础上,增强业务逻辑。
-
使用代理模式的作用
- 功能增强:常用的作用是为了在原有的功能之上增加新的功能
- 控制访问
-
实现代理的方式
- 静态代理
- 描述:
- 代理类是自己手工实现的,自己创建的一个Java类,表示代理类
- 同时你所要代理的目标类是确定的
- 目标对象和代理对象必须同时实现同一个接口
- 特点:
- 实现简单
- 容易理解
- 缺点:
- 当目标类增加了,代理类可能也需要成倍的增加。代理类数量过多。
- 当接口中功能增加了,或者修改了,会影响到众多的实现类,那么厂家类和代理类都需要修改。影响比较多。修改的比较多,出错的纪律比较大。
- 描述:
- 动态代理
- 优点
- 在静态代理目标类很多的时候,可以使用动态代理,避免静态代理的缺点。
- 动态代理中目标类即使很多,代理类的数量可以很少,当修改了几口中的方法时,不会影响代理类。
- 概念:
- 在程序执行的过程中,使用JDK的反射机制,创建代理类对象,并动态指定要代理的目标类。
- 动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术
- 在程序的执行过程中,使用jdk的反射机制创建代理对象,并且动态指定要代理的目标类。
- 换句话说:动态代理是一种创建java对象的能力,让你不用把代理类手动写出来就能创建代理对象
- 优点
- 静态代理
-
动态代理的实现【动态代理分为两种】
- JDK动态代理:使用Java反射包中的类和接口实现动态代理的功能。
- 反射包:java.lang.reflect里面有三个类:InvocationHandler,Method,Proxy
- cglib动态代理:cglib是第三方的工具库创建代理对象。(Code Generic Library)字节码生成库
- cglib的原理是继承,cglib通过继承目标类,创建它的子类,在子类中重写父类中的同名方法,实现功能增强。
- 因为cglib是继承,重写方法,所以要求目标类不能是final修饰的,方法不能是final修饰的。
- cglib的要求目标类比较宽松,只要能继承就可以了(而jdk动态代理还需要实现某个特定的接口)。cglib在很多的框架中使用,比如spring。mybatis都有使用。
- JDK动态代理:使用Java反射包中的类和接口实现动态代理的功能。
-
jdk动态代理的实现
-
反射,Method类,表示方法。类中的方法。通过Method可以执行某个方法
-
jdk动态代理的实现:反射包中有三个类:InvocationHandler,Method,Proxy
-
InvocationHandler(译为:调用处理程序)接口:就有一个方法invoke()
-
invoke()表示代理对象要执行的功能代码,你的代理类要完成的功能就写在invoke()方法中。
- 代理类完成的功能
- 调用目标代码,执行目标方法的功能
- 功能增强,在目标方法调用时,增加功能。
- 代理类完成的功能
-
-
-
// 方法源码
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
// proxy:jdk创建的代理对象。
// method:目标类中的方法,jdk提供method对象的,代理类强转为接口,当调用接口的a方法method就是a方法对象,当调用接口中的b方法的时候,method就是b方法对象。
// args:目标类中方法的参数,jdk提供的
// 返回值Object对应代理类的代理方法的返回值【就是目标类返回的类型,这个invoke方法的返回值也应该是什么类型的数据,如果目标对象没有返回值那么invoke方法的返回值就可以随意指定了】
- InvocationHandler接口:表示你的代理类要干什么。
- 怎么用:
1. 创建类实现接口InvocationHandler
2. 重写Invoke()方法,把原来静态代理中代理类要完成的功能写到这里来。
2. Method类:表示方法的,确切的说就是目标类中的方法。
- 作用:通过Mehtod可以执行某个目标类中的方法,Method.invoke();method.invoke(目标对象,方法的参数)
- method.invoke()是用来执行目标方法的。
3. Proxy类:核心对象,创建代理对象。之前创建对象都是new类的构造方法现在我们使用Porxy类的方法,代替new的使用。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) ;
// loader:类加载器,负责向内存中加载代理类的类加载器。【对于不同的类加载器有不同的优先级,对于用户类(程序员写的类)的类加载器,都是同一个类加载器,随便取一个类加载器就可以】
// interfaces:目标对象实现的接口,也是反射获取的。目标对象实现了什么接口,代理类就是先什么接口
// h:我们自己写的,代理类要完成的功能。
// 返回值时Object类型的,就是目标对象的代理对象,代理对象肯定能强转成目标类实现的接口的类型,因为目标类和代理类实现了同一个接口
-
实现动态代理的步骤:
-
创建接口,定义目标类要完成的功能
-
创建目标类实现接口
-
创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
- 调用目标方法
- 增强功能
-
使用Proxy类的静态方法,创建代理对象,并把返回值转为接口类型.
-
-
总结:
-
什么是动态代理机制:使用jdk动态代理机制,创建对象的能力,创建代理类的对象.而不用你创建类文件.不用写Java文件.
- 动态:在程序执行时,调用jdk提供的方法才能创建代理类对象.【jdk帮我们生成的代理类也实现了目标类实现的接口】
- jdk动态代理必须有接口,目标类必须实现接口,没有接口时,需要使用cglib动态代理
-
动态代理能做什么:可以在不改变原来目标方法功能的前提下,可以在代理类中增强的功能.
-
代理类似过滤器:在执行目标方法之前执行一些增强功能的代码,在目标方法之后执行一些增强功能的代码。
-
jdk为我们动态生成的这个代理类,继承了java.lang.reflect.Proxy类,实现了目标类实现的所有接口,所以目标类需要实现一个接口才能使用jdk的动态代理,如果没有实现接口只能使用cglib动态代理。【调用Proxy的newProxyInstance方法获取代理对象】
- jdk生成的代理类头
- public final class $Proxy0 extends Proxy { … }
- jdk生成的代理类头
-
动态代理:
- 为目标类实现的接口中所有的方法规定了统一的增强的功能,都写在invoke方法中
- 优点:代理的目标类是可以在程序的运行阶段动态调整的,【所有的方法的增强功能都写在incoke方法中,虽然不像静态代理那样目标类中一个方法在代理类中就有一个增强方法,但是可以通过method对象判断调用的是目标类中的哪个方法,从而执行不同的逻辑来完成给不同的方法定义不同的增强功能】
-
静态代理:
- 为每一个方法都声明了增强的功能,确定是代理类代理的对象都是确定的,
- 缺点:代理类的代码得手动写出来。【但是增强规则可以为每一个方法都写一个不同的规则】
-
不是只有接口才能实现动态代理,只是jdk使用接口这种方式实现的动态代理机制,我们不需要关心动态代理的代理类是怎么生成的我们只要会调用jdk中的方法完成动态代理的功能即可,这只是一种常用的方法,当然用别的方法也可以实现动态代理机制。
-
什么是前置通知?什么是后置通知?
- 前置通知:调用目标方法之前执行的代码或者操作叫做“前置通知”
- 后置通知:调用目标方法之后执行的代码或者操作叫做“后置通知”
-
InvocationHandler中的invoke方法的返回值应该与目标类中的目标方法的返回值一致。【InvocationHandler类中的invoke方法就相当于代理类中的代理方法,在代理方法中完成目标方法的调用和功能的增强】
-
在一个类中想调用另一个类中的方法的时候,一般都将另一个类的对象声明为私有的成员属性/或者静态属性,
- 为什么是成员属性?
- 以前我们写的是测试程序,所以我们想调用某个类的某个方法的时候,直接在main方法中调用实例化某个类的对象在调用某个类的方法。真正写业务的时候是将另一个类的对象定义为私有的成员,以便于当前类的所有方法都能使用这个类中的方法。
- 不管调用代理对象的任何一个方法都会被方法拦截处理器拦截,然后就会调用InvocationHandler类中的invoke方法。
- 所以输出代理对象的时候就会调用invoke方法,因为输出对象的时候自动调用toString方法,所以就会执行InvocationHandler中的invoke方法。
-
-
总结2
-
当目标类中有返回值的时候,
- 那么InvocationHandler类中的invoke方法的返回值必须是能转换成目标类的返回值类型的对象。不然就会抛出ClassCastException。
-
当目标类没有返回值的时候
- 那么InvocationHandler类的invoke方法的返回值可以返回任意值。【这样做没有意义,一般目标类返回什么值,这里就返回什么值】
-
为什么在用sout输出代理对象的时候会调用InvocationHandler类中的invoke方法,
-
因为jdk为我们生成的代理类toString方法底层调用了InvocationHandler类中的invoke方法。
-
代理类toString源码:
public final String toString() { /* 因为sout输出的时候会调用对象的toString方法,所以在代理对象被输出的时候,会调用jdk为我们生成的这个代理对象的toString方法。通过这个代理对象的源码发现,底层会调用InvocationHandler类的invoke方法,然后将invoke方法的返回值强转成String类型的。强转失败会抛出ClassCaseException异常。 */ toString方法首先会执行InvocationHandler的invoke方法,所以 return (String)super.h.invoke(this,m2,(Object[])null); }
-
调用接口中的任何方法都会被invoke方法拦截,都会先执行invoke方法。
//假设接口中的方法是 public Student query(Long id); //那么jdk帮我们生成的这个代理类的query方法就是这样的 public final Student query(Long var1) { /* 第一个参数就是当前的代理对象(jdk帮我们传入的),第二个参数就是要执行的方法(我们将代理对象转换成接口类型,调用哪个方法,m2就是哪个方法,也是jdk帮我们传入的),第三个参数是传入的参数,我们面向接口掉方法的时候,给方法传进去的是什么样的参数,第三个参数就是我们传入的这个参数。 */ //面向接口调用query方法的时候,因为实际的对象是代理对象,会调用代理对象的query方法,代理对象的该方法会调用到InvocationHandler类中的invoke方法,所以invoke方法会拦截接口中所有的方法 // 将调用invoke方法的返回值强转成接口中定义的返回值类型,如果强转不成功,抛出异常,所以invoke方法的返回值一般都和接口中定义的返回值一样。换句话说就是和目标类的返回值类型一致。 return (Student)super.h.invoke(this,m4,new Object[]{var1}); }
-
jdk帮我们生成的这个动态代理类的方法都是final修饰的。
-
-
-