Spring框架
Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。
简介
-
介绍 : “Spring” means different things in different contexts. 我们常说的 Spring 实际上是指 Spring Framework,而 Spring Framework 只是 Spring 家族中的一个分支而已。
-
优势 :
- 方便解耦,简化开发
- AOP编程的支持
- 声明式事务的支持
- 方便程序的测试
- 方便集成各种优秀框架
- 降低JavaEE API使用难度
- Java源码是经典学习范例
-
Spring体系
-
程序间耦合 :
- 耦合 : 程序间的依赖
- 类之间的依赖
- 方法间的依赖
- 解耦 : 降低程序间的依赖关系
- 开发要求 : 编译期不依赖,运行期才依赖
- 解决思路 :
- 使用反射来创建对象,而避免使用new关键字
- 通过读取配置文件来获取要创建对象的全限定类名.
- 耦合 : 程序间的依赖
Spring初体验
-
IOC : 控制反转(Inversion of Control,缩写为IoC) , 就是把对象的创建权交由Spring, 需要的时候注入即可
-
IOC实例小Demo
-
创建项目并导入Maven依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.1</version> </dependency>
-
实体类
public class User { private int id; private String username; private String password; // 省略setter/getter和toString }
-
创建配置文件applicationcontext.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> // User对象交由Spring管理 <bean class="org.java.jsmf_spring.model.User" id="user"/> </beans>
-
客户端调用
public class Client { public static void main(String[] args) { ClassPathXmlApplicationContext cpac = new ClassPathXmlApplicationContext("applicationcontext.xml"); User user = (User) cpac.getBean("user"); user.setId(1); user.setUsername("zs"); user.setPassword("123"); System.out.println(user); } } // output User{id=1, username='zs', password='123'}
-
实现原理: 反射+工厂+单例实现
-
-
DI: 依赖注入. DI是在IOC的前提下, 在类实例化的过程中为其属性赋值.
-
DI的实现方式
-
通过构造方法注入
<bean class="org.java.jsmf_spring.model.User" id="user"> <constructor-arg name="id" value="1"/> <constructor-arg name="username" value="ls"/> <constructor-arg name="password" value="123"/> </bean>
-
使用set方法注入
// 实体类 public class Role { private String id; private String name; private User user; // 省略setter/getter和toString 方法 } // applicationcontext.yml中添加 <bean class="org.java.jsmf_spring.model.User" id="user"> <property name="id" value="3"/> <property name="username" value="ww"/> <property name="password" value="123"/> </bean> <bean class="org.java.jsmf_spring.model.Role" id="role"> <property name="id" value="4"></property> <property name="name" value="zl"></property> <property name="user" ref="user"></property> </bean>
-
外部Bean的注入
-
使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器中)
public class UserFactory { private static User user; public static User getInstance() { if (user==null) { user = new User(); } return user; } } <bean class="org.java.jsmf_spring.utils.UserFactory" factory-method="getInstance" id="user2"></bean>
-
使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
public class RoleFactory { private Role role; public Role getRole() { if (role ==null) { role =new Role(); } return role; } } <bean class="org.java.jsmf_spring.utils.RoleFactory" id="roleFactory"></bean> <bean factory-bean="roleFactory" factory-method="getRole" id="role2"></bean>
-
-
List, Array, Set的注入(只需更换对应标签即可)
public class JavaGuy { private String name; private List<String> favorites; } // 省略setter/getter和toString <bean class="org.java.jsmf_spring.model.JavaGuy" id="javaGuy"> <property name="name" value="xiaohei"></property> <property name="favorites"> <list> <value>抽烟</value> <value>喝酒</value> <value>烫头</value> </list> </property> </bean>
-
Map, Properties注入
<property name="myProp"> <props> <prop key="testC">ccc</prop> <prop key="testD">ddd</prop> </props> </property> <property name="myMap"> <map> <entry key="testA" value="aaa"></entry> <entry key="testB"> <value>BBB</value> </entry> </map> </property>
-
bean的作用范围调整
- bean标签的scope属性
作用 : 用于指定bean的作用范围
取值 : 常用的是单例和多例
1. singleton : 单例的(默认值)
2. prototype : 多例的
3. request : 作用于web应用的请求范围
4. session : 作用于web应用的会话范围
5. global-session : 作用于集群环境的会话范围,不是集群的话相当于session.
- bean标签的scope属性
-
bean的生命周期
- 单例 :
出生 : 容器创建时
活着 : 容器存在时一直活着
死亡 : 容器销毁时 - 多例 :
出生 : 使用对象时,spring为我们创建
活着 : 只要在使用过程中就一直活着
死亡 : 长时间不用,并且没有其它引用时,java垃圾回收机制回收
- 单例 :
-
Spring的注解
- 用于创建对象的
- component :
-
作用 : 把当前类对象存入spring容器中
-
属性 : value : 用于指定bean的id,当我们不写时,它的默认值是当前类名且首字母改小写.
-
注意 : 为了使该注解生效需要在名称空间声明使用注解
<?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"> <!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为 context名称空间和约束中--> <context:component-scan base-package="com.junyang"></context:component-scan> </beans>
-
- Controller : 一般用于表现层
- Service : 一般用哦关于业务层
- Repository : 一般用在持久层
- component :
- 用于注入数据的
- Autowired :
- 作用 : 自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入.
- 出现位置 : 成员变量,方法
- Resource :
- 作用 : 直接按照bean的id注入.
- 属性 : name =xxx
- 注意 : 以上注解只能注入其他bean类型的数据,而基本类型和String无法使用,另外集合类型的注入只能通过xml来实现.
- Value :
- 作用 : 注入基本数据类型和String类型
- 属性 : 用于指定数据的值,它可以使用Spring中的Spel的el表达式(${表达式})
- Autowired :
- 用于改变bean作用范围的
- Scope : 常用的是 singleton 和 prototype
- 和声明周期相关的
- PreDestory : 用于指定销毁方法
- PreConstruct : 用于指定初始化方法
- Spring中的新注解
- Configuration : 指定当前类是一个配置类
- ComponentScan : 用于通过注解指定spring在创建容器时要扫描的包
- Bean : 用于把当前方法的返回值作为bean对象存入spring的ioc容器.
- 当使用value时,其值就是bean的id.没有的话id就是方法名.
- 细节 : 当我们使用注解配置方法时,如果方法有参数,Spring框架或去容器中查找有没有可用的bean对象,查找方法和Autowired注解的作用是一样的.
- 注解开发 ```AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(配置类的名称.class);``
- import : 用于导入其它的配置类,用于指定其它配置类的字节码
- PropertySource("classpath : ") : 用于指定peoperties文件的位置
- xml或者是注解开发原则 : 自己写的类用注解,引用其它人写的类用xml配置
- Qualifier("") : 可以加载参数列表中,指定bean的id.
- Spring整合junit
- 导入jar包的坐标.
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.6.RELEASE</version> </dependency>
- 使用RunWith(SpringJunit4ClassRunner.class)
- 告知Spring的运行器,Spring的ioc创建是基于xml还是注解的,并且说明位置ContextConfiguration .
- location : 指定xml文件的位置,加上classpath关键字,表示类路径
- classes : 指定注解类所在的位置.
- 导入jar包的坐标.
- 事务控制流程
- 开启事务
- 执行操作
- 提交事务(正常情况直接跳到5)
- 回滚事务
- 释放资源
Spring使用到的技术–动态代理
- 基于接口
- 基于接口的动态代理 :
- 涉及的类 : Proxy
- 提供者 : JDK
- 如何创建代理对象
- 使用Proxy类中的newProxyInstance方法
- 创建代理对象的要求
- 被代理类至少实现一个接口,没有则不能使用
- newProxyInstance方法的参数
- classLoader : 类加载器 : 它是用于记载代理对象字节码的,写的是被代理对象的类加载器.简言 : 代理谁就写谁–IAccountServiceImpl.class.getClassLoader()
- Class[ ] : 字节码数组 : 它是用于让代理对象和被代理对象有相同方法. 简言之,代理谁就写谁. – IAccountServiceImpl.class.getInterfaces()
- InvocationHandler : 用于提供增强的代码. 它是让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的,此接口的实现类都是谁用谁写.
- 作用 : 执行被代理对象的任何接口方法都会经过该方法
- 参数 :
- proxy : 代理对象的引用
- method : 当前执行的方法
- args : 当前执行方法所需的参数
- return : 和被代理对象方法有相同的返回值
- 基于接口的动态代理 :
- 基于类的动态代理
-
导入坐标 :
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_3</version> </dependency>
-
涉及的类:Enhancer
-
提供者:第三方cglib库
-
如何创建代理对象:使用Enhancer类中的create方法
-
创建代理对象的要求:被代理类不能是最终类
-
create方法的参数:
Class:字节码 它是用于指定被代理对象的字节码。
Callback:用于提供增强的代码
它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类都是谁用谁写。
我们一般写的都是该接口的子接口实现类:MethodInterceptor/** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() { /** * 执行被代理对象的任何方法都会经过该方法 * @param proxy * @param method * @param args * 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的 * @param methodProxy :当前执行方法的代理对象 * @return * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.7f); } return returnValue; } }); cglibProducer.saleProduct(10000f); } }
-
Spring中的AOP
-
概念 : AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,在不改变源码的情况下, 增强程序的功能。
-
具体应用
- 日志
- 事务
- 数据库
- …
-
AOP相关术语
- Joinpoint(连接点) : 所谓连接点是指那些被拦截的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.简单点说,Spring允许你使用通知的地方.
- Pointout(切入点) : 所谓切入点是指我们要对那些Joinpoint进行拦截的定义.简单点讲,在连接点中选择你想增强的方法,这些方法就是切点.
- Advice(通知/增强) : 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.简单理解就是你想要实现的功能.
- 通知的类型 : 前置通知,后置通知,异常通知,最终通知,环绕通知.
- Introduction(引介) : 引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或field.
- Target(目标对象): 代理的目标对象
- Maving(织入) : 是指把增强应用到目标对象来创建新的代理对象的过程.
- Proxy(代理) : 一个类被AOP织入增强后,就产生一个结果代理类
- Aspect(切面) : 是切入点和通知的结合.
-
基于xml的AOP使用
-
把通知Bean交给spring来管理
-
使用aop:config标签表明开始AOP的配置
-
使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的Id。 -
在aop:aspect标签的内部使用对应标签来配置通知的类型
我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强 -
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表) -
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.junyang.service.impl..(…) -
配置 :
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="AccountService" class="com.junyang.aop.service.impl.AccountServiceImpl"></bean> <bean id="logger" class="com.junyang.aop.unit.Logger"></bean> <!--配置AOP--> <aop:config> <!--配置切面--> <aop:aspect ref="logger"> <!--配置切入点--> <aop:pointcut id="logAdvice" expression="execution(* com.junyang.aop.service.impl.*.*(..))"></aop:pointcut> <!--配置前置通知--> <aop:before method="printLogger" pointcut-ref="logAdvice"></aop:before> <!--配置最终通知--> <aop:after method="printLogger" pointcut-ref="logAdvice"></aop:after> <!--配置异常通知--> <aop:after-throwing method="printLogger" pointcut-ref="logAdvice"></aop:after-throwing> <!--配置后置通知--> <aop:after-returning method="printLogger" pointcut-ref="logAdvice"></aop:after-returning> </aop:aspect> </aop:config> </beans>
-
-
基于注解的AOP使用
/** * 用于记录日志的工具类,它里面提供了公共的代码 */ @Component("logger") @Aspect//表示当前类是一个切面类 public class Logger { @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){} /** * 前置通知 */ // @Before("pt1()") public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */ // @AfterReturning("pt1()") public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ // @AfterThrowing("pt1()") public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ // @After("pt1()") public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } /** * 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ @Around("pt1()") public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } } }
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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置业务层--> <bean id="accountService" class="com.junyang.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 配置账户的持久层--> <bean id="accountDao" class="com.junyang.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置数据源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property> </bean> <!-- spring中基于XML的声明式事务控制配置步骤 1、配置事务管理器 2、配置事务的通知 此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的 使用tx:advice标签配置事务通知 属性: id:给事务通知起一个唯一标识 transaction-manager:给事务通知提供一个事务管理器引用 3、配置AOP中的通用切入点表达式 4、建立事务通知和切入点表达式的对应关系 5、配置事务的属性 是在事务的通知tx:advice标签的内部 --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务的通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 配置事务的属性 isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。 propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。 read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。 timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。 rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。 --> <tx:attributes> <tx:method name="*" propagation="REQUIRED" read-only="false"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method> </tx:attributes> </tx:advice> <!-- 配置aop--> <aop:config> <!-- 配置切入点表达式--> <aop:pointcut id="pt1" expression="execution(* com.junyang.service.impl.*.*(..))"></aop:pointcut> <!--建立切入点表达式和事务通知的对应关系 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config> </beans>
-
基于注解
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置spring创建容器时要扫描的包--> <context:component-scan base-package="com.junyang"></context:component-scan> <!-- 配置JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置数据源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property> </bean> <!-- spring中基于注解 的声明式事务控制配置步骤 1、配置事务管理器 2、开启spring对注解事务的支持 3、在需要事务支持的地方使用@Transactional注解 --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启spring对注解事务的支持--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> </beans>