Spring出现的目的,是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。Spring的成功来自于理念,而不是技术,它最核心的理念是IoC(控制反转)和AOP面向切面编程,其中IOC是Spring的基础,AOP是其重要的功能。
Spring IoC
基本概念
控制反转(Inversion of Control,IoC),看名字比较抽象,它还有另外一种说法,叫依赖注入(Dependency injection,DI),只是从不同的角度描述相同的概念。
用一个编程来表示的实际生活例子来解释下Ioc和DI,比如说,你想吃面包,在没有面包店的情况下,你需要自己主动制作面包,也就是说你是调用者,需要调用另一个对象,会采用"new 面包"这样的方式创建面包,而Spring的出现,对象不需要调用者创建,而是由Spring容器(面包店)来创建,这样一来,你再也不能直接控制面包了,而是面包店控制了面包,控制权由调用者转移到了Spring容器,控制权发生了反转,就叫Spring的控制反转。这是站在调用者的角度看。
站在Spring容器角度看,面包店知道你需要面包,它会把对应的面包给你,也就是调用者依赖了另一个对象,Spring为调用者注入了它所依赖的对象,这过程叫做依赖注入。
所以,Spring中实现控制反转的是IoC容器,IoC容器怎么做到的呢,它是通过依赖注入,也就是依赖注入是它的实现方法。
简单使用
引入Srping:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
public interface TestDao {
public void helloSpring();
}
public class TestDaoImpl implements TestDao {
@Override
public void helloSpring() {
System.out.println("Hello,Spring");
}
}
在resources下新建applicationContext.xml,或者叫spring-config.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"
>
<bean id="test" class="com.dane.spring.dao.TestDaoImpl"/>
</beans>
测试代码:
public static void main(String[] args) {
//早期Spring采用的是BeanFactory,它与ApplicationContext的区别是:
//BeanFactory采取延迟加载,第一次getBean时才会初始化Bean
//ApplicationContext在加载spring‐config.xml时候就会创建具体的Bean对象的实例
ApplicationContext context
= new ClassPathXmlApplicationContext("applicationContext.xml");
TestDao testDao = (TestDao) context.getBean("test");
testDao.helloSpring();
}
有参构造的方式创建对象
<?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="user" class="com.dane.spring.domain.User">
<constructor-arg name="id" value="1"/>
<constructor-arg name="account" value="dane"/>
<constructor-arg name="name" value="dane"/>
<constructor-arg name="sex" value="1"/>
<constructor-arg name="home_id" value="1"/>
</bean>
<!--也可以使用使用index指定,对应构造参数-->
<bean id="user1" class="com.dane.spring.domain.User">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="dane"/>
<constructor-arg index="2" value="dane"/>
<constructor-arg index="3" value="1"/>
<constructor-arg index="4" value="1"/>
</bean>
<!--也可以使用按照顺序,对应构造参数-->
<bean id="user2" class="com.dane.spring.domain.User">
<constructor-arg value="1"/>
<constructor-arg value="dane"/>
<constructor-arg value="dane"/>
<constructor-arg value="1"/>
<constructor-arg value="1"/>
</bean>
</beans>
注入的方式创建对象
来个综合类
public class Home {
private int id;
/**注入实体Bean*/
private User user;
/**注入数组*/
private Object[] array;
/**注入List集合*/
private List<Object> list;
/**注入Set集合*/
private Set<Object> set;
/**注入Map键值对*/
private Map<Object, Object> map;
/**注入properties类型*/
private Properties properties;
....
}
然后看下配置文件怎么写:
<?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="home" class="com.dane.spring.domain.Home">
<property name="id" value="1"/>
<property name="user">
<bean class="com.dane.spring.domain.User">
<property name="id" value="1"/>
<property name="account" value="dane"/>
<property name="sex" value="1"/>
<property name="home_id" value="1"/>
<property name="name" value="dane"/>
</bean>
</property>
<property name="array">
<array>
<value>array1</value>
<value>array2</value>
<value>array3</value>
</array>
</property>
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
<value>list3</value>
</list>
</property>
<property name="map">
<map>
<entry>
<key>
<value>1</value>
</key>
<value>1.1</value>
</entry>
<entry>
<key>
<value>2</value>
</key>
<value>2.2</value>
</entry>
</map>
</property>
<property name="set">
<set>
<value>set1</value>
<value>set2</value>
<value>set3</value>
</set>
</property>
<property name="properties">
<props>
<prop key="prop1">propv1</prop>
<prop key="prop2">propv2</prop>
</props>
</property>
</bean>
</beans>
SpringIOC 创建对象细节
<?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">
<!--scope :用来设置单例或多例
singleton:单例,默认的,工厂加载xml后,会产生对象,每次去获取是同一个
prototype:多例,工厂加载xml后,不会产生对象,每次获取创建一个对象
-->
<bean id="user" class="com.dane.spring.domain.User" scope="prototype"/>
<!--lazy-init:延迟加载,只对单例有效
true:工厂加载xml后,不会产生对象
false:工厂加载xml后,会产生对象
-->
<bean id="user1" class="com.dane.spring.domain.User" scope="singleton" lazy-init="true"/>
<!-- init-method:bean初始化的时候调用
destroy-method:bean销毁的时候调用
-->
<bean id="user1" class="com.dane.spring.domain.User" init-method="init" destroy-method="destroy"/>
</beans>
SpringIOC注解的使用
首先要在spring-config.xml引入context约束:
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
然后配置 <context:component-scan>,具体如下:
<?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="com.dane.spring.*"/>
</beans>
然后就可以使用注解了:
/**
* Spring中提供@Component的三个衍生注解,功能目前来讲是一致的
* @Controller WEB层
* @Service 业务层
* @Repository 持久层
*/
@Component("home")
public class Home {
/**
* 属性注入
*/
@Value("1")
private int id;
/**
* Autowired注解表示自动装载,它会在容器找user
* 另外,@Resource和@Autowired一样,区别在于@Autowired默认按照类型进行装载,
* @Resource默认根据名字来装载。
* @Autowired想使用名称来加载,要和@Qualifier配合使用。
*/
@Autowired
private User user;
@Override
public String toString() {
return "Home{" +
"id=" + id +
", user=" + user +
'}';
}
}
SpringEL的使用
SpringEL是一种表达式,可以动态的为Bean的属性赋值,Spring3后才支持。
<?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="user" class="com.dane.spring.domain.User"/>
<bean id="person" class="com.dane.spring.domain.Person"/>
<bean id="el" class="com.dane.spring.domain.SpringELTest">
<property name="num" value="#{3*5}"></property>
<property name="sayWhat" value="#{person.say('hello')}"> </property>
<property name="name" value="#{person.getName()}"></property>
<property name="user" value="#{user}"></property>
<property name="rand" value="#{T(Math).random()}"></property>
<property name="flag" value="#{user.id==10003}"></property>
</bean>
</beans>
Spring整合单元测试
引入配置:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
然后这样写测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
public class AppTest
{
@Autowired
private IUserDao userDao;
@Test
public void test1(){
userDao.getUsers();
}
}
SpringAOP
基本概念
AOP是Aspect Oriented Programming的缩写,意思是面向切面编程,AOP可以在不修改代码的情况下,对程序进行加强,比如权限校验、日志记录、性能监控、事务控制等。
AOP的相关术语
Joinpoint(连接点):指程序运行中的一些时间点,在spring中,与方法有关的前前后后,都是连接点,比如调用前、调用后、抛出异常时等。
Pointcut(切入点):就是我们从Joinpoint(连接点)中找出的那些想要拦截处理的点。
Advice(通知/增强):指拦截到的Joinpoint(连接点)后的通知回调,分为前置通知、后置通知、异常通知、最终通知、环绕通知。
Target(目标对象):代理的目标对象。
Weaving(织入):指的是通过代理对象,把切面应用到目标对象的过程,SpringAOP采用动态代理切入。
Proxy(代理):Weaving(织入)后产生的代理对象。
Aspect(切面):是切入点和通知的结合。
SpringAOP动态代理
SpringAOP底层的实现采用的是代理技术,有两种方式,一种是基于JDK动态代理,用于实现类是接口,另一种是CGLIB动态代理,用于没有接口的实现类,CGLIB是对目标类产生一个子类,并进行增强。
图解AspectJ方法执行流程
image.pngSpringAOP XML形式的开发
引入配置:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
写个切面类:
public class UserDaoAspect {
/**
* 方法执行前的通知
*/
public void before(){
System.out.println("before");
}
/**
* 方法执行通知
* pjp.proceed()就是执行目标方法,返回值就目标方法的返回值,
* 可以在这里对返回值进行处理修改
*/
public Object around(ProceedingJoinPoint pjp) {
System.out.println("around");
Object result = null;
try {
result = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
/**
* 方法正常常返回通知,抛出异常才会调用
*/
public void afterReturning(){
System.out.println("afterReturning");
}
/**
* 方法异常返回通知
*/
public void afterReturnThrowing(){
System.out.println("afterReturnThrowing");
}
/**
* 方法执行后的通知
*/
public void after(){
System.out.println("after");
}
}
然后在applicationContext.xml或spring-config.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: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/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
"
>
<bean id="userDao" class="com.dane.spring.dao.UserDaoImpl"/>
<bean id="aspect" class="com.dane.spring.aspect.UserDaoAspect"/>
<aop:config>
<aop:pointcut id="pt" expression="execution(public * com.dane.spring.dao.UserDaoImpl.getUsers())"/>
<aop:aspect ref="aspect">
<aop:before method="before" pointcut-ref="pt"/>
<aop:around method="around" pointcut-ref="pt"/>
<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
<aop:after-throwing method="afterReturnThrowing" pointcut-ref="pt"/>
<aop:after method="after" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
</beans>
execution表达式语法
基本格式:
execution(方法的修饰符 方法的返回值类型 方法所属的类 方法名 方法中参数列表
方法抛出的异常)
简化格式:
//可以不写包名和类:代表是工程所有addUser方法都有效
execution(方法的返回值类型 方法名(方法中参数列表))
execution(public void addUser() throws Exception)
//可以不写包名和类,一个参数可以使用 * 号代替
execution(public void addUser(*)throws Exception)
//可以不写包名和类,任意参数(包括没有参数)可以使用 ..
execution(public void addUser(..)throws Exception)
//省略修饰符,返回值用*代替
execution(* com.dane.spring.dao.UserDaoImpl.addUser(..))
//可以不写包名和类,返回值用*代替,声明异常
execution(* addUser() throws Exception)
//可以不写包名和类,任意方法名:*
execution(public void *(..)throws Exception)
//可以不写包名和类,方法名以add开始
execution(public void add*(..)throws Exception)
//可以不写包名和类,方法名以service结尾
execution(public void *service(..)throws Exception)
//省略修饰符,可以不写包名和类,返回值用*代替
execution(* add())
//省略修饰符,可以不写包名和类,返回值用*代替,任意方法名:*,任意参 数(包括没有参数)可以使用 ..
execution(* *(..))
省略修饰符,com.dane.spring.dao包和其子包都有效果,方法名以add开始
execution(* com.dane.spring.dao..insert*(..))
SpringAOP 注解形式的开发
在applicationContext.xml或spring-config.xml编写相关配置:
<context:component-scan base-package="com.dane.spring.*"/>
<aop:aspectj-autoproxy proxy-target-class="false"/>
然后这样使用注解:
@Component()
@Aspect()
public class UserDaoAspect {
@Pointcut("execution(public * com.dane.spring.dao.UserDaoImpl.getUsers())")
public void pointCut(){
}
@Before("pointCut()")
public void before() {
System.out.println("before");
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) {
System.out.println("around");
Object result = null;
try {
result = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
@AfterReturning("pointCut()")
public void afterReturning() {
System.out.println("afterReturning");
}
@AfterThrowing("pointCut()")
public void afterReturnThrowing() {
System.out.println("afterReturnThrowing");
}
@After("pointCut()")
public void after() {
System.out.println("after");
}
}
Spring事务
XML方式使用事务
基本使用的话,完成了配置基本上就可以了,所以这里不贴业务代码了。
引入spring-tx:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
编写配置文件:
<?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" xmlns:tx="http://www.springframework.org/schema/tx"
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
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"
>
<!--扫描指定的包,使注解生效-->
<context:component-scan base-package="com.dane.spring"/>
<!--配置数据源,这里使用c3p0-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/dane?characterEncoding=utf-8"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--添加数据源到事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--编写通知声明事务,这里表示任意方法-->
<tx:advice id="myAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.dane.spring.service.*.*())"/>
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPointCut"/>
</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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
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
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"
>
<!--扫描指定的包,使注解生效-->
<context:component-scan base-package="com.dane.spring"/>
<!--配置数据源,这里使用c3p0-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/dane?characterEncoding=utf-8"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--添加数据源到事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--注册注解驱动-->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
然后使用@Transactional
@Transactional
public void addUser(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addUser(user1);
}
事务处理捕获异常
如果我们在业务代码中加了try...catch语句,出异常时Spring事务将无法回滚,例如:
public void addUser(){
try {
User1 user1=new User1();
user1.setName("张三");
user1Service.addUser(user1);
}catch (Exception e) {
System.out.println("出现异常")
}
}
若是采用XML方式使用注解,你必须将 <tx:method name="*"/>修改为:
<tx:method name="*" rollback-for="java.lang.Exception"/>
然后在catch添加throw new RuntimeException():
public void addUser(){
try {
User1 user1=new User1();
user1.setName("张三");
user1Service.addUser(user1);
}catch (Exception e) {
throw new RuntimeException():
System.out.println("出现异常")
}
}
若是注解,则:
@Transactional(rollbackFor={Exception.class})
public void addUser(){
try {
User1 user1=new User1();
user1.setName("张三");
user1Service.addUser(user1);
}catch (Exception e) {
throw new RuntimeException():
System.out.println("出现异常")
}
}
事务的传播行为
PROPAGATION_REQUIRED
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY
使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
网友评论