spring介绍
spring框架主要是为了方便我们进行程序的开发,降低代码之间的耦合度。使得代码之间依赖关系不是那么强烈。
spring主要功能有IoC(控制反转)和AOP(面向切面编程)以及事务的管理。并且spring可以和其他优秀的框架进行整合,方便我们使用。
IoC(控制反转)
控制含义:创建对象并对其赋值和管理。
反转含义:将创建对象的任务交给容器。我们程序员不需要再进行干预。
正转含义:我们程序员在我们需要对象的时候手动进行对象的创建。
控制反转含义:使用容器进行对象的创建和赋值以及管理。
控制反转是一个概念。其实现主要依靠DI(依赖注入)。而我们的依赖注入可以通过两种方式实现,一种是xml文件配置,另一种则是使用注解。
DI(依赖注入)
xml文件配置方式
使用xml文件进行注入的方式有两种,一种是设值注入,另外一种是构造注入,而我们的引用类型的属性可以通过自动注入实现。
设值注入
设值注入的原理是通过对我们配置文件中属性名的字符串拼接并调用其对应的set方法进行注入。比如name属性,底层会将n转成N并在其前面添加上set组成setName。
在如下的代码中我们的name属性和id属性都是基本数据类型(String在spring中也算基本数据类型),而School是我们自己创建的一个类,在我们的Student类中使用School属性则其为引用数据类型。需要在autowire
属性中为其声明是通过属性名(byName)自动注入,还是通过数据类型(byType)进行注入。引用数据类型需要使用ref
进行赋值。
<bean id="student" class="com.right.pojo.Student" autowire="byName">
<property name="name" value="张三"/>
<property name="id" value="20"/>
<property name="school" ref="school"/>
</bean>
<bean id="school" class="com.right.pojo.School">
<property name="name" value="清华大学"/>
<property name="address" value="北京"/>
</bean>
构造注入
构造注入和设值注入类似,和设值注入的区别为构造注入是调用对应对象的有参构造方法进行注入。
在构造注入中如果我们不指定参数的位置,默认按照构造方法中参数从左到右进行注入,如果使用index进行顺序的定位,则我们进行赋值时可以按照index定义的属性顺序进行赋值,index的0为有参构造中第一个参数。
<bean id="student" class="com.right.pojo.Student" autowire="byName">
<constructor-arg value="20"/>
<constructor-arg value="张三"/>
<constructor-arg ref="school"/>
</bean>
<bean id="school" class="com.right.pojo.School">
<property name="name" value="北京大学"/>
<property name="address" value="北京"/>
</bean>
使用index对位置进行标注,index中的数字代表参数所处位置,只需要将值对应上即可。
<bean id="student" class="com.right.pojo.Student" autowire="byName">
<constructor-arg index="1" value="张三"/>
<constructor-arg index="0" value="20"/>
<constructor-arg index="2" ref="school"/>
</bean>
<bean id="school" class="com.right.pojo.School">
<property name="name" value="北京大学"/>
<property name="address" value="北京"/>
</bean>
注解方式
想要使用注解注入,我们需要了解在IoC中有那些注解我们可以使用?
@Component注解:用来标注实体类的注解。
@Repository注解,用来标注DAO层的注解。
@Service注解,用来标注service层的注解。
@Controller注解,用来标注web层的注解。
这些注解都需要放到类的上方,且注解中有一个value属性,该属性是为该类创建的容器对象进行命名。以上注解本质没有区别, 只是用来标注类所属身份不同。
在基本数据类型的属性上方我们可以使用@value属性进行赋值。
对于引用数据类型,我们可以使用@Autowire注解进行赋值,autowire默认通过byType(数据类型)进行赋值,如果我们想通过属性名赋值的话,需要在@Autowire注解的下方使用@qualifier注解进行通过属性名赋值(@Qualifier(“属性名”))。
对于引用数据类型,我们还可以通过@Resource注解进行赋值,resource注解不是spring中的注解,而是一个普通的注解,该注解默认先通过byName进行赋值,如果找不到传值的对象,则其会通过byType继续进行寻找。
@Component("student")
public class Student {
@Value("5")
private Integer id;
@Value("王五")
private String name;
@Autowired
@Qualifier("school")
private School school;
==============================================
@Component("school")
public class School {
@Value("郑大")
private String name;
@Value("郑州中原区")
private String address;
当我们通过注解注入时,需要在我们的spring配置文件中,配置组件扫描器,该组件扫描器会扫描对应包下所有拥有注解的类,并通过spring容器进行容器对象的创建。
<context:component-scan base-package="com.right.pojo"/>
通过两种注入方式可以完成容器对象的创建,但是我们如何使用这些对象呢?
public class StudentTest {
@Test
public void test_01(){
String config = "applicationContext.xml";//spring配置文件信息
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
Student student = (Student) applicationContext.getBean("student");//该操作可以从容器中取出student对象
System.out.println(student);
}
}
AOP(面向切面编程)
面向切面编程实质上就是动态代理的规范化。切面的意思就是为我们的代理目标增强的功能。
动态代理的作用是为我们的代理目标增强功能,我们需要在代理类中调用代理目标类的功能方法,并在该功能方法上下加入我们需要增强的功能。
动态代理的实现有两种方式,一种是JDK动态代理,该代理目标类要求必须有接口,之后我们才能通过反射完成代理(代理三大类:proxy、invocationhandle、method)。另一种是cglib动态代理,该代理要求代理目标必须可以被继承。
在我们平常的使用中,主要是JDK动态代理。
谈完动态代理我们说一下AOP
想要使用AOP,我们需要先了解AOP的使用要求:
-
需要增强的切面方法必须要有
-
除此之外我们还应当注意切入的时机,是在代理目标类方法的何处添加我们的切面方法?
-
以及切入的位置,为哪个代理目标类的功能方法增加切面功能?
AOP的实现有两种方式:
-
一种是spring框架内部实现AOP,这种方式我们一般在事务中使用。该方式为spring内部实现,不需要配置。在aspectj通过xml文件配置时,spring内部自动实现AOP。
-
另一种方式是使用aspectj框架进行实现。
aspectj框架实现AOP
aspectj框架实现AOP有两种配置方式
-
一种是在xml文件中进行配置,这种方式应用在事务中。在事务管理中我们会进行详细的介绍。
-
另一种是使用aspectj框架自带的注解。我们主要使用该种方式实现AOP。
aspectj框架注解实现AOP
使用之前,首先了解aspectj实现AOP有哪些注解:
@Aspect 注解标注该类为切面类。
五种通知
-
@Before 前置通知,作用于方法之前,没有返回值,单纯增加功能。
-
@AfterReturnning 后置通知
作用于方法之后,有返回值,在切面方法中我们可以接收该返回值,且该返回值我们无法改变。但是我们可以在对应切面方法中对返回值引用地址中的内容修改以达到改变返回值的目的。不提倡。
-
@Around 环绕通知
最接近JDK动态代理的通知,无返回值,但我们可以改变返回值的结果,在切面内部调用代理目标类的功能方法,并在功能方法上下添加切面功能。
-
@AfterThrowing 异常通知
当代理目标类方法发生异常时该切面生效。有返回值,返回值为异常信息,在切面方法中我们可以接收该返回值。
-
@After 最终通知
相当于try{…}catch(Exception e){…}finally{…}中的finally,无论程序是否因发生异常而终止,在After中的代码必定会执行。
以上5种通知中切面方法都有一个默认存在的参数JoinPoint,这个参数对象可以获取到代理目标类的一些相关信息,该参数如果不设置便罢了,如果设置的话,必须在切面方法参数的第一位。特别要注意的是,在@Around注解中的切面方法需要用到的ProceedingJoinPoint参数,该ProceedingJoinPoint类继承了JoinPoint类,因此可以直接使用JoinPoint类中的方法。
public void myOther( JoinPoint jp ,Object res){}
当我们的多个切入点信息相同的时候,我们可以为其创建一个特别的方法来为切入点起别名,需要使用@PointCut注解进行标注。方法体内部不需要有代码。
@After(value = "myCut()")
public void myFinal(JoinPoint jp){
System.out.println("最终通知,必须执行");
System.out.println(jp.getSignature().getName());
}
@Pointcut(value = "execution(* *..MyAopImpl.myAround(..))")//为* *..MyAopImpl.myAround(..)切入点取别名
private void myCut(){}
五种通知的演示代码
package com.right.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.text.SimpleDateFormat;
import java.util.Date;
@org.aspectj.lang.annotation.Aspect
public class Aspect {
@Before(value = "execution(public void com.right.service.impl.MyAopImpl.doSome(String))")
public void myDate(JoinPoint joinPoint){
System.out.println(joinPoint.getSignature());
System.out.println(joinPoint.getSignature().getName());
Object[] args = joinPoint.getArgs();
for (Object arg:args) {
System.out.println(arg);
}
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String format = simpleDateFormat.format(date);
System.out.println("当前时间是:"+format+"作用在功能之前的切面方法");
}
@AfterReturning(value = "execution(public Student com.right.service.impl.MyAopImpl.doOther(String,int))",returning = "res")
public void myOther( JoinPoint jp ,Object res){
System.out.println("后置方法被执行");
/*if (res!=null){强行改值
Student student = (Student) res;
student.setName("王五");
student.setAge(25);
}*/
}
@Around(value = "execution(* *..MyAopImpl.myAround(..))")
public void myAround(ProceedingJoinPoint pjp) throws Throwable {
pjp.getSignature().getName();
System.out.println("作用于环绕通知之前");
pjp.proceed();
System.out.println("作用于环绕通知之后");
}
@AfterThrowing(value = "execution(* *..MyAopImpl.myAround(..))",throwing = "exc")
public void myException(JoinPoint jp ,Exception exc){
System.out.println("程序发生异常错误"+exc);
}
@After(value = "myCut()")
public void myFinal(JoinPoint jp){
System.out.println("最终通知,必须执行");
System.out.println(jp.getSignature().getName());
}
@Pointcut(value = "execution(* *..MyAopImpl.myAround(..))")
private void myCut(){}
}
注意
当我们使用aspectj框架时要在maven的pom.xml文件中导入aspectj对应的依赖。
在我们spring主配置文件中,要将我们的切面类和我们的代理目标类添加到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" 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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="myAop" class="com.right.service.impl.MyAopImpl"/>
<bean id="aspect" class="com.right.service.Aspect"/>
<!--自动代理生成器-->
<aop:aspectj-autoproxy/>
</beans>
Spring事务管理
事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。
在spring框架中我们通过两种方式来完成事务的管理
- 使用spring框架内部的AOP来实现事务管理,适用于中小型项目。
- 使用aspectj的xml配置文件来完成事务管理,适用于大型项目,代码和事务配置分离。解耦合。
事务定义信息,主要有三个:
-
隔离级别(isolation):读未提交(READ_UNCOMMITTED)、读已提交(READ_COMMITTED)、可重复读(REPEATABLE_READ)、串行化(SERIALIZABLE)。如果不给事务设置隔离级别,则显示默认值,mysql默认隔离级别是可重复读,oracle默认隔离级别是读已提交。
-
超时时间(timeout):我们一般不设置该值,因为影响该值的因素过多,比如网络延迟,SQL语句查询延迟。默认值为无限时长。
-
传播行为(propegation):传播行为有5种,我们常用的有三种。PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、PROPAGATION_REQUIRES_NEW。默认值为PROPAGATION_REQUIRED。
PROPAGATION_REQUIRED:指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。
PROPAGATION_SUPPORTS:指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
PROPAGATION_REQUIRES_NEW:总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
使用spring框架内部的AOP来实现事务管理(xml+注解)
非常简单
在加入对应的事务依赖后,我们在spring主配置文件中声明一个事务管理器对象,并告诉spring我们需要使用注解驱动来实现事务的管理,并在我们需要配置事务的功能方法中使用@transaction注解进行声明即可。
<!--使用德鲁伊数据库连接池-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="maxActive" value="20"/>
</bean>
<!--声明事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
<!--告诉spring我们要使用注解驱动管理事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>
功能方法
//配置事务定义
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false)
@Override
public int buyGoods(Integer gid, Integer sum) {内部可能还有方法}
使用aspectj框架进行事务的管理(xml)
首先加入对应依赖,并在spring主配值文件中配置事务信息。先声明一个事务管理器对象,然后在tx:advice标签内声明我们需要事务的方法,并对事务进行定义。在aop:config标签内配置我们的切入点信息。
最后将我们advice通知信息和切入点信息在aop:advisor标签中声明即可,我们的java代码不需要任何配置。
<!--使用德鲁伊数据库连接池-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="maxActive" value="20"/>
</bean>
<!--声明事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
<!--切面信息-->
<tx:advice id="interceptor" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="buy*" isolation="DEFAULT" propagation="REQUIRED" read-only="false" rollback-for="NullPointerException,NoStockException"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--配置切入点-->
<aop:pointcut id="myPt" expression="execution(* *..service..*.*(..))"/>
<!--为增强器配置通知和切入点-->
<aop:advisor advice-ref="interceptor" pointcut-ref="myPt"/>
</aop:config>
spring整合mybatis
spring框架的优点便是能够和其他优秀框架进行整合,方便我们的使用。
spring整合mybatis实质上就是通过spring的容器来对mybatis对象进行创建。
mybatis需要创建的对象主要有三个,连接池对象、sqlSessionFactory对象以及Dao对象。
当我们在spring中声明连接池对象时,我们的mybatis主配置文件就不需要声明environment信息了。
<!--使用德鲁伊数据库连接池-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="maxActive" value="20"/>
</bean>
<!--声明sqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--创建dao对象,由于dao对象可能有若干个,因此没有id值,但是我们在调用对象时直接使用类名首字母小写即可调用比如下方的service-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.right.dao"/>
</bean>
<!--声明service对象,里面直接可以使用上方创建的dao对象-->
<bean id="buyGoods" class="com.right.service.impl.BuyGoodsImpl">
<property name="goodsDao" ref="goodsDao"/>
<property name="saleDao" ref="saleDao"/>
</bean>
#Spring 与 Web
当我们在servlet程序中,每次调取所需对象都需要重新创建一次容器对象,非常浪费内存。在此,我们只需要将我们的容器对象存储到servletContext全局域中,那么我们每次调用的容器对象都是同一个,可以大大减少内存的消耗。
Spring 的监听器 ContextLoaderListener很好的为我们解决了这一问题。
首先我们需要加入使用监听器所需依赖。然后在servlet的主配置文件中声明监听器,并在该文件中指定我们spring主配置文件位置。
然后在servlet程序中调用webApplication的getRequiredWebApplication对象即可创建容器对象,且在以后调用的容器对象都是通一个。
<!--监听器所需依赖配置到pom.xml中-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--监听器信息,配置到servlet配置文件中-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--spring主配置文件路径-->
<param-value>applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
使用工具方法获取容器对象,ctx便是获取的容器对象,可以调用其getBean方法获取具体对象。
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
spring常用依赖
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--aspectj框架-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<!--事务-->
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--mybatis与spring整合-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--数据库连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!--德鲁伊数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!--监听器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
maven扫描文件的pom.xml信息
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>