Linux
- Linux基础这部分,看看笔记
- 在Linux上安装JDK,解压tomcat,安装MySQL
- 项目放到
tomcat/webapps
下即可 - 记得关闭防火墙
systemctl stop firewalld
- 虚拟机还是virtualbox吧,玛德
- 项目放到
Maven
- 有关Maven的部分可以大致看看笔记
- 在Windows使用IDEA写项目,最后部署到Linux服务器
- 使用骨架archetype创建webAPP项目,需要补上后端代码目录和测试目录
- webapp下存放前端代码,测试部分的resources不是必须的
- 在pom中配置好依赖,刷新maven,就会自动下载到本地仓库
- 仓库之间的关系
- 使用tomcat7提供运行环境,添加
plugin
,完整pom<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>mvnwebdemo</artifactId> <version>1.0</version> <packaging>war</packaging> <name>mvnwebdemo Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.2.1</version> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>mvnwebdemo</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/demo</path> </configuration> </plugin> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
- 想查什么依赖可以去远程仓库找: https://mvnrepository.com/
- maven帮我们管理用到的jar包,其他的和之前无异,还是用Servlet(@+service)
- maven构建项目,每一步都有命令,但必须在pom所在目录执行
- maven有传递性和依赖性
Redis
- 现在网站的特点:高并发、高容量、高效存储、高扩展、高可用
- nosql—>Redis,nosql还有其他产品
- Redis的详细学习笔记,这里做一些补充
- Redis是键值对存储,但是这个值有很多种类型!
- 关于Redis**单线程(进程)**模型的理解
- 因为非关系型数据库不存在约束,所以单线程多进程的集群非常合适
- 具体问题结合场景理解,就不瞎猜了
事务管理
- Redis服务类似前端的Ajax引擎;处于应用和数据库之间,提供高效访问
- Redis是单线程的,所有的命令都会先放入一个队列中,但一个Redis服务器可能同时连接多个客户端,命令的执行顺序是不确定的,所以有redis事务,将一串命令当做整体加入队列,一起执行
- 使用事务,
multi
和exec
命令127.0.0.1:6379> multi OK 127.0.0.1:6379> set bookname java QUEUED 127.0.0.1:6379> set bookname c++ QUEUED 127.0.0.1:6379> set bookname html QUEUED 127.0.0.1:6379> exec 1) OK 2) OK 3) OK
哨兵模式
- 给集群分配一个站岗的,对Redis系统的运行情况监控,它是一个独立进程
- 功能:监控,选举主机
- 环境准备:一主两从,启动任一从机时,启动哨兵模式
- Redis需要编译安装,安装后指定位置下会产生一个
bin
目录,这个机器就是redis server了 - 我们复制两个安装后的redis,编辑配置文件,启动,当做从机(多进程模拟多机器,这也是发挥多核优势的方法)
bind ip port 6379/6380/6381 daemonize yes replicaof masterip masterport # 从机需要配置
- Redis需要编译安装,安装后指定位置下会产生一个
- 启动服务器并查看主从信息
cd Redis/ ./bin/redis-server redis.conf # 从机也这样启动 ./bin/redis-cli -h ip -p 6379 # 连接客户端 xxx> info replication
- 在任一从机bin目录下新增文件 sentinel.conf
sentinel monitor mastername ip 6379 1 # mastername 监控主数据的名称,自定义 # 1:最低通过票数 选举时只要有1骗票就可以成为主机
- 将日志写入指定文件,然后启动哨兵
./redis-sentinel ./sentinel.conf >sent.log & # 后台单独进程运行 # 启动前确保主从正常运行 ./redis-server sentinel.conf --sentinel
- 虽然启动报错,但是主机宕机仍然能选举成功?
- kill掉master的进程,6381被选举,
keys *
查看数据都在
- 查看日志sent.log:
- 查看配置文件,已经被重写:
- 注:如果需要再次启动哨兵,需要删除
myid
唯一标示(最好是根据目前情况,重新配置一遍,不然就冲突了) - Ubuntu20.4切换到root用户需要:
passwd root
重置密码 - virtualbox使用host-only设置网络:
cd /etc/netplan
,默认是NAT-DHCP模式(没有下图ethernets部分),但不能连secureCRT所以换一下 - 参考链接,host-only不能上Internet,还要在主机网络连接中配置 VirtualBox Host-Only Network
- 改为桥接模式,相当于和主机平起平坐,需要和主机的Internet在同一网段;可以再添加一块网卡搞成NAT模式(对应VMnet8)
- 注:如果需要再次启动哨兵,需要删除
- 和主机平起平坐,需要虚拟机网卡使用DHCP自动获取IP,将主机视为交换机,然后搭建网桥br0接入主机
- 还需要桌面点击一下Wired
- secureCRT连不上Ubuntu20.4,秘钥不对,只能升级到8.5.3,烦死了
搭建集群
- 集群是扩展Redis单线程的最好方式,同时让多台(master)机器处理指令,整个集群当做一个redis server(注意区分集群和主从)
- 搭建集群至少需要三台主机(master),每台再配一个从机,至少要六台机器
- 编写脚本启动集群
cd 7001 ./bin/redis-server ./redis.conf cd .. cd 7002 ./bin/redis-server ./redis.conf cd .. cd 7003 ./bin/redis-server ./redis.conf cd .. cd 7004 ./bin/redis-server ./redis.conf cd .. cd 7005 ./bin/redis-server ./redis.conf cd .. cd 7006 ./bin/redis-server ./redis.conf cd ..
- 进入某一台主节点启动集群
./redis-cli --cluster create 192.168.100.68:7001 192.168.100.68:7002 192.168.100.68:7003 192.168.100.68:7004 192.168.100.68:7005 192.168.100.68:7006 --cluster-replicas 1
- 主从自动分配好了,一主带一从
- 输出以上信息证明集群启动成功
- 连接集群测试,从任何一个节点连接都可以(集群对外是一个整体,内部分摊请求)
- 跳转到另外的节点上存储
- 连接从节点也可写入,但一定会跳到它对应的主节点写入
- 三个主节点保存的key(槽)互通
- 底层hash结构,键唯一
- 这里的客户端就是我们的响应浏览器请求的服务器,槽数据怎么查?三个节点同时查(发给不同机器)!
- 命令的执行顺序怎么保证呢?因为键是唯一的,所以同一键值的设置只会在同一节点,后来的命令肯定后执行(相当于先查后改)
- 使用jedis连接集群
public class TestRedis { @Test public void test01() throws IOException { // 创建一连接,JedisCluster对象,在系统中是单例存在 Set<HostAndPort> nodes = new HashSet<HostAndPort>(); nodes.add(new HostAndPort("192.168.100.68", 7001)); nodes.add(new HostAndPort("192.168.100.68", 7002)); nodes.add(new HostAndPort("192.168.100.68", 7003)); nodes.add(new HostAndPort("192.168.100.68", 7004)); nodes.add(new HostAndPort("192.168.100.68", 7005)); nodes.add(new HostAndPort("192.168.100.68", 7006)); JedisCluster cluster = new JedisCluster(nodes); // 执行JedisCluster对象中的方法,方法和redis指令一一对应。 cluster.set("test1", "test111"); String result = cluster.get("test1"); // 键存在没关系,覆盖! System.out.println(result); //存储List数据到列表中 cluster.lpush("site-list", "java"); cluster.lpush("site-list", "c"); cluster.lpush("site-list", "mysql"); // 获取存储的数据并输出 List<String> list = cluster.lrange("site-list", 0 ,2); for(int i=0; i<list.size(); i++) { System.out.println("列表项为: "+list.get(i)); } // 程序结束时需要关闭JedisCluster对象 cluster.close(); System.out.println("集群测试成功!"); } }
- 关闭集群就是kill所以进程?
- shutdown掉某个节点,启动后加不回来?从机顶上了(7001没了)
./bin/redis-cli -c -h 192.168.100.68 -p 7001 shutdown
缓存雪崩
- 一般分为虚拟机缓存,分布式缓存,数据库缓存
- 一般查找缓存的步骤:
- 缓存雪崩:原本访问Redis缓存的请求,因为缓存到期失效,同一时间高并发访问数据库,把数据库搞坏
- 解决方案:
- 缓存失效后,通过加锁或者队列来控制请求数据库的线程数量
- 比如某个key失效了,但只允许查询次key的其中一个线程查询数据库和写缓存,其他线程等待
- 虽然缓解了数据库压力,但降低了系统的吞吐量
- 分析用户的行为,不同的key设置不同的过期时间,让缓存失效的时间点尽量均匀
- 缓存失效后,通过加锁或者队列来控制请求数据库的线程数量
缓存穿透
- 用户查询数据,在数据库没有,自然在缓存中也不会有,用户每次查询这个key的时候,在缓存中找
不到,每次都要去数据库再查询一遍,然后返回null - 解决方案:
- 如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴
- 或者存成null,这部分key单独设置缓存区域,每次查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑
- 在给对应的key存放真值的时候,需要先清除对应的之前的空缓存
缓存击穿
- 对于热点key,一旦失效会引起高并发访问数据库
- 解决方案:
- 使用锁,单机用synchronized,lock等,分布式用分布式锁
- 缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存
- 和雪崩的区别在于:雪崩时多个key同时失效,击穿是热点key失效;但都会引起高并发访问数据库
分布式锁
- 锁的根本目的是实现资源访问的有序,处理竞争
- 锁主要分为三种
- 线程锁:一个进程中(也可以说是在同一个JVM中,一个JVM一个进程)的多个线程,依靠共享内存实现加锁;(比如synchronized是共享对象头,显示锁Lock是共享某个变量(state))
- 进程锁:进程之间是可以通信的,也是可以竞争相同的其他资源的;本质是各进程中线程的竞争,这就是一个机器上多个JVM之间了,这就要到共享资源出设置锁(超出进程内,多个进程间的事情不好把控)
- 分布式锁:每个机器一个JVM进程,多个机器(进程),同时访问某个机器
- 分布式锁可以基于很多种方式实现,比如zookeeper、redis
- 基本原理相同:用一个状态值表示锁,对锁的占用和释放通过状态值来标识
- 那么这个状态值的更新算法就很重要了!
- Redis中的两个重要命令
SETNX key value
,若此key 已经存在,则 SETNX 不做任何动作(SET if Not eXists),设置成功返回 1GETSET key value
,先获取key对应的value值,若不存在则返回nil,然后将旧的value更新为新的value
- 分布式锁原理
- 同一时刻只能有一个进程获取到锁(某个共同约定的key的操作权),执行加锁原语:
SETNX lock.foo <current Unix time + lock timeout + 1>
,这里设置了超时时间- 如果 SETNX返回 1 ,说明客户端(某进程)已经获得了锁,之后可以通过
DEL lock.foo
来释放锁(不删除就不能设置新值,即使过期) - 如果 SETNX返回 0 ,可以进入一个重试循环
- 如果 SETNX返回 1 ,说明客户端(某进程)已经获得了锁,之后可以通过
- 释放锁:锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁
- 流程大致如图
- 同一时刻只能有一个进程获取到锁(某个共同约定的key的操作权),执行加锁原语:
- 问题:检测到某个持有锁的进程超时了,其他进程通过
DEL
方法获取锁,然后SETNX,这就存在一个竞态,删除这个锁的人就能获取这个锁(DEL权利太大) - 所以,其他进程要使用
getset
方法,DEL只能由持有锁的人使用!!!(解铃还须系铃人)- 先GET,如果过期了,执行:
GETSET lock.foo
,此时拿到的时间戳如果仍然是超时的,那就说明如愿以偿拿到锁了(没人重置过期时间)- 如果有人快一步执行了上面的操作,那么拿到的时间戳肯定是个未超时的值,需要再次等待或重试
- 也就是说getset这个命令会判断过期,然后决定是否把锁给你;但是你执行getset会重置持有锁进程的过期时间(如果一直有人getset岂不是永不过期?)
- 还有个问题
- 持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作
- 因为可能客户端因为某个耗时的操作而挂起,操作完的时候锁因为超时已经被别人获得,这时就不必解锁了
Spring框架
- 接下来学SSM框架中的spring
- 框架是不完整的,是为扩展而设计的;和实现某个功能的类库不是一回事
- Spring具有控制反转(IoC)和面向切面(AOP)两大核心
- Java Spring 框架通过声明式方式进行事务管理,提高开发效率和质量
- Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力
体系结构
- spring框架提供大概20个模块
- 打开官网,一大堆spring相关,也就是spring全家桶,我们先从最原始的
Spring Framework
开始
IoC
- Ioc—Inversion of Control,即“控制反转”
- 创建对象不是程序员来new了,而是交给spring框架完成
- 类似tomcat容器自动帮我们创建Servlet对象
- 不是什么技术,而是一种设计思想
- 后面的spring项目都放在一个empty工程下,每个项目通过new module创建
- 新建module,maven添加spring依赖,创建bean、配置文件、测试类
public class Test01 { @Test public void test01() { String configPath = "application.xml"; // 根据文件中配置的bean将需要的对象创建好 ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configPath); // 也可以使用BeanFactory,但是不常用 Roy roy = (Roy)applicationContext.getBean("roy1"); // xml中定义 } }
- 看一下配置文件,这个
.xsd
文件限定了bean的属性<?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"> <!-- 相当于:springMap(id, "对象") 将指定的对象放入容器中--> <bean id="roy1" class="vip.roykun.pojo.Roy"></bean> <!-- 可以放入非自定义对象--> <bean id="date" class="java.util.Date"></bean> </beans>
- 设置scope;指定初始化和销毁方法;是否懒加载,区别如下
- 使用有参构造创建对象
- 使用工厂方法创建对象(静态/非静态),都不懒
<?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"> <!--相当于:springMap(id, "对象"), 将指定的对象放入容器中--> <!--singleton:默认值,单例:在容器启动的时候就已经创建了对象,而且整个容器只有为一个 的一个对象 lazy-init默认是false 不懒--> <!--lazy-init=false 不延迟创建对象,容器加载的时候立即创建 true:默认不加载,使用对象的时候才去创建对象--> <bean id="roy1" class="vip.roykun.pojo.Roy" scope="singleton" lazy-init="true" init-method="init" destroy-method="destroy"></bean> <!--prototype:多例,在使用对象的时候才创建对象(getBean就是使用),每次都创建新的对象 lazy-init默认是true,懒--> <bean id="roy2" class="vip.roykun.pojo.Roy" scope="prototype"></bean> <!--使用有参构造创建 实体类要有对应的方法--> <bean id="roy3" class="vip.roykun.pojo.Roy"> <constructor-arg name="name" value="roy3"/> <constructor-arg name="age" value="18"/> </bean> <!--使用工厂静态方法创建对象--> <bean id="roy4" class="vip.roykun.pojo.MyFactory" factory-method="factory"></bean> <!--使用工厂非静态方法创建--> <bean id="factory" class="vip.roykun.pojo.MyFactory"></bean> <bean id="roy5" factory-bean="factory" factory-method="instance"></bean> <!--可以放入非自定义对象--> <bean id="date" class="java.util.Date"></bean> </beans>
public class MyFactory { public static Roy factory() { System.out.println("static"); return new Roy(); } public Roy instance() { System.out.println("instance"); return new Roy("roy4", 18); } }
public class Test01 { @Test public void test01() { String configPath = "application.xml"; // 根据文件中配置的bean,将需要的对象创建好,并放入容器 ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configPath); Roy roy1 = (Roy)applicationContext.getBean("roy1"); // xml中定义 // Roy roy2 = (Roy)applicationContext.getBean("roy2"); // xml中定义 // 非自定义对象 Date date = (Date) applicationContext.getBean("date"); System.out.println(date); // 获取容器中的对象个数 int beanCount = applicationContext.getBeanDefinitionCount(); System.out.println("容器中对象个数:"+beanCount); // 容器中对象名称 String[] beanNames = applicationContext.getBeanDefinitionNames(); for (String name : beanNames) { System.out.println(name); } // 会触发destroy方法 } }
- 这部分就是IoC最原始的实现方式
DI
- dependency injection 依赖注入
- IoC是一种思想,DI是它的具体实现,我们上面的factory-bean其实就是这个意思
- 对象的创建,由容器完成
- 初始化属性也由容器完成,称为注入
- 通过配置完成,不用直接导包来new,就松耦合了!
- 属性的注入有三种方式,例如我们给service注入dao(service类中有dao这个属性)
set
方法注入(常用)- 使用构造方法注入
- 自动注入:byName(属性名称存在)、byType(属性类型唯一)
<?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="dao" class="vip.roykun.dao.RoyDao"></bean> <!--set注入--> <bean id="service" class="vip.roykun.service.RoyService"> <!--name=属性名,引用了dao--> <property name="royDao" ref="dao"></property> </bean> <!--使用构造方法注入属性--> <bean id="service2" class="vip.roykun.service.RoyService"> <constructor-arg name="royDao" ref="dao"></constructor-arg> </bean> <!--自动注入:byName--> <bean id="royDao" class="vip.roykun.dao.RoyDao"></bean> <!--我需要一个royDao属性,正好上面bean的id是这个名字 缺点:必须有和属性同名的声明--> <bean id="service3" class="vip.roykun.service.RoyService" autowire="byName"></bean> <!--自动注入:byType 缺点:这个xml中只能有一个royDao类型的声明,但现在有 dao和royDao,于是报错--> <bean id="service4" class="vip.roykun.service.RoyService" autowire="byType"></bean> </beans>
- 注意区别属性和引用属性
- 例如上面的,普通属性可以使用
name
和value
设置 - 引用属性使用
ref
public class TestDi { @Test public void test01() { String configPath = "di.xml"; // 根据文件中配置的bean,将需要的对象创建好,并放入容器 ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configPath); RoyService royService = (RoyService) applicationContext.getBean("service"); royService.add(); RoyService royService2 = (RoyService) applicationContext.getBean("service2"); royService2.add(); RoyService royService3 = (RoyService) applicationContext.getBean("service3"); royService3.add(); } }
注解
- 通过注解实现IoC,有点像web的路由注解
- 上面的DI:类自动创建,属性的初始化有三种方式
- 这里将类的创建通过注解交给容器,属性的初始化也通过注解
- 先看类的创建,
Component
是万能注解,但最好各层分开;最后需要xml中指定扫描 value
参数就是xml中beanid
的值,方便从容器中get到// dao层用Repository @Repository(value = "royDao") public class RoyDao { RoyDao() { System.out.println("roy dao--------"); } public void add() { System.out.println("dao add-----"); } }
// 这个value如果不写,就是类名,但小写字母开头 @Service public class RoyService { private RoyDao royDao; // service调用dao // servlet最后调用service public void add() { royDao.add(); System.out.println("service add----"); } RoyService() { System.out.println("roy service -------"); } }
@Repository(value = "royDao") public class RoyDao { RoyDao() { System.out.println("roy dao--------"); } public void add() { System.out.println("dao add-----"); } }
- 这个xml文件要注意了,需要增加
context
约束<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="vip.roykun.dao"></context:component-scan> <context:component-scan base-package="vip.roykun.service"></context:component-scan> <context:component-scan base-package="vip.roykun.controller"></context:component-scan> </beans>
- 这个context可以直接写父包
vip.roykun
,或者将多个要扫描的包写在一个context,分号隔开
- 这个context可以直接写父包
- 再看属性的初始化,通过在属性上加
@Value
注解实现;传入的值都是String,可以加到set方法上@Component("teambean") public class Team { @Value("Allen") private String name; private Integer age; public Team() { } public Team(String name, Integer age) { this.name = name; this.age = age; } @Override public String toString() { return "Team{" + "name='" + name + '\'' + ", age=" + age + '}'; } public void setName(String name) { this.name = name; } @Value("18") public void setAge(Integer age) { this.age = age; } }
- 可以直接加在set方法上
- 再看引用属性的初始化,使用
@Autowired()
实现byType注解require
参数默认是true,表示引用必须存在
- 或者再配合
@Qualifier
实现byName注解@Service public class TeamService { @Autowired() // required = false @Qualifier("teamDao1") private TeamDao teamDao; public TeamService() { System.out.println("team service"); } public TeamService(TeamDao teamDao) { this.teamDao = teamDao; } }
- 和DI的类似,如果指定名字,这里用到dao层,需要那里的注解设置id
@Repository("teamDao1") public class TeamDao { public TeamDao() { System.out.println("team dao"); } }
- 引用属性的初始化还可以通过
@Resoource
,Spring提供了对 jdk中@Resource注解的支持,默认按名称的方式注入@Controller public class TeamServlet { @Resource(name = "teamService1") private TeamService teamService; public TeamServlet() { System.out.println("team servlet"); } public TeamServlet(TeamService teamService) { this.teamService = teamService; } public void add() { teamService.add(); } }
- 默认如果name没有匹配的就找type;如果指定了name/type,就只会按照指定的参数注入,找不到就会报错
- 所以在service层要加上
@Service(value = "teamService1")
- 小结
- spring IoC的实现有三类,但都需要resources/下配置xml实现
- 原始方式:直接定义bean,指定类的路径,将创建对象、初始化属性的权利交给容器;可以使用工厂方法完成一切
- DI方式:直接定义bean,指定类的路径,将创建对象的权利交给容器,但属性和引用属性的设置有三种方式
- 注解方式:不直接定义bean,给各层类加注解,将创建对象的权利交给容器,属性的初始化用
@Value
,引用属性的初始化有两种方式;xml中指定要扫描的包即可
- 下面学AOP