一、Spring的概述
- 什么是Spring框架?
Spring就是把每个bean(实体类)bean的关系全部交给第三方容器进行管理,这个容器就是Spring,整个对象的生命周期都是交给Spring进行管理的,Spring的核心是:IOC(控制反转)、DI(依赖注入)、AOP(面向切面编程)。
DI和IOC的区别:SpringIOC负责创建对象和处理对象依赖关系,DI仅仅负责对象赋值的依赖注入。
Spring框架是可以解决对象创建以及对象之间依赖关系的一种框架,且可以和其他框架一起使用,如:Spring与Struts, Spring与hibernate(是起到整合(粘合)作用的一个框架)。 - Spring提供的一站式解决方案:
Spring整合/集成任何框架,无非都是一个概念,都是把集成的对象交给Spring容器管理。- Spring Core:spring的核心功能: IOC容器, 解决对象创建及依赖关系
- Spring Web:Spring对web模块的支持。
- 可以与struts整合,让struts的action创建交给spring
- SpringMVC模式(MVC是一种软件架构思想,不是设计模式,设计模式是做重构代码,提高代码复用的。)
- Spring DAO:Spring 对jdbc操作的支持 【JdbcTemplate模板工具类】
- Spring ORM:spring对orm的支持:
- 既可以与hibernate整合【session】
- 也可以使用spring对hibernate操作的封装
- Spring AOP:切面编程
- SpringEE:spring 对javaEE其他模块的支持
二、Spring环境搭建
-
在pom.xml文件中添加maven坐标:
<dependencies> <!-- 引入Spring-AOP等相关Jar --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.3</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.3</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.10</version> </dependency> </dependencies>
-
创建需要交给Spring管理的注入类UserEntity.java:
package chauncy.entity; public class UserEntity { private String name; private Integer age; public UserEntity(){ System.out.println("UserEntity无参构造函数被执行"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
-
创建spring.xml配置文件:
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userEntity" class="chauncy.entity.UserEntity" /> <!-- 测试Spring的xml是否允许配置beanId重复 --> <bean id="userEntityRepeat" class="chauncy.entity.UserEntity" /> </beans>
-
创建测试类SpringTest.java:
import org.springframework.context.support.ClassPathXmlApplicationContext; import chauncy.entity.UserEntity; public class SpringTest { public static void main(String[] args) { // 1.先加载spring容器 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); System.out.println("Spring容器被加载"); // 2.使用bean的id查找对象 UserEntity userEntity1 = (UserEntity) applicationContext.getBean("userEntity"); UserEntity userEntity2 = (UserEntity) applicationContext.getBean("userEntity"); // 判断对象在Spring中是否单例存在,比较两个对象的内存地址是否一样,若一样则证明Spring默认是单例的,线程不安全。 System.out.println(userEntity1 == userEntity2); } }
注:Spring配置文件中beanId不允许重复,如果分别使用配置文件和注解方式定义重复的beanId则允许。
三、Spring 加载过程
- Spring默认是单例还是多例?
Spring默认是单例,在容器加载的时候会去初始化创建bean对象。
通过Springbean的无参构造函数,创建出不同对象,判断不同对象的内存地址是否相同,若相同表示spring默认为单例。
通过Spring配置文件为bean增加scope=“prototype” 属性,设置该bean为多例方式加载,不会在容器启动的时候创建,在用到时单独创建:<bean id="userEntity" class="chauncy.entity.UserEntity" scope="prototype" />
- Spring作用域:
- singleton 作用域:
当一个bean的 作用域设置为singleton, 那么Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。换言之,当把 一个bean定义设置为singleton作用域时,Spring IOC容器只会创建该bean定义的唯一实例。这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都 将返回被缓存的对象实例,这里要注意的是singleton作用域和GOF设计模式中的单例是完全不同的,单例设计模式表示一个ClassLoader中 只有一个class存在,而这里的singleton则表示一个容器对应一个bean,也就是说当一个bean被标识为singleton时 候,spring的IOC容器中只会存在一个该bean。 - singleton Prototype(原型):
prototype作用域部署的bean,每一次请求(将其注入到另一个bean中,或者以程序的方式调用容器的 getBean()方法)都会产生一个新的bean实例,相当与一个new的操作,对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责,容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法,而对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。 清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。(让Spring容器释放被singleton作用域bean占用资源的一种可行方式是,通过使用 bean的后置处理器,该处理器持有要被清除的bean的引用。) - singleton request(web项目会用到,用的不多):
request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效。 - singleton session(web项目会用到,用的不多):
session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效。
- singleton 作用域:
四、SpringIOC 容器
SpringIOC容器,是spring核心内容。
作用: 创建对象 & 处理对象的依赖关系。
- IOC容器创建对象的方式:
- 调用无参数构造器:
<!-- SpringIOC创建对象方式1:调用无参构造函数 --> <bean id="userEntity1" class="chauncy.entity.UserEntity"></bean>
- 带参数构造器:
<!-- SpringIOC创建对象方式2:调用有参构造函数 --> <bean id="userEntity2" class="chauncy.entity.UserEntity"> <constructor-arg name="name" value="ChauncyWang"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> </bean>
- 工厂创建对象:
工厂类:package chauncy.entity; public class ObjectFactory { /** * * @methodDesc: 功能描述(实例工厂方法) * @author: ChauncyWang * @param: @return * @createTime: 2019年4月22日 上午10:57:29 * @returnType: UserEntity */ public UserEntity getUserEntity(){ System.out.println("ObjectFactory->getUserEntity()"); return new UserEntity("Chauncy",19); } /** * * @methodDesc: 功能描述(静态工厂方法) * @author: ChauncyWang * @param: @return * @createTime: 2019年4月22日 上午10:57:29 * @returnType: UserEntity */ static public UserEntity getStaticUserEntity(){ System.out.println("ObjectFactory->getUserEntity()"); return new UserEntity("Chauncy",20); } }
- 工厂类,静态方法创建对象:
<bean id="staticFactoryUserEntity" class="chauncy.entity.ObjectFactory" factory-method="getStaticUserEntity"> </bean>
- 工厂类,非静态方法创建对象:
<!-- SpringIOC创建对象方式3:工厂创建对象(不太常用):1>实例工厂2>静态工厂 --> <bean id="objectFactory" class="chauncy.entity.ObjectFactory"></bean> <bean id="userEntity3" factory-bean="objectFactory" factory-method="getUserEntity"> </bean>
- 工厂类,静态方法创建对象:
- 调用无参数构造器:
- DI依赖注入:
Spring中,如何给对象的属性赋值? 【DI, 依赖注入】- 通过构造函数:
<bean id="userEntity2" class="chauncy.entity.UserEntity"> <constructor-arg name="name" value="ChauncyWang"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> </bean>
- 通过set方法给属性注入值(使用<property>标签必须要有set方法):
package chauncy.service; import chauncy.dao.UserDao; public class UserService { private UserDao userDao; public void add(){ System.out.println("业务逻辑层。。。"); userDao.add(); } public void setUserDao(UserDao userDao) { System.out.println("UserService的setUserDao方法"); this.userDao = userDao; } }
<!-- 使用set方法DI注入 --> <bean id="userDao" class="chauncy.dao.UserDao"></bean> <bean id="userService1" class="chauncy.service.UserService"> <property name="userDao" ref="userDao"></property> </bean>
- p名称空间(spring3.0以上版本才支持,用的不多,底层也是使用set方法给属性注入):
<!-- 使用P名称空间DI注入 --> <bean id="userService2" class="chauncy.service.UserService" p:userDao-ref="userDao"> </bean>
- 自动装配(了解):
有些字段不需要注入,也会帮我们注入进去,所以不推荐使用。自动装配的方式有:no(默认设置,没有自动装配)、byName(由属性名自动装配)、byType(由属性数据类型自动装配)、constructor(与byType类似,应用于构造函数)。<!-- 自动装配DI注入 --> <bean id="userService3" class="chauncy.service.UserService" autowire="byName"> </bean>
- 注解:
SpringBoot中应用较多,SpringBoot简化XML配置,全部使用注解代替,而且是微服务架构模式。<!-- 如果要使用注解,首先要开启注解权限,扫描注解 --> <context:component-scan base-package="chauncy.*"></context:component-scan>
package chauncy.service; import javax.annotation.Resource; import org.springframework.stereotype.Service; import chauncy.dao.UserDao; //等同于在XML文件中声明<bean id="userService" class="chauncy.service.UserService"></bean> @Service public class UserService { /** * @Resource 默认以属性名称userDao,去找bean对象 */ @Resource private UserDao userDao; public void add(){ System.out.println("业务逻辑层。。。"); userDao.add(); } public void setUserDao(UserDao userDao) { System.out.println("UserService的setUserDao方法"); this.userDao = userDao; } }
package chauncy.dao; import org.springframework.stereotype.Repository; //标识注入DAO层,默认是以<bean id="userDao" class="chauncy.dao.UserDao"></bean>这种方式配置到Spring容器中 @Repository public class UserDao { public void add() { System.out.println("数据库访问层。。。 flag:"+flag); } }
import org.springframework.context.support.ClassPathXmlApplicationContext; import chauncy.entity.UserEntity; import chauncy.service.UserService; public class SpringTest4 { public static void main(String[] args) { // 1.先加载spring容器 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring4.xml"); System.out.println("Spring容器被加载"); // 2.使用bean的id查找对象 UserService userService = (UserService) applicationContext.getBean("userService"); userService.add(); System.out.println(userService); } }
- @Resource与@Autowired的区别:
@Autowired是sprig框架自带,默认先以类型查找,再以名称查找(名称需要自定义),@Resource是jdk1.6才有(新创建的maven工程默认是jdk1.5),默认先以名称查找,名称查找不到再以类型进行查找。证明方法:改变bean的id、class,查看是否报错。 - 如果XML和注解混合使用,则允许定义相同beanId(单独在xml中定义相同beanId会报错),在实际使用的时候使用的是xml定义的同名bean,证明方法:
<!-- XML和注解混合使用,定义相同beanId,验证是否会报错,若不报错,XML和注解谁先执行 --> <bean id="userDao" class="chauncy.dao.UserDao"> <property name="flag" value="true"></property> </bean>
package chauncy.dao; import org.springframework.stereotype.Repository; //标识注入DAO层,默认是以<bean id="userDao" class="chauncy.dao.UserDao"></bean>这种方式配置到Spring容器中 @Repository public class UserDao { //验证XML和注解混合使用定义重名bean,是否报错,先执行哪个 private boolean flag; public void add() { System.out.println("数据库访问层。。。 flag:"+flag); } public void setFlag(boolean flag) { System.out.println("This is flag:"+flag); this.flag = flag; } }
- 创建对象以及处理对象依赖关系,相关的注解:
@Component 指定把一个对象加入IOC容器
@Repository 作用同@Component; 在持久层使用
@Service 作用同@Component; 在业务逻辑层使用
@Controller 作用同@Component; 在控制层使用
@Resource 属性注入
- @Resource与@Autowired的区别:
- 通过构造函数:
五、AOP面向切面编程
- 应用场景:权限方面、日志处理、控制方面、事务原理(aop)。注:事务的作用:保证数据一致性。
springAOP使用cglib动态代理,其底层依托于asm框架(字节码控制)实现。AOP编程的作用就是把代码进行分离。
模拟事务原理底层的aop实现:package chauncy.service; import org.springframework.stereotype.Component; @Component public class AOPService { //开启事务 public void begin(){ System.out.println("事务开启。。。"); } //提交事务 public void commit(){ System.out.println("事务提交。。。"); } }
package chauncy.service; import javax.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import chauncy.dao.UserDao; //等同于在XML文件中声明<bean id="userService" class="chauncy.service.UserService"></bean> @Service public class UserService { /** * @Resource 默认以属性名称userDao,去找bean对象 */ @Resource private UserDao userDao; @Autowired private AOPService aopService; public void add(){ aopService.begin(); System.out.println("业务逻辑层。。。"); userDao.add(); aopService.commit(); } public void setUserDao(UserDao userDao) { System.out.println("UserService的setUserDao方法"); this.userDao = userDao; } }
- AOP编程概述:
- Aop:aspect object programming 面向切面编程
功能: 让关注点代码与业务代码分离。 - 关注点:
重复代码就叫做关注点。 - 切面:
关注点形成的类,就叫切面(类)。
面向切面编程,就是指:对很多功能都有的重复的代码抽取,再在运行的时候往业务方法上动态植入“切面类代码”。 - 切入点:
执行目标对象方法,动态植入切面代码。
可以通过切入点表达式,指定拦截哪些类的哪些方法; 给指定的类在运行的时候植入切面类代码。
- Aop:aspect object programming 面向切面编程
- 注解方式实现AOP编程:
@Aspect 指定一个类为切面类
@Pointcut(“execution(* chauncy.service.UserService.add(…))”) 指定切入点表达式
@Before(“pointCut_()”) 前置通知: 目标方法之前执行
@After(“pointCut_()”) 后置通知:目标方法之后执行(始终执行)
@AfterReturning(“pointCut_()”) 返回后通知: 执行方法结束前执行(异常不执行)
@AfterThrowing(“pointCut_()”) 异常通知: 出现异常时候执行
@Around(“pointCut_()”) 环绕通知: 环绕目标方法执行- mybatis核心配置文件中开启aop注解
<!-- 开启aop注解 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 定义切面类和关注点
package chauncy.service; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Component //标识为切面 @Aspect public class AOPService { //关注点,开启事务 //前置通知 @Before("execution(* chauncy.service.UserService.add(..))") public void begin(){ System.out.println("##########使用springAOP前置通知,开启事务。。。"); } //提交事务 //后置通知 @After("execution(* chauncy.service.UserService.add(..))") public void commit(){ System.out.println("##########使用springAOP后置通知,提交事务。。。"); } //异常通知 @AfterThrowing("execution(* chauncy.service.UserService.add(..))") public void error(){ System.out.println("##########springAOP异常通知"); } //运行通知(无异常时才会执行) @AfterReturning("execution(* chauncy.service.UserService.add(..))") public void run(){ System.out.println("##########springAOP运行通知"); } //环绕通知 @Around("execution(* chauncy.service.UserService.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("##########springAOP环绕通知-之前"); proceedingJoinPoint.proceed(); System.out.println("##########springAOP环绕通知-之后"); } }
- 切入点的实现
package chauncy.service; import javax.annotation.Resource; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import chauncy.dao.UserDao; //等同于在XML文件中声明<bean id="userService" class="chauncy.service.UserService"></bean> @Service public class UserService { /** * @Resource 默认以属性名称userDao,去找bean对象 */ @Resource private UserDao userDao; public void add(){ //1/0会报错,模拟异常通知使用场景 int i=1/0; System.out.println("业务逻辑层。。。"); userDao.add(); } public void setUserDao(UserDao userDao) { System.out.println("UserService的setUserDao方法"); this.userDao = userDao; } }
- 测试类的编写
import org.springframework.context.support.ClassPathXmlApplicationContext; import chauncy.entity.UserEntity; import chauncy.service.UserService; public class SpringTest4 { public static void main(String[] args) { // 1.先加载spring容器 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring4.xml"); System.out.println("Spring容器被加载"); // 2.使用bean的id查找对象 UserService userService = (UserService) applicationContext.getBean("userService"); userService.add(); System.out.println(userService); } }
- mybatis核心配置文件中开启aop注解
- XML方式实现AOP编程:
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 如果要使用注解,首先要开启注解权限,扫描注解 --> <context:component-scan base-package="chauncy.*"></context:component-scan> <!-- XML和注解混合使用,定义相同beanId,验证是否会报错,若不报错,XML和注解谁先执行 --> <bean id="userDao" class="chauncy.dao.UserDao"> <property name="flag" value="true"></property> </bean> <!-- 把切面类加入到容器中 --> <bean id="aopService" class="chauncy.service.AOPService"></bean> <!-- aop的配置 --> <aop:config> <!-- 配置切入点,如果add替换成*表示UserService下所有方法都会当做切入点 --> <aop:pointcut expression="execution(* chauncy.service.UserService.add(..))" id="pc"/> <!-- 配置切面 --> <aop:aspect ref="aopService"> <aop:before method="begin" pointcut-ref="pc"/> <aop:after method="commit" pointcut-ref="pc"/> <aop:after-throwing method="error" pointcut-ref="pc"/> <aop:after-returning method="run" pointcut-ref="pc"/> <aop:around method="around" pointcut-ref="pc"/> </aop:aspect> </aop:config> </beans>