Skip to content

三层架构

f337ce8f6f78febda6adace777d1f354_MD5 由于单一职责原则,这里把三个职责集中在一起,是不合适的. 三层架构: dbefec19c3b6aed29829147c1bc029c7_MD5 三层架构依次调用 5c34d5f4359c1b9783c657a27923c745_MD5

统一接口

DAO并不总是从文件访问数据,还可能从数据库 网络访问.因此,会有很多DAO层. 我们一般会给所有DAO层提供同一的访问接口.即面向接口编程. service层一般也同理. 我们的架构变成了这样 b14ea33f09be807c9c8317324892f583_MD5 其中,dao层提供统一的接口UserDao,而UserDaoImpl是一个实现类. tip:这里包名的命名impl 实现类的命名UserDaoImpl都是固定的规范. service同理 6af39f154a1ec9631a3c27f482b0694f_MD5

SpringbootDemo1当中的代码抽取出来: 245fb5109933d219ad0bf81dfb99099a_MD5 634d92ab16840ece73791df82d71d57d_MD5 911fa1a0ddaf1649173b0d8c446d1ee9_MD5 48a88f6861072661215aa995e18d4afa_MD5 注意了,这里我们发现lines需要从dao中获取,因此我们需要有一个dao成员.这个成员类型应该是接口类型,这样可以利用多态. 最后: a112e372993d9387870281b7ab175d06_MD5

高内聚低耦合 IOC&DI

最好能解耦.刚才的代码耦合关联度还是太高了 刚才层与层都是通过写死成员来关联的.这很不好. 我们可以创建一个容器.把一个UserServiceImpl对象放到一个容器中,让UserController从容器当中获得对象. 这样,如果我们想修改UserCOntroller选取的Service,UserController本身的代码不用动.

如何把UserService放到容器中 -- 控制反转 如何让UserController获得容器中的数据 -- 依赖注入 d4d3fe1e16e77b99fba196c7e3b894bf_MD5 IOC是Spring的第一大核心.IOC容器也叫Spring容器 为什么叫控制反转?之前,应该是service自己创建对象,放到容器里,控制权在service里.现在控制权反过来了. 为什么叫依赖注入?以前是固定写死了Controller依赖于某个Service,现在的依赖则是我们注入的.

代码实现

59b55c18091c7e9743e277cf29b3c38e_MD5 8d6f40374c25eb60a37b9d37379eab4b_MD5 加上Component注解之后:就会把这个类交给IOC管理 a8099f9ab8fe93c1c0a5eab534f92e82_MD5 e0f5efb5bbd7845f819bd05c711d4920_MD5 加上autowired之后,在运行时,程序会自动从容器中查询找到该类型的Bean对象,并且赋值给这个成员 b97e96d45f8e294d91c4d7244f9e0ef9_MD5 0b55b52355dc1f6d4fcd08948651bd85_MD5

IOC使用细节

505faf35665e89d82f29d0080712dbe8_MD5 下面三个是对coomponent的封装,也就是包含了Component. 常见的工具类配置类不能归属于这三层架构,因此还是会使用Component

代码

b098e3bcb9f449dd3bb10dd3627b731a_MD5 点开看一下,可见就是封装: c9e8f4d1126624f3b3fe434ddddfab3a_MD5 6c0b77839b59d82be80d9e89cc3882f4_MD5 Controller其实不用专门加,因为restController已经依赖了Controller了. 015eeb69ccc8b73c8ec9961003de0b81_MD5 一些问题: e037e572e6d014a4950d44192587acfc_MD5 而Service和repository可以被component替代.但建议还是写明. 如果后面用来mabatis这样的框架,那么就不用repository了.(后面再说)

细节

bean的名字

IOC中如何区分每个bean?其实每个bean都有它的名字.默认的名字就是类名首字母小写. 控制台中通过actuator监控就能看到我们定义的bean c490283d7b33c7794c3b0412954b8869_MD5 我们也可以指定名字: 952f164dca54d9e06764272ab364d4fa_MD5 这个value就表示有一个属性value,添加属性就能重命名: d8f42781c05022cc6d5c2c1a41ba8d4a_MD5 但通常我们不会指定名字.

组件扫描

4a6bf02e8a6032e64c9a13c080fedac2_MD5 组件扫描要扫描到四大注解才能正确生效. 8bf0b6a44fa2ab4c031cee691beb8d5e_MD5 如果没有扫描到,会在控制台报错.

DI细节

三种方式

2e35f3487c14898f12573d038209d902_MD5 userService是一个属性.因此这叫属性注入. 6174fc628c598818a5f452702c84af0e_MD5 也叫构造器注入.成员变量一般被final修饰,表示不可变. 如果当前类的构造函数只有构造器一个,那么autowired也可以省略. c504e407921f16683c2ab0286984cd32_MD5 这样写之后,类创建对象时,会自动调用setter方法,来进行初始化. 980a12db5ec52525049744c73dc3e643_MD5 事实上,本质就是需要时会自动把bean对象赋值给成员/传给构造函数/传给setter

优缺点

属性注入

13ba381e7ae7abfe2a19482da493516c_MD5 因为对于外界来看,由于没有getset方法,那么就根本不知道Controller依赖了Service.(而后两者都可以从public的方法的参数获知有这层依赖关系) 另外,由于没有getset方法,实际上底层是通过反射来给userService赋值的,这破坏了封装性.

构造器注入

60a169edd3574739ca7c4234f0356624_MD5 由于有final,故比较安全.

setter注入

d7477db8d6406951da66991f7d37f716_MD5

官网推荐使用构造器输入,但是真实企业开发中往往用属性注入.

按类型注入

5d054bd3c4208b1b6c2fa5d1880ca112_MD5 也就是说,autoWired会默认到IOC中查找类型为UserService的bean来进行注入,它的依据是类型,所以说叫"按类型注入" 那么:

如果符合条件的bean有多个怎么办?

比如我实现了多个Service类: 新建一个Service2类: 969640866a3565993f390c7645a305e1_MD5 会发现报错: 582b31b5a913cc0abea2e8c4bbdd9f7f_MD5 因此结论是会多个报错.

如何处理?

99c333e227ee63dc94d2a3fa493b5ded_MD5 4a5d28622113c9f7d5c4ac6d2f41c847_MD5 primary:指定哪个类优先 qualifier/resource:指定用哪个类注入. 注意,这两个要写的不是类名,而是bean的名字. tip:注解的顺序不影响,即autowired和qualifier哪个在上不重要. resource不是Spring提供的,而是javaee规范中提供的. 03f4411e14ef82ed4201b3cdb09aa009_MD5