目录
BeanFactory与ApplicationContext接口
1、Spring概念介绍
什么是Spring
Spring是开源的、轻量级的、企业级的应用集合框架,是包含众多工具方法的IOC容器
2、IOC与DI概念
什么是责任链
使用MVC开发的时候数据在各层之间传递,在业务上形成一个链条
基于责任链模式开发的缺点
当最底层的类被修改时,那么整个调用链的所有类都需要被修改。造成耦合度比较高。
什么是IOC
IOC就是控制反转,处理对象的创建
控制:对类实例化的控制,也就是创建对象这件事
反转:原本由程序员创建实例,现在由Spring进行实例化
什么是DI
DI就是依赖注入,处理属性的赋值
依赖:一个类在另一个类中作为全局属性时
注入:通过Spring容器为自己的属性注册一个值
3、IOC实现原理
IOC实现原理
1.XML解析技术读取配置文件
<bean id="u1" class="com.mdh.demo.User"></bean>
将上面的信息读入程序,一个是对象的id,一个是对象的类的全路径名
2.反射技术实例化对象,放到容器中
(1)首先获得类的字节码
Class clazz = Class.forName("com.mdh.demo.User");
(2)然后通过字节码实例化对象
Object obj = clazz.newInstance();
(3)最后将对象放到一个类似map的集合中
map.put("u1",obj);
3.工厂模式返回Bean对象
public Object getBean(String name){ Object obj = map.get(name); return obj; }
BeanFactory与ApplicationContext接口
BeanFactroy:Bean工厂
ApplicationContext:应用上下文
1.ApplicationContext是BeanFactroy的子类,提供了更多面向实际应用的功能,例如:对国际化的支持,支持资源的访问,支持时间的传播
2.BeanFactory采用延迟加载Bean,在第一次使用getBean方法获取Bean实例时才加载Bean;而ApplicationContext在自身被实例化时一次完成所有Bean创建
3.ApplicationContext类体系结构中,主要的实现类有两个
(1)ClassPathXmlApplicationContext从类路径加载配置文件
(2)FileSystemXmlApplicationContext从文件系统中加载配置文件(如c盘)
使用Spring
1.创建Maven项目,导入依赖包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.2.3.RELEASE</version> </dependency>
2.创建业务对象
3.在resource下配置xml
4.创建启动类或测试类进行使用
public class App { public static void main(String[] args) { //1.1获取到Spring的上下文对象 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //1.2获取到Spring的上下文对象 //BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml"); //BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); //2.1从Spring(上下文)中获取到业务对象,通过bean的id //UserBean user = (UserBean) context.getBean("user"); //2.2从Spring(上下文)中获取到业务对象,通过对象类型 //UserBean user = context.getBean(UserBean.class); //2.3从Spring(上下文)中获取到业务对象,通过id+对象类型 User user = context.getBean("user1",User.class); //3.使用业务对象 System.out.println(user); } }
4、XML实现DI
DI给对象属性赋值
首先我们创建一个User类,并通过lombok添加构造方法和set、get方法
@Data public class User { private int uid; private String username; private String password; }
1.通过set方法
<bean id="u1" class="com.mdh.demo.User"> <property name="uid" value="1"></property> <property name="username" value="张三"></property> <property name="password" value="123456"></property> </bean>
先通过无参构造实例化,再用set方法赋值
2.通过有参构造
<bean id="u2" class="com.mdh.demo.User"> <!-- 此处的参数:index表示形参下标,name表示形参名字,value表示基本类型的值,ref表示自定义的引用类型的值--> <constructor-arg index="0" value="2"></constructor-arg> <constructor-arg index="1" value="李四"></constructor-arg> <constructor-arg index="2" value="123456"></constructor-arg> </bean>
通过有参构造实例化
3.通过p名称空间和c名称空间
<beans xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"> <bean id="u3" class="com.mdh.demo.User" p:uid="3" p:username="王五" p:password="123456"></bean> <bean id="u4" class="com.mdh.demo.User" c:uid="4" c:username="王六" c:password="123456"></bean> </beans>
实际上是前两种的简化写法,注意要在beans里加约束
4.注入空值和特殊符号
<bean id="u1" class="com.mdh.demo.User"> <!-- 空值 --> <property name="uid"> <null></null> </property> <!-- 特殊符号使用转义字符 <为< >为> &为&--> <property name="username" value="&"></property> <!-- CDATA原样设置 --> <property name="password"> <value> <![CDATA[内容]]> </value> </property> </bean>
5.bean引用另一个bean
创建一个Student类,其属性中有Score类,并通过lombok添加构造方法和set、get方法
@Data public class Score{ private int java; private int cpp; } @Data public class Student { private String name; private Score score; }
(1)引用外部bean
<bean id="score1" class="com.mdh.demo.Score"> <property name="java" value="100"></property> <property name="cpp" value="100"></property> </bean> <bean id="stu1" class="com.mdh.demo.Student"> <property name="name" value="张三"></property> <property name="score" ref="score1"></property> </bean>
(2)引用内部bean
<bean id="stu2" class="com.mdh.demo.Student"> <property name="name" value="李四"></property> <property name="score"> <bean class="com.mdh.demo.Score"> <property name="java" value="100"></property> <property name="cpp" value="100"></property> </bean> </property> </bean>
(3)级联引入bean
<bean id="score3" class="com.mdh.demo.Score"></bean> <bean id="stu3" class="com.mdh.demo.Student"> <property name="name" value="王五"></property> <property name="score" ref="score3"></property> <property name="score.java" value="100"></property> <property name="score.cpp" value="100"></property> </bean>
原理为用反射调用get方法,获取对象之后,再赋值
6.注入集合
例如有这样一个类
@Data public class Students { private String[] books; private List<String> bookList; private Set<String> bookSet; private Map<String,String> bookMap; private List<Book> bookList2; } public class Book { private String name; private String author; }
配置xml
<bean id="students1" class="com.mdh.demo.Students"> <!-- array数组注入 --> <property name="books"> <array> <value>java</value> <value>cpp</value> </array> </property> <!-- list集合注入 --> <property name="bookList"> <list> <value>java</value> <value>cpp</value> </list> </property> <!-- set集合注入 --> <property name="bookSet"> <set> <value>java</value> <value>cpp</value> </set> </property> <!-- map集合注入 --> <property name="bookMap"> <map> <entry key="java" value="bite"></entry> <entry key="cpp" value="bite"></entry> </map> </property> <!-- list对象集合注入 --> <property name="bookList2"> <list> <ref bean="b1"></ref> <ref bean="b2"></ref> </list> </property> </bean> <!-- 声明多个book对象 --> <bean id="b1" class="com.mdh.demo.Book" p:name="java" p:author="bite" ></bean> <bean id="b2" class="com.mdh.demo.Book" p:name="cpp" p:author="bite" ></bean>
7.工厂方式获取bean
<bean id="bookFactory" class="com.mdh.factory.BookFactory"></bean>
实现FactoryBean接口
public class BookFactory implements FactoryBean<Book> { @Override public Book getObject() throws Exception { return new Book(); } @Override public Class<?> getObjectType() { return null; } }
在getBean时,需要用Book对象接收
什么是工厂模式
某个特定的类型对象由工厂统一生产,以达到对对象的统一管理
一些Bean标签
1.lazy-init:true懒加载(延迟加载)false立即加载
2.init-method:设置对象初始化阶段调用的方法
3.destory-method:设置对象销毁阶段调用的方法
4.scope:设置作用域
5、bean作用域、生命周期与自动装配
bean作用域
前两个是SpringCore项目都可以用,后三个是基于Web的项目可用
1.singleton
描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例
2.prototype
描述:每次对该作⽤域下的Bean的请求都会创建新的实例
3.request
描述:每次http请求会创建新的Bean实例,类似于prototype
场景:⼀次http的请求和响应的共享Bean
4.session
描述:在⼀个http session中,定义⼀个Bean实例
场景:⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息
5.application
描述:在⼀个http servlet Context中,定义⼀个Bean实例
场景:Web应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息
bean的生命周期
所谓的⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程
1.通过构造器创建bean实例(执行构造器)
2.为bean赋值(执行set方法)
3.把bean实例传递给bean的前置处理器方法
4.初始化bean(调用bean的初始化方法)
5.把bean实例传递给bean的后置处理器方法
6.bean的获取(getBean方法)
7.容器关闭并销毁bean(调用销毁方法)
通知处理器的使用
<!-- 配置后置通知处理器 --> <bean id="myBeanProcessor" class="com.mdh.demo.MyBeanProcessor"></bean>
对于所有bean都会影响
public class MyBeanProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //Object bean 要干预的bean //String beanName bean的id return null; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return null; } }
bean的自动装配
自动注入bean,例如
public class Score{ private int java; private int cpp; } public class Student { private String name; private Score score; }
autowire属性控制自动将容器中的对象注入到当前对象的属性上
byName根据目标id值和属性值注入,要保证当前对象的属性值和目标对象的id值一致
byType根据类型注入,要保证相同类型的目标对象在容器中只有一个实例
<bean id="score" class="com.mdh.demo.Score"></bean> <bean id="student" class="com.mdh.demo.Student" autowire="byName"></bean>
6、引入外部属性配置文件
在resources目录下用.properties结尾的文件来配置
username="root" pa55word="123456"
在xml里就可以引入
<!-- 读取jdbc.properties配置文件 --> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${username}"></property> <property name="password" value="${password}"></property> </bean>
7、注解方式管理bean
注解方式实现IOC
@Component【组件类】
该注解有四个子注解
(1)@Controller【控制器】
(2)@Service【业务逻辑类】
(3)@Repository【数据持久类】
(4)@Configuration【配置类】
第一步:在xml文件里开启注解扫描
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 设置为true,则使用默认扫描过滤器,可以识别并包含所有五个注解 --> <!-- 设置为false,则自己设置扫描哪些注解 --> <context:component-scan base-package="com.mdh.demo" use-default-filters="false"> <!-- include 控制只扫描哪些注解--> <!-- exclude 控制不扫描哪些注解--> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> </beans>
第二步:在类上添加注解,让spring容器给我们创建bean实例并存储于容器中
@Component("user1") //可以命名id, //如果不命名,默认是类首字母小写 public class User { private int uid; private String username; private String password; }
注解方式实现DI
1.对象注入
例如在service层调用dao层
使用Autowired注解注入,如果有多个实现类,则可以配合Qualifier注解使用
也可以使用Resource实现
@Service public class UserServiceImpl implements UserService{ @Autowired @Qualifier("userDaoImplB") //@Resource(name="userDaoImplB") private UserDao userDao; @Override public void add(){ userDao.add(); } }
2.注入基础数据类型(8+String)
@Value("zhangsan") private String name; @Value("1") private Integer age;
也可以通过外部属性配置文件注入
首先配置properties文件
name="张三" age=1
其次配置xml
<context:property-placeholder location="classpath:*.properties"></context:property-placeholder>
然后就可以使用了(可能出现中文乱码问题)
@Value("${name}") private String name; @Value("${age}") private Integer age;
配置类方式实现IOC和DI
可以通过ComponentScan配置扫描路径
也可以通过PropertySource注入外部属性配置文件
@ComponentScan(basePackages = "com.mdh") @PropertySource("classpath:aaa.properties") public class SpringConfig{ }
在使用时,就从此类的字节码文件中获取spring上下文对象
public class App { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("SpringConfig.class"); User user = context.getBean("user1",User.class); System.out.println(user); } }
8、代理模式
什么是代理模式
通过代理对象访问目标对象,这样可以在目标对象的基础上增强额外的功能,如添加权限、访问控制和审计等功能
相当于中介或者律师
代理模式的作用
在不修改或者没有办法修改原有代码的情况下,增强对象功能,使用代理对象代替原来的对象去完成功能进而达到拓展功能的目的
静态代理
静态代理中代理类与被代理类都需要实现同一接口,这就说明一个静态代理类只能代理一个类,并且还要事先知道我们要代理哪个类才能写代理类,如果我们有其他类还想使用代理那就必须再写一个代理类。然而在实际开发中我们是可能有非常多的类需要被代理的,并且事先我们可能并不知道我们要代理哪个类,所以如果继续使用静态代理反而会增加许多的工作量,并且效率低下,代码复用率也不高
动态代理
动态代理可以针对一些不特定的类或者一些不特定的方法进行代理,我们可以在程序运行时动态的变化代理的规则,代理类在运行时才创建的代理模式称为动态代理,这种情况下,代理类并不是在Java代码中定义好的,而是在程序运行时根据我们在Java代码中的“指示”动态生成的
Proxy动态代理:JDK动态代理(面向接口)
cglib动态代理:第三方动态代理(面向父类)
JDK动态代理
面向接口
1.一定要有接口和实现类的存在,
2.代理对象只能增强接口中的定义的方法
3.代理对象只能读取到接口中方法上的注解
public class Test { public static void main(String[] args) { Dinner dinner = new Person("张三"); ClassLoader classLoader = dinner.getClass().getClassLoader(); Class[] interfaces = dinner.getClass().getInterfaces(); InvocationHandler invocationHandler = new InvocationHandler() { @Override //当我们让代理对象调用任何方法时,都会触发invoke方法执行 //(1)Object proxy,代理对象,这里是dinnerProxy //(2)Method method,被代理的方法,这里是eat方法或者drink方法 //(3)Object[] args,被代理方法运行时的实参,这里是“饭”或者“茶” public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //接收方法的返回值 Object res = null; //我们这里对eat方法进行增强,对drink方法不增强 if (method.getName().equals("eat")){ System.out.println("饭前洗手"); //执行原有的eat方法 res = method.invoke(dinner,args); System.out.println("饭后刷牙"); }else{ //只用执行原本的drink方法 res = method.invoke(dinner,args); } return res; } }; //通过Proxy动态代理获得一个代理对象,在代理对象中,对某个方法进行增强 //(1)ClassLoader loader,被代理对象的类加载器,这里为dinner的类加载器 //(2)Class<?>[] interfaces,被代理对象所实现的所有接口,这里为Dinner接口 //(3)InvocationHandler h,执行处理器对象,专门用于定义增强规则,在这里写增强的策略 Dinner dinnerProxy = (Dinner)Proxy.newProxyInstance(classLoader,interfaces,invocationHandler); dinnerProxy.eat("饭"); dinnerProxy.drink("茶"); } } @AllArgsConstructor class Person implements Dinner{ private String name; @Override public void eat(String food) { System.out.println(name+"正在吃"+food); } @Override public void drink(String food) { System.out.println(name+"正在喝"+food); } } interface Dinner{ void eat(String food); void drink(String food); }
cglib动态代理
面向父类
1.和接口没有直接关系
2.不仅仅可以增强接口中定义的方法,还可以增强类中定义的方法
3.可以读取父类中方法上的所有注释
public class Test2 { public static void main(String[] args) { Student student = new Student("张三"); //1.获得一个Enhancer对象 Enhancer enhancer = new Enhancer(); //2.设置父类字节码 enhancer.setSuperclass(student.getClass()); //3.获取MethodInterceptor对象,用于定义增强规则 MethodInterceptor methodInterceptor = new MethodInterceptor() { //Object o,生成之后的代理对象,就是我们接下来的studentProxy //Method method,父类中原本要执行的方法,也就是Student里的eat方法 //Object[] objects,方法调用时传入的实参数组 //MethodProxy methodProxy,子类中重写父类的方法,也就是studentProxy里的eat方法 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //接收方法的返回值 Object res = null; //我们这里对eat方法进行增强,对drink方法不增强 if (method.getName().equals("eat")){ System.out.println("饭前洗手"); //执行原有的eat方法 res = methodProxy.invokeSuper(o,objects); System.out.println("饭后刷牙"); }else{ //只用执行原本的drink方法 res = methodProxy.invokeSuper(o,objects); } return res; } }; //4.设置MethodInterceptor enhancer.setCallback(methodInterceptor); //4.获得代理对象 Student studentProxy = (Student)enhancer.create(); //5.使用代理对象完成功能 studentProxy.eat("饭"); studentProxy.drink("茶"); } } @NoArgsConstructor @AllArgsConstructor class Student { private String name; public void eat(String food){ System.out.println(name+"正在吃"+food); } public void drink(String food){ System.out.println(name+"正在喝"+food); } }
9、AOP的概念和原理
什么是AOP
AOP是面向切面编程,一般可以帮助我们在不修改现有代码的情况下,对程序的功能进行拓展,往往用于实现日志处理,权限控制,性能检测,事务控制等
AOP实现原理
实现原理就是动态代理,在有接口的情况下,使用JDK动态代理,没有接口的情况下,使用cglib动态代理
AOP的一些术语
1.连接点 Join Point
可以被增强的方法
2.切入点 Pointcut
实际被增强的方法
3.通知 Advice
实际增强的功能
4.目标对象Target
被代理的对象
5.切面Aspect
功能相关的advice方法放在一起声明成的一个Java类
6.织入 Weaving
创建代理对象并实现功能增强的声明并运行的过程
AOP五个注解
1.@Before:前置通知
切点方法执行之前先执行的功能
2.@After:后置通知
方法执行之后要增强的功能
3.@AfterReturning:返回通知
切点方法return之后增强的功能,切点方法如果出现异常则不执行
4.@AfterThrowing:异常通知
切点方法出现异常就执行,不出现异常就不执行
5.@Around:环绕通知
切点方法之前和之后都能进行功能的增强,可以控制切点方法的执行
环绕通知的返回值必须是Object,在通知中必须要将切点方法继续向上返回
10、AOP注解方式的实现
注解方式实现AOP
1.导入依赖包
<!-- spring切面包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.5</version> </dependency> <!--aop联盟包--> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency>
2.通过@Aspect注解声明切面
@Aspect @Component //如果有多个增强类对同一方法进行增强,可以通过此注解设置优先级 //数字越小,代理位置越靠近注入位置,优先级越高 @Order(1) public class DaoAspect { //定义一个公共切点,可以指向方法或者接口 @Pointcut("execution(* com.mdh.mvc.dao.UserDao.add(..))") public void addPointCut(){} @Before("addPointCut()") public void methodBefore(JoinPoint joinPoint){ //可以获取方法执行的参数 Object[] args = joinPoint.getArgs(); System.out.println(Arrays.toString(args)); } @After("addPointCut()") public void methodAfter(JoinPoint joinPoint){ } @AfterReturning(value = "addPointCut()",returning = "res") public void methodAfterReturning(JoinPoint joinPoint,Object res){ //可以接收方法的返回值 System.out.println(res); } @AfterThrowing(value = "addPointCut()",throwing = "e") public void methodAfterThrowing(Exception e){ //可以获取异常信息 System.out.println(e.getMessage()); } @Around("addPointCut()") public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //切点方法在这里执行 Object res = proceedingJoinPoint.proceed(); return res; } }
JoinPoint对象
JoinPoint对象封装了SpringAOP中切面方法的信息
方法名 功能 Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 Object[] getArgs(); 获取传入目标方法的参数列表 Object getTarget(); 获取被代理的对象 Object getThis(); 获取代理对象
ProceedingJoinPoint对象
是对象JoinPoint的子接口,只用于@Around的切面方法中,添加了两个方法
Object proceed();执行目标方法
Object proceed(Object[] varl);传入的新的参数去执行目标方法
通过配置类
//声明是一个配置类 @Configuration //要扫描的包 @ComponentScan("com.mdh") //是否自动生成切面代理对象 @EnableAspectJAutoProxy(proxyTargetClass = true) public class SpringConfig{ }
11、事务
事务的概念
事务指的是一个操作序列,该操作序列中的多个操作,要么都做,要么都不做,是一个不可分割的单位
事务的四大特性
1.原子性
事务是最小的执行单位,不允许分割,事务的原子性确保动作要么全部执行,要么全部不执行
2.一致性
执行事务的前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的
3.隔离性
各个事务的执行互不干扰
4.持久性
一个事务被提交后,他对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响
事务的并发问题
1.脏读
事务B修改数据后回滚,期间事务A读到的数据就是脏读
时间点 事务A 事务B 1 开启事务A 2 开启事务B 3 读 A = 10 4 写 A = 20 5 读 A = 20 6 事务回滚 2.不可重复读
事务B修改数据导致事务A前后两次读取不一致
时间点 事务A 事务B 1 开启事务A 2 开启事务B 3 读 A = 10 4 写 A = 20 5 读 A = 20 3.幻读
一个事务两次读取,中间进行了插入操作
时间点 事务A 事务B 1 开启事务A 2 开启事务B 3 size = N 4 插入一条数据 5 size = N+1
事物的隔离级别
隔离级别 脏读 不可重复读 幻读 未提交读 × × × 提交读 √ × × 可重复读 √ √ × 可串行化 √ √ √ 可串行化:一个事务执行完,另一个事务才能执行
12、日志和测试
日志的级别
过滤规则:比日志设置级别低的信息就会忽略掉
trace:轻微痕迹,级别最低;
debug:调试日志;
info:普通信息,默认级别;
warn:警告,不影响使⽤,但需要注意的问题;
error:错误信息;
fatal:致命,代码异常导致程序退出执⾏的事件,级别最高;
使用日志框架
添加依赖
<!-- spring切面包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.5</version> </dependency> <!--aop联盟包--> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency>
在resource目录下创建log4j2.xml文件
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="DEBUG"> <Appenders> <!-- OUT打印黑色字体,ERR是红色 --> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <!-- 打印日志级别 --> <Root level="debug"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration>
junit5单元测试
导入依赖包
<!-- junit5 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.0</version> <scope>test</scope> </dependency>
添加测试代码
//配置扫描的xml文件 @SpringJUnitConfig(locations = "classpath:applicationContext.xml") public class Test{ @Autowired private UserBean userBean; @Test public void testUserBean(){ } }