Spring IOC 解决的是 对象管理和对象依赖的问题。
Spring AOP 解决的是 非业务代码抽取的问题。
Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架。以 IoC(Inverse Of Control:控制反转)和AOP(Aspect Oriented Programming:面向切面编程)为内核。
控制反转指的就是:本来是由我们自己new出来的对象,现在交给了IOC容器(Spring为我们提供得IOC容器),把这个对象的控制权给别人了。控制反转更多的是一种思想或者说是设计模式,把原有由自己掌控的事交给别人来处理。
依赖注入(Dependency Injection)是 IOC(控制反转) 的具体实现,对象无需自行创建或管理它们的依赖关系,依赖关系将被自动注入到需要它们的对象当中去。在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。
最简单理解依赖注入和控制反转:本来我们的对象都是由我们自己new出来的,现在我们把这个对象的创建权限和对象之间的依赖关系交由IOC容器来管理。
为什么我们要把对象交给IOC容器来管理呢?就是想将对象集中统一管理,便于修改。 降低耦合度(调用方无需自己组装,也无需关心对象的实现,直接从IOC容器中获取就好了。
无论是创建对象、处理对象之间的依赖关系、对象创建的时间还是对象的数量,我们都是在Spring为我们提供的IOC容器上配置对象的信息就好了。
Spring提供了四种方式来管理IOC容器:注解、XML、JavaConfig、基于Groovy DSL配置 ,掌握注解和XML方式即可。
Spring优势
方便解耦,简化开发
AOP 编程的支持
声明式事务的支持
方便程序的测试
Spring开发步骤
①导入 Spring 开发的基本包坐标
②编写 Dao 接口和实现类
③创建 Spring 核心配置文件(习惯命名为applicationContext.xml)
④在 Spring 配置文件中配置 UserDaoImpl
⑤使用 Spring 的 API 获得 Bean 实例
获取Bean实例之前需要获取IOC容器,通过IOC容器获得Bean实例,IOC容器有两种类型:
①BeanFactory
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
②ApplicationContext(一般使用这个)
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService)ac.getBean("userService");
配置文件applicationContext.xml
<bean id="userDao" class="cn.xuexi.dao.impl.UserDaoImpl" scope="prototype"></bean>
Bean标签基本配置:id class(配置全限定名)
Bean标签范围配置:scope(singleton prototype request globalsession)
Bean初始化方法和销毁方法配置:init-method destroy-method
singleton:Bean在加载配置文件创建Bean容器时候创建,只创建一个。只要容器在,对象一直活着。当销毁容器时,对象就被销毁了
prototype:Bean在getBean时候创建,获取一次创建一个。只要对象使用就一直存活。当对象长时间不用时,被 Java GC回收。
Bean实例化三种方式(Bean对象创建方式)
无参构造方法:会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败。
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
工厂静态方法:先要有工厂类实现静态方法
public class StaticFactoryBean {
public static UserDao createUserDao(){
return new UserDaoImpl();
}
}
然后xml配置
<bean id="userDao" class="com.itheima.factory.StaticFactoryBean" factory-method="createUserDao" />
工厂实例方法 :先要有工厂类
public class DynamicFactoryBean {
public UserDao createUserDao(){
return new UserDaoImpl();
}
}
然后xml配置
<bean id="factoryBean" class="com.itheima.factory.DynamicFactoryBean"/>
<bean id="userDao" factory-bean="factoryBean" factory-method="createUserDao"/>
Bean的依赖注入方式有两种 构造方法注入(``第一个userDao是构造函数参数名,第二个userDao是第一行的对象) ```xml ``` set方法注入(第一个userDao是setUserDao后边部分把首字母小写,第二个userDao是第一行的对象),在实现类中需要有set方法:
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
P命名空间(由set方法演化而来)
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
除了对象的引用可以注入,普通数据类型,集合也可以在容器中进行注入。
可注入三种数据类型
普通数据类型
引用数据类型
集合
ApplicationContext实现类
ClassPathXmlApplicationContext:从类的根路径下加载配置文件
FileSystemXmlApplicationContext:从磁盘路径上加载配置文件
AnnotationConfifigApplicationContext:注解时使用
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,使用注解可以简化配置。
使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。
<context:component-scan base-package="cn.xuexi"></context:component-scan>
原始注解
@Component 使用在类上用于实例化Bean
@Controller 使用在web层类上用于实例化Bean
@Service 使用在service层类上用于实例化Bean
@Repository 使用在dao层类上用于实例化Bean
@Autowired 使用在字段上用于根据类型依赖注入
@Qualififier 结合@Autowired一起使用用于根据名称进行依赖注入
@Resource 相当于@Autowired+@Qualififier,按照名称进行注入
@Value 注入普通属性
@Scope 标注Bean的作用范围
@PostConstruct 使用在方法上标注该方法是Bean的初始化方法
@PreDestroy 使用在方法上标注该方法是Bean的销毁方法
新注解
@Configuration 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan 用于指定 Spring 在初始化容器时要扫描的包。 作用和在 Spring 的 xml 配置文件中的 <context:component-scan base-package=“com.itheima”/>一样,需要和@Configuration这个注解配合一起使用。
@Bean 使用在方法上,标注将该方法的返回值存储到 Spring 容器中
@PropertySource 用于加载.properties 文件中的配置
@Import 用于导入其他配置类
静态代理代码如下,静态代理缺点在于接口改了代理需要跟着改。
// 接口
public interface IUserDao {
void save();
}
//UserDao实现该接口,重写save()方法
public class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("-----已经保存数据!!!------");
}
}
//代理实现IUserDao接口
public class UserDaoProxy implements IUserDao{
private IUserDao target;
public UserDaoProxy(IUserDao target) {
this.target = target;
}
@Override
public void save() {
System.out.println("开始事务...");
target.save();
System.out.println("提交事务...");
}
}
//外界调用代理而不是目标
public static void main(String[] args) {
IUserDao target = new UserDao();
IUserDao proxy = new UserDaoProxy(target);
proxy.save();
}
动态代理相对于静态代理,代理对象不需要实现接口,代理对象是利用JDKAPI生成, 动态地在内存中构建代理对象
常用的动态代理技术
JDK 代理 : 基于接口的动态代理技术,缺点是目标对象必须有一个接口,代理对象基于此接口实现。
cglib 代理:基于父类的动态代理技术
由于静态代理的目标对象和代理对象需要实现相同的接口,因此出现了JDK动态代理,由于JDK动态代理的目标对象一定是要实现接口(增强对象不需要实现该接口),因此出现了cglib动态代理,也叫子类代理。
JDK动态代理代码:
//接口
public interface TargetInterface {
public void save();
}
//目标对象(需要实现接口)
public class Target implements TargetInterface {
public void save() {
System.out.println("save running.....");
}
}
//增强对象(不需要实现接口)
public class Advice {
public void before(){
System.out.println("前置增强....");
}
public void afterReturning(){
System.out.println("后置增强....");
}
}
//调用
public class ProxyTest {
public static void main(String[] args) {
final Target target = new Target();
final Advice advice = new Advice();
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(), //目标对象类加载器
target.getClass().getInterfaces(), //目标对象相同的接口字节码对象数组
new InvocationHandler() {
//调用代理对象的任何方法 实质执行的都是invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.before(); //前置增强
Object invoke = method.invoke(target, args);//执行目标方法
advice.afterReturning(); //后置增强
return invoke;
}
}
);
//调用代理对象的方法
proxy.save();
}
}
cglib动态代理代码:
//目标对象
public class Target {
public void save() {
System.out.println("save running.....");
}
}
//增强对象
public class Advice {
public void before(){
System.out.println("前置增强....");
}
public void afterReturning(){
System.out.println("后置增强....");
}
}
//调用
public class ProxyTest {
public static void main(String[] args) {
final Target target = new Target();
final Advice advice = new Advice();
//返回值 就是动态生成的代理对象 基于cglib
//1、创建增强器
Enhancer enhancer = new Enhancer();
//2、设置父类(目标)
enhancer.setSuperclass(Target.class);
//3、设置回调
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
advice.before(); //执行前置
Object invoke = method.invoke(target, args);//执行目标
advice.afterReturning(); //执行后置
return invoke;
}
});
//4、创建代理对象
Target proxy = (Target) enhancer.create()
//调用代理对象的方法
proxy.save();
}
}
AOP :Aspect Oriented Programming(面向切面编程),是通过预编译方式和运行期动态代理(cglib)实现程序功能的统一维护的一种技术。其主要干的事情就是:把重复的代码抽取成切面类代码,在运行的时候在业务方法上动态植入切面类代码。
AOP 在程序运行期间,在不修改源码的情况下对方法进行功能增强。可减少重复代码,提高开发效率,并且便于维护。
AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象的方法,从而完成功能的增强。
使用xml开发AOP步骤:
①导入 AOP 相关坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
②创建目标接口和目标类(内部有切点)
public interface TargetInterface {
public void method();
}
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
}
}
③创建切面类(内部有增强方法)
public class MyAspect {
//前置增强方法
public void before(){
System.out.println("前置代码增强.....");
}
}
④将目标类和切面类的对象创建权交给 spring(在application.xml中配置)
<bean id="target" class="com.itheima.aop.Target"></bean>
<bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>
⑤在 applicationContext.xml 中配置织入关系
先要导入aop命名空间
<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/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
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
再配置织入关系
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
<aop:before method="before" pointcut="execution(public void com.itheima.aop.Target.method()) "></aop:before>
</aop:aspect>
</aop:config>
⑥测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.method();
}
}
上方代码pointcut="execution(public void com.itheima.aop.Target.method())部分是切点表达式,其语法为 execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 如execution(public void com.itheima.aop.Target.method())
- 访问修饰符可以省略 如execution(void com.itheima.aop.Target.method())
- 返回值类型、包名、类名、方法名可以使用星号* 代表任意 如execution(public * ..*())
- 包名与类名之间一个点 . 代表当前包下的类,两个点. . 表示当前包及其子包下的类 如execution(public void com.itheima.aop. . Target.method())
- 参数列表可以使用两个点. . 表示任意个数,任意类型的参数列表 如execution(public void com.itheima.aop.Target.method())
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<aop:pointcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"/>
<aop:before method="before" pointcut-ref="myPointcut"></aop:before>
</aop:aspect>
</aop:config>
使用注解开发aop步骤
①创建目标接口和目标类(内部有切点)
public interface TargetInterface {
public void method();
}
@Component("target")
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
}
}
②创建切面类(内部有增强方法)
@Component("myAspect")
@Aspect
public class MyAspect {
//前置增强方法
@Before("execution(* com.itheima.aop.*.*(..))")
public void before(){
System.out.println("前置代码增强.....");
}
}
③在配置文件中开启组件扫描和 AOP 的自动代理
<context:component-scan base-package="com.itheima.aop"/>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
④测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.method();
}
}
如果在同一个xml中有配置一样的aop,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在在增强注解中进行引用。具体如下:
@Component("myAspect")
@Aspect
public class MyAspect {
@Before("MyAspect.myPoint()")
public void before(){
System.out.println("前置代码增强.....");
}
@Pointcut("execution(* com.itheima.aop.*.*(..))")
public void myPoint(){}
}
Spring可以直接帮我们获取数据库连接池 配置文件中配置
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
代码中的dataSource可以直接获取
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“applicationContext.xml”);
DataSource dataSource = (DataSource)applicationContext.getBean(“dataSource”);
JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。
使用JdbcTemplate开发步骤:
①导入spring-jdbc和spring-tx坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
②创建数据库表和实体
③创建JdbcTemplate对象
public void test1() throws PropertyVetoException {
//创建数据源对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser("root");
dataSource.setPassword("root");
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//设置数据源对象 知道数据库在哪
jdbcTemplate.setDataSource(dataSource);
//执行操作
int row = jdbcTemplate.update("insert into account values(?,?)", "tom", 5000);
System.out.println(row);
}
一般我们使用JdbcTemplate,会将dataSource和jdbcTemplate对象创建交给spring:
<!--加载jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--数据源对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--jdbc模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
设置事务的隔离级别,可以解决事务并发产生的脏读、不可重复读和虚读等问题。 ISOLATION_DEFAULT ISOLATION_READ_UNCOMMITTED 读未提交 ISOLATION_READ_COMMITTED 读已提交 ISOLATION_REPEATABLE_READ 可重复读 ISOLATION_SERIALIZABLE 顺序读
事务的传播行为:常用的就是REQUIRED和REQUERS_NEW
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。是默认选项。
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
是否只读:建议查询时设置为只读
一般地,我们事务控制都是在service层做的。为什么要在service层而不是在dao层呢:service层是业务逻辑层,service的方法一旦执行成功,那么说明该功能没有出错。一个service方法可能要调用dao层的多个方法,如果在dao层做事务控制的话,一个dao方法出错了,仅仅把事务回滚到当前dao的功能,这样是不合适的(因为我们的业务由多个dao方法组成)。如果没有出错,调用完dao方法就commit了事务,这也是不合适的(导致太多的commit操作)。
事务控制分为两种:
①编程式事务控制:自己手动控制事务,事务的控制粒度较细,可以对指定的方法或指定方法的某几行添加事务控制。比较灵活,但开发起来比较繁琐,每次都要开启、 提交、回滚。如Conn.setAutoCommite(false)
②声明式事务控制:Spring提供的事务控制。
声明式事务的优势:事务管理不侵入开发的组件,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可;在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便。
如果用户想要使用Spring的事务控制,只需要配置就行了。当不用Spring事务的时候,直接移除就行了。Spring的事务控制是基于AOP实现的。因此它的耦合度是非常低的。Spring的事务控制是粗粒度的事务控制,只能给整个方法应用事务,不可以对方法的某几行应用事务。(因为aop拦截的是方法)
Spring提供了两种声明式事务管理器类:Jdbc或mybatis的DataSourceTransactionManager和Hibernate的HibernateTransactionManager
声明式事务,顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。
基于xml配置声明式事务步骤
①引入tx命名空间
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
②配置事务增强
<!--平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--事务增强配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
③配置事务 AOP 织入
<!--事务的aop增强-->
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.itheima.service.impl.*.*(..))"/> //配置完成后com.itheima.service.impl下的所有包中的所有类的所有方法都被Spring的声明式事务控制了
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"></aop:advisor>
</aop:config>
基于注解配置声明式事务步骤
①编写 AccoutDao
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void out(String outMan, double money) {
jdbcTemplate.update("update account set money=money-? where name=?",money,outMan);
}
public void in(String inMan, double money) {
jdbcTemplate.update("update account set money=money+? where name=?",money,inMan);
}
}
②编写 AccoutService
@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public void transfer(String outMan, String inMan, double money) {
accountDao.out(outMan,money);
int i = 1/0;
accountDao.in(inMan,money);
}
}
③编写 applicationContext.xml 配置文件
<!—省略datsSource、jdbcTemplate、平台事务管理器的配置-->
<!--组件扫描-->
<context:component-scan base-package="com.itheima"/>
<!--一定开启事务的注解驱动-->
<tx:annotation-driven/>
注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。使用在方法上,不同的方法可以采用不同的事务参数配置。