百度百科的介绍
Spring官方网址: http ://spring.io/
我们经常说的Spring其实指的是 Spring Framework (Spring 框架)
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。
在软件工程中,耦合指的就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类与架构之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。 划分模块的一个准则就是高内聚低耦合。
内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当适当做一件好事。
内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。 在进行软件设计时,应力争做到高内聚,低耦合 。
IoC( 核心中的核心 ):Inverse of Control,控制反转 。对象的创建权力由程序反转给Spring框架。
AOP:Aspect Oriented Programming,面向切面编程。在不修改目标对象的源代码情况下,增强IoC容器中Bean的功能。
DI:Dependency Injection,依赖注入。在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件中!!
所谓的IoC容器就是指的是Spring中 Bean工厂 里面的Map存储结构( 存储了Bean的实例 )。
ApplicationContext 接口()
实现了BeanFactory接口
实现ApplicationContext接口的工厂,可以获取到容器中具体的Bean对象
BeanFactory 工厂( 是Spring架构早期的创建Bean对象的工厂接口 )
实现BeanFactory接口的工厂也可以获取到Bean对象
其实通过源码分析,不管是BeanFactory还是ApplicationContext,其实最终的底层BeanFactory都是 DefaultListableBeanFactory
ApplicationContext和BeanFactory的区别?
创建Bean对象的时机不同:
BeanFactory采取延迟加载,第一次getBean时才会初始化Bean。
ApplicationContext是加载完applicationContext.xml时,就创建具体的Bean对象的实例。( 只对BeanDefition中描述为时单例的Bean,才进行饿汉堡式加载 )
ClassPathXmlApplicationContext:
它是从类的根路径下加载配置文件推荐使用这种
FileSystemXmlApplicationContext:
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
AnnotationConfigApplicationContext:
当我们使用注解配置容器对象时,需要使用此类来创建Spring容器。它用来读取注解。
ApplicationContext context= new ClassPathXmlApplicationContext (xml路径);
web.xml中配置ContextLoaderListener接口,并配置ContextConfigLocation参数
web容器启动之后加载web.xml,此时加载 ContextLoaderListener 监听器( 实现了ServletContextListener接口,该接口的描述请见下面的《三类八种监听器》 )
ContextLoaderListener监听器会在web容器启动的时候,出发 ContextInitialized ()方法
ContextInitialized()方法会调用 initWebApplicationContext ()方法,该方法负责创建Spring容器( DefaultListableBeanFactory )
【Web三类八种监听器】
监听 域对象 的生命周期
ServletContextListener:
创建:服务器启动
销毁:服务器正常关闭
spring ContextLoaderListener(服务器启动时负责加载spring配置文件)
HttpSessionListener
创建:第一次访问request.getHttpSession()
销毁:调用invalidate();非法关闭;过期
ServletRequestListener
创建:每一次访问
销毁:相应结束
监听域对象的属性:(添加、删除、替换)
ServletContextAttributeListener
HttpSessionAttributeListener
ServletRequestAttributeListener
监听HttpSession中JavaBean的改变:
HttpSessionBindingListener(HttpSession和JavaBean对象的绑定和解绑)
HttpSessionActivationListener(HttpSession的序列化,活化,纯化)
参考资料中的源码中的工程《Spring-sourcecode》
1.web服务器(tomcat)启动会加载web.xml(启动ContextLoaderListener监听器):
2.创建web环境中的Spring容器
3.ContextLoader类中创建Spring容器并初始化容器中的Bean实例
4.configureAndRefreshWebApplicationContext方法中调用初始化Bean的 refresh 方法
该图主要是分析上面第三步骤中【创建Spring容器】的图示
源码来源于 AbstractApplicationContext 类:
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); //1.创建真正的Spring容器(DefaultListableBeanFactory) //2.加载BeanDefition(描述要初始化的Bean的信息) //3.将BeanDefition注册到BeanDefitionRegistry // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); //执行实现了BeanFactoryPostProcessor接口的Bean //比如PropertyPlaceHolderConfigurer(context:property-placeholer)就是此处被调用的,替换掉BeanDefition中的占位符(${})中的内容 // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); //注册BeanPostProcessor(后置处理器) //比如容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器(实现@Autowired注解功能) // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); //初始化非懒加载方式的单例Bean实例 // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
POM文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.kkb</groupId> <artifactId>spring</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!-- spring 核心组件中的4个依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.0.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.0.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>5.0.7.RELEASE</version> </dependency> <!-- 单元测试Junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> <build> <plugins> <!-- 配置Maven的JDK编译级别 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
Spring配置文件(只编写配置文件头)
<?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"> </beans>
具体实现
在Spring的XML配置文件中配置一个 bean标签 ,该标签最终会被加载为一个 BeanDefition 对象(描述对象信息)
思路:
编写UserService接口的实现类
将UserService实现类交给Spring IoC容器管理
从Spring IoC容器中获取UserService实现类
编写接口:UserService
编写实现类:UserServiceImpl
编写XML文件:applicationContext.xml
编写单元测试代码:TestSpring
用于配置对象让Spring来创建
默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
id:给对象在 容器中 提供一个唯一标识。用于获取对象。
class:指定类的全限定类名。用于反射创建对象。默认情况下 调用无参构造函数 。
scope:指定对象的作用范围。
singleton : 默认值 ,单例的( 在整个容器中只有一个对象 )。
prototype :多例的。
request:web项目中,Spring创建一个Bean的对象,将对象存入到request域中。
session:web项目中,Spring创建一个Bean的对象,将对象存入到session域中。
global session:web项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession相当于session。
init-method:指定类中的初始化方法名称。
destroy-method :指定类中销毁方法名称。比如DataSource的配置中一般需要指定destroy-method="close"。
单例对象:scope=" singleton "
一个应用只有一个对象的实例。它的作用范围就是整个应用
生命周期:
对象出生:当应用加载,创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
多例对象:scope=" prototype "
每次访问对象时,都会重新创建对象实例。
生命周期:
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时,被java的垃圾回收期回收了。
第一种:使用默认无参构造函数(重点)
在默认情况下:它会根据默认无参构造函数来创建类对象。
如果bean中没有默认无参构造函数,将会创建失败
<bean id="userService" class="com.chenyanbin.spring.service.UserServiceImpl">
第二种:静态工厂(了解)
/** * 模拟一个静态工厂,创建业务层实现类 */ public class StaticFactory { public static UserService createUserService(){ return new UserServiceImpl(); } }
<!--此种方法时: 使用StaticFactory类中的静态方法createUserService创建对象,并存入Spring容器 id属性:指定bean的id,用于从容器中获取 class属性:指定静态工厂的全限定类名 factory-method属性:指定生产对象的静态方法 --> <bean id="userService" class="com.chenyanbin.spring.factory.StaticFactory" factory-method="createUserService"></bean>
第三种:实例工厂(了解)
/** * 模拟一个实例工厂,创建业务层实现类 * 此工厂创建对象,必须现有工厂实例对象,再调用方法 */ public class InstanceFactory { public UserService createUserService(){ return new UserServiceImpl(); } }
<!-- 此种方式是: 先把工厂的创建交给spring来管理。 然后在使用工厂的bean来调用里面的方法 factory-ben属性:用于指定实例工厂bean的id。 factory-method属性:用于指定实例工厂中创建对象的方法。 --> <bean id="instanceFactory" class="com.chenyanbin.factory.InstanceFactory"></bean> <bean id="userService" factory-bean="instanceFactory" factory-method="createUserService"></bean>
依赖指的就是Bean实例中的属性
属性分为:简单类型(8种基本类型和String类型)的属性、POJO类型的属性、集合数组类型的属性。
依赖注入:Dependency Injection。它是Spring框架核心IoC的具体实现。
我们的程序在编写时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况。
IoC解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。
简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
顾名思义,就是使用类中的构造函数,给成员变量赋值。
注意,赋值的操作不是我们自己做的,而是通过配置的方式,让Spring框架来为我们注入。
具体代码如下:
public class UserServiceImpl implements UserService { private int id; private String name; public UserServiceImpl(int id, String name) { this.id = id; this.name = name; } @Override public void saveUser() { System.out.println("保存用户:id为"+id+",name为"+name+" Service实现"); } }
<!--使用构造函数的方式,给Service中的属性传值要求:类中需要提供一个对应参数列表的构造函数。 涉及的标签:constructor-arg index:指定参数在构造函数参数列表的索引位置 name:指定参数在构造函数中的名称 value:它能赋的值是基本数据类型和String类型 ref:他能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean --> <bean id="userService" class="com.chenyanbin.spring.service.UserServiceImpl"> <constructor-arg name="id" value="1"></constructor-arg> <constructor-arg name="name" value="zhangsan"></constructor-arg> </bean>
set方法注入又分为 手动装配方式注入 和 自动装配方式注入 。
手动装配方式(XML方式):bean标签的子标签 property , 需要在类中指定set方法 。
自动装配方式(注解方式):@Autowired注解、@Resource注解。
@Autowired:一部分功能是 查询实例 ,从spring容器中根据 类型 (java类)获取对应的实例。另一部分功能就是 赋值 ,将找到的实例,装配和另一个实例的属性值。( 注意事项:一个java类型在同一个spring容器中,只能有一个实例 )
@Resource:一部分功能是 查询实例 ,从spring容器中根据 Bean的名称 (Bean标签的名称)获取对应的实例。另一部分功能就是 赋值 ,将找到的实例,装配给另一个实例的属性值。
使用p名称空间注入数据(本质上还是调用set方法)
1.步骤一:需要先引入p名称空间
在schema的名称空间中加入该行:
xmlns:p="http://www.springframework.org/schema/p"
2.步骤二:使用p名称空间的语法
p:属性名="" p:属性名-ref=""
3.步骤三:测试
<bean id="person" class="com.chenyanbin.spring.demo.Person" p:pname="隔壁老王" p:car2-ref="car2" /> <bean id="car2" class="com.chenyanbin.spring.demo.Car2" />
<bean id="userService" class="com.chenyanbin.spring.service.UserServiceImpl"> <property name="id" value="1"></property> <property name="name" value="zhangsan"></property> </bean>
ref就是reference的缩写,是引用的意思
<bean id="userService" class="com.chenyanbin.spring.service.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <bean id="userDao" class="com.chenyanbin.spring.dao.UserDaoImpl"></bean>
1.如果是数组或者List集合,注入配置文件的方式是一样的
<bean id="collectionBean" class="com.chenyanbin.demo5.CollectionBean"> <property name="arrs"> <list> <!--如果集合内是简单类型,使用value子标签,如果是POJO类型,则使用bean标签--> <value>张三</value> <value>李四</value> <value>王五</value> </list> </property> </bean>
2.如果是Set集合,注入的配置文件方式如下:
<property name="sets"> <set> <!--如果集合内是简单类型,使用value子标签,如果是POJO类型,则使用bean标签--> <value>哈哈</value> <value>喜喜</value> </set> </property>
3.如果是Map集合,注入的配置方式如下
<property name="map"> <map> <entry key="老王" value="18" /> <entry key="王五" value="19" /> </map> </property>
4.如果是 Properties 集合的方式,注入的配置如下:
<property name="pro"> <props> <prop key="userName">root</prop> <prop key="userPassword">123</prop> </props> </property>
学习基于注解的IoC配置,大家脑海里首先得有一个认识,即 注解配置和xml配置要实现的功能都是一样的 ,都是要降低程序间的耦合。只是配置的形式不一样。
关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以 这两种配置方式都需要掌握 。
在讲解注解配置时,采用上一章节的案例,把Spring的xml配置内容改为使用注解逐步实现。
相当于:<bean id="" class=""></bean>
@Component注解
作用: 把资源让Spring来管理。相当于在XML中配置一个bean。 属性: value:指定bean的id。 如果不指定value属性,默认bean的id时当前类的类型。首字母小写。
@Controller、@Service、@Repository注解
他们三个注解都是针对@Component的衍生注解 他们的作用及属性都是一摸一样的。他们只不过是提供了更加明确的语义化。 @Controller:一般用于表现层的注解。 @Service:一般用于业务层的注解。 @Repository:一般用于持久层的注解。 细节:如果注解中有且只有一个属性要赋值时,且名称是value,value在赋值是可以不写。
相当于:<property name="" ref="">
@Autowired
默认按 类型装配 (byType)
这个注解是spring自身的
默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如: @Autowired(required=false)
如何我们想使用名称装配可以结合 @Qualifier注解 进行使用
@Qualifier
在自动按照类型注入的基础之上,再按照Bean的id注入。
它在给 字段注入 时不能独立使用, 必须和@Autowire一起使用 ;但是给方法参数注入时,可以单独使用。
@Resource
默认按照 名称 (byName)进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取 字段名 进行按照名称查询,当找不到域名称匹配的bean时才按照 类型 进行装配。
但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
推荐使用@Resource注解,因为这个注解是属于J2EE的,减少了与spring的耦合。这样代码看起来就比较优雅。
相当于:<property name="" value="">
@Value
给基本类型和String类型注入值
可以使用占位符获取属性文件中的值。
@Value("${ name }") //name是properties文件中的key
@Scope
相当于:<bean id="" class="" scope="" >
作用:
指定bean的作用范围
属性:
value:指定范围的值
取值: singleton prototype request session globalsession
相当于:<bean id="" class="" init-method="" destroy-method="" >
@PostConstruct和@PreDestroy
注解的优势:
配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
XML的优势:
修改时,不用改源码。不涉及重新编译和部署。
Spring管理Bean方式的比较
到这里,基于注解的IoC配置已经完成,但是大家都发现了一个问题:我们依然离不开spring的xml配置文件, 那么能不能不写这个applicationContext.xml,所有配置都用注解来实现呢?
需要注意以下,我们选择那种配置的原则是简化开发和配置方便,而非追求某种技术。
想一想能不能将以下这些bean的配置都从xml中去掉,并且最终将xml也去掉。
如果可以,那么我们就可以脱离xml配置了。
<!--开启注解并扫描指定包中带有注解的类--> <context:component-scan base-package="com.chenyanbin.spring.service" />
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" value="dataSource"></property> </bean>
之前创建ApplicationContext都是通过读取XML文件进行创建的。 ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
介绍:
从Spring3.0,@Configuration用于定义 配置类 ,可 替换XML配置文件
相当于<beans>根标签
配置类 内部包含有一个或多个被 @Bean 注解的方法,这些方法将会被 AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类进行扫描,并用于构建bean定义,初始化Spring容器。
属性:
value:用于指定配置类的字节码
示例代码:
@Configuration public class SpringConfiguration { //spring容器初始化时,会调用配置类的无参构造函数 public SpringConfiguration(){ System.out.println(“容器启动初始化。。。”); } }
介绍:
@Bean标注在方法上(返回某个实例的方法),等价于spring配置文件中的<bean>
作用:
注册bean对象
主要用来配置非自定义的bean,比如DruidDataSource、SqlSessionFactory
属性:
name:给当前@Bean注解方法创建的对象指定一个名称(即bean的id)。
如果不指定, 默认与标注的方法名相同
@Bean注解 默认作用域为单例singleton作用域 ,可通过@Scope("prototype")设置为原型作用域;
示例代码:
public class SpringConfiguration { //spring容器初始化时,会调用配置类的无参构造函数 public SpringConfiguration(){ System.out.println(“容器启动初始化。。。”); } @Bean @Scope(“prototype”) public UserService userService(){ return new UserServiceImpl(1,“张三”); } }
介绍:
相当于context:component-scan标签
组件扫描器,扫描@Component、@Controller、@Service、@Repository注解的类。
该注解是编写在类上面的, 一般配合@Configuration 注解一起使用。
属性:
basePackages:用于指定要扫描的包
value:和basePackages作用一样
示例代码:
Bean类(Service类):
@Service public class UserServiceImpl implements UserService { @Override public void saveUser() { System.out.println("保存用户 Service实现"); } }
配置类:
@Configuration @ComponentScan(basePackages="com.kkb.spring.service") public class SpringConfiguration { public SpringConfiguration() { System.out.println("容器初始化..."); } // @Bean // @Scope("prototype") // public UserService userService() { // return new UserServiceImpl(1,"张三"); // } }
介绍
加载properties配置文件
编写在类上面
相当于 context:property-placeholder 标签
属性
value[]:用于指定properties文件路径,如果在类路径下,需要写上classpath
示例代码
配置类:
@Configuration @PropertySource(“classpath:jdbc.properties”) public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** *创建一个数据源,并存入 spring 容器中 *@return */ @Bean(name="dataSource") public DataSource createDataSource() { try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds; } catch (Exception e) { throw new RuntimeException(e); } } }
properties文件:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///spring jdbc.username=root jdbc.password=root
问题:
当系统中有多个配置类时怎么办?想一想之前使用XML配置的时候时如何解决该问题的。
介绍
用来组合多个配置类
相当于spring配置文件中的 Import标签
在引入其他配置类时,可以不用再写@Configuration注解。当前,写上也没问题。
属性
value:用来指定其他配置类的字节码文件
示例代码
@Configuration @ComponentScan(basePackages = "com.kkb.spring") @Import({ JdbcConfig.class}) public class SpringConfiguration { } @Configuration @PropertySource("classpath:jdbc.properties") public class JdbcConfig{ }
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class); UserService service = context.getBean(UserService.class); service.saveUser();
<web-app> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context. support.AnnotationConfigWebApplicationContext </param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value> com.kkb.spring.test.SpringConfiguration </param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> </web-app>
分模块开发场景描述
表现层:spring配置文件,只想管理表现层的Bean
业务层:spring配置文件,只想管理业务层的Bean,并且进行事务控制
持久层:spring配置文件,只想管理持久层的Bean,并且还有需要管理数据源的Bean
为了方便管理项目中不同层的Bean对象,一般都是将一个spring配置文件,分解为多个spring配置文件。
分解之后的spring配置文件如何一起被加载呢?
一种就是同时指定多个配置文件的地址一起加载
另一种就是:定义一个import.xml文件,通过import标签将其他多个spring配置文件导入到该文件中,tomcat启动时只需要加载import.xml就可以。
@Autowired注解,它是如何生效的? AutowiredBeanPostProcessor类
XML配置方式
注解+XML配置方式
@Component
@Controller
@Service
@Repository
@Autowired
@Resource
纯注解方式
@Configuration
@Bean
@ComponentScan
@PropertySource
@Import
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService service1 = context.getBean(UserService.class);
我们使用单元测试要测试的是业务问题,以上两端代码明显不是 业务代码 。
但是这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建spring容器,我们就无需手动创建了,问题也就解决了。
但紧接的问题就是Juit它本身不认识spring,更无法帮助创建spring容器了,不过好在Junit给我们暴露一个注解( @RunWith ),可以让我们替换掉它的运行器。
这时,我们需要依靠 spring框架 ,因为 它提供了一个运行器 ,可以读取配置文件(或注解)来创建容器。我们只需告诉它配置文件在哪就行了。
添加spring-test包即可
Spring的运行器是SpringJunit4ClassRunner
什么是AOP?
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向 切面 编程
AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
AOP最早由AOP联盟的组织提出的,制定了一套规范,spring将AOP思想引入到框架中,必须遵守AOP联盟的规范
通过预编译方式和 运行期动态代理 实现程序功能的统一维护的一种技术
AOP是OOP的延续,是软件开发中的一个热点,也是spring框架中的一个重要内容,是函数式编程的一种衍生范式
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
作用:
AOP采用 横向抽取 机制,取代了 传统纵向继承体系 重复性代码(性能监视、事务管理、安全检查、缓存)
在程序运行期间,不修改源码对已有方法进行增强。
将业务逻辑和系统处理的代码( 关闭连接、事务管理、操作日志记录 )解耦。
优势:
AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器)
可以这样说 AspectJ是目前实现AOP框架中最成熟 ,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用非常容易。
了解AspectJ应用到java代码的过程(这个过程称为织入),对于织入这个概念,可以简单理解为Aspect(切面)应用到目标函数(类)的过程。
对于这个过程,一般分为 动态织入和静态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中 ,这样往往时通过 动态代理技术完成的 。如java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术。
AspectJ采用的就是静态织入的方式。AspectJ主要采用的是编译期织入,这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
Spring AOP是通过 动态代理技术 实现的,而动态代理是基于 反射 设计的。
动态代理技术的实现方式有两种:基于接口的JDK动态代理和基于继承的CGLib动态代理。
JDK动态代理
目标对象必须实现接口
使用Proxy类来生成代理对象的一些代码如下
/** * 使用JDK的方式生成代理对象 * @author Administrator */ public class MyProxyUtils { public static UserService getProxy(final UserService service) { // 使用Proxy类生成代理对象 UserService proxy = (UserService) Proxy.newProxyInstance( service.getClass().getClassLoader(), service.getClass().getInterfaces(), new InvocationHandler() { // 代理对象方法一执行,invoke方法就会执行一次 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("save".equals(method.getName())){ System.out.println("记录日志..."); // 开启事务 } // 提交事务 // 让service类的save或者update方法正常的执行下去 return method.invoke(service, args); } }); // 返回代理对象 return proxy; } }
CGLib动态代理
目标对象不需要实现接口
底层是通过继承目标对象产生代理子对象(代理子对象中继承了目标对象的方法,并可以对该方法进行增强)
public static UserService getProxy(){ // 创建CGLIB核心的类 Enhancer enhancer = new Enhancer(); // 设置父类 enhancer.setSuperclass(UserServiceImpl.class); // 设置回调函数 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if("save".equals(method.getName())){ // 记录日志 System.out.println("记录日志了..."); } return methodProxy.invokeSuper(obj, args); } }); // 生成代理对象 UserService proxy = (UserService) enhancer.create(); return proxy; }
a、开发阶段(我们做的)
编写 核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公司代码抽取出来,制作成 通知 。(开发阶段最后在做):AOP编程人员来做。
在配置文件中,声明切入点与通知间的关系,即 切面 。:AOP编程人员来做。
b、运行阶段(Spring框架完成的)
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
编写接口和实现类(目标对象)
UserService接口
UserServiceImpl实现类
配置目标类,将目标类交给Spring IoC容器管理
实现步骤
POM.XML
<!-- 基于AspectJ的aop依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.7.RELEASE</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency>
编写 通知 (增强类,一个普通的类)
配置通知,将通知类交给Spring IoC容器管理
配置AOP切面
切入点表达式
execution( [修饰符] 返回值 包名.类名.方法名(参数) )
execution:必须有
修饰符:可省略
返回值类型: 必须要 ,但是可以使用*通配符
包名:
多级包之间使用.分割
包名可以使用*代替,多级包名可以使用多个*代替
如果想省略中间的报名可以使用..
类名:
可以使用*代替
也可以写成*DaoImpl
方法名:
也可以使用*代替
也可以写成add*
参数:
参数使用*代替
如果有多个参数,可以使用..代替
通知类型(五种):前置通知、后置通知、最终通知、环绕通知、异常抛出通知。
前置通知:
执行时机: 目标对象方法之前执行通知
配置文件:< aop:before method="before" pointcut-ref="myPointcut" />
应用场景:方法开始时可以进行校验
后置通知:
执行时机: 目标对象方法之后执行通知,有异常则不执行了
配置文件:< aop:after-returning method="afterReturning" pointcut-ref="myPointcut" />
应用场景:可以修改方法的返回值
最终通知:
执行时机: 目标对象方法之后执行通知,有没有异常都会执行
配置文件 :< aop:after method="after" pointcut-ref="myPointcut" />
应用场景:例如像释放资源
环绕通知 :
执行时机:目标对象方法之前和之后都会执行。
配置文件:< aop:around method="around" pointcut-ref="myPointcut" />
应用场景:事务、统计代码执行时机
异常抛出通知:
执行时机:在抛出异常后通知
配置文件:< aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" />
应用场景:包装异常
编写 切面类 (注意不是 通知类 ,因为该类中可以指定 切入点 )
<context:component-scan back-package="com.chenyanbin.spring" />
@Around
作用:
把当前方法看成是环绕通知属性。
value:
用于指定切入点表达式,还可以指定切入点表达式的引用。
使用@PointCut注解在切面类中定义一个通用的切入点,其他通知可以引用该切入点
@Configuration
@ComponentScan(basePackages="com.chenyanbin")
@EnableAspectJAutoProxy
public class SpringConfiguration{
}
POM.XML
编写测试代码
@Test public void run1(){ // 创建连接池,先使用Spring框架内置的连接池 DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql:///spring "); dataSource.setUsername("root"); dataSource.setPassword("root"); // 创建模板类 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); // 完成数据的添加 jdbcTemplate.update("insert into t_account values (null,?,?)", "测试",10000); }
步骤一:Spring管理内置的连接池
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///spring_day03"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean>
步骤二:Spring管理模板类
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean>
步骤三:编写测试程序
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class Demo2{ @Resource(name="jdbcTemplate") private JdbcTemplate jdbcTemplate; @Test public void run(){ jdbcTemplate.update("insert into t_account values (null,?,?)", "测试2",10000); } }
管理DBCP连接池 * 先引入DBCP的2个jar包 * com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar * com.springsource.org.apache.commons.pool-1.5.3.jar 如果是maven环境,需要填写GAV坐标 * 编写配置文件 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///spring "/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> 管理C3P0连接池 * 先引入C3P0的jar包 * com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar * 编写配置文件 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql:///spring"/> <property name="user" value="root"/> <property name="password" value="root"/> </bean>
增删改查的操作 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class SpringDemo { @Resource(name="jdbcTemplate") private JdbcTemplate jdbcTemplate; @Test // 插入操作 public void demo1(){ jdbcTemplate.update("insert into account values (null,?,?)", "冠希",10000d); } @Test // 修改操作 public void demo2(){ jdbcTemplate.update("update account set name=?,money =? where id = ?", "思雨",10000d,5); } @Test // 删除操作 public void demo3(){ jdbcTemplate.update("delete from account where id = ?", 5); } @Test // 查询一条记录 public void demo4(){ Account account = jdbcTemplate.queryForObject("select * from account where id = ?", new BeanMapper(), 1); System.out.println(account); } @Test // 查询所有记录 public void demo5(){ List<Account> list = jdbcTemplate.query("select * from t_account", new BeanMapper()); for (Account account : list) { System.out.println(account); } } } class BeanMapper implements RowMapper<Account>{ public Account mapRow(ResultSet rs, int arg1) throws SQLException { Account account = new Account(); account.setId(rs.getInt("id")); account.setName(rs.getString("name")); account.setMoney(rs.getDouble("money")); return account; } }
1. 步骤一:创建WEB工程,引入需要的jar包 * IOC的6个包 * AOP的4个包 * C3P0的1个包 * MySQL的驱动包 * JDBC的2个包 * 整合JUnit测试包 2. 步骤二:引入配置文件 * 引入配置文件 * 引入log4j.properties * 引入applicationContext.xml <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql:///spring "/> <property name="user" value="root"/> <property name="password" value="root"/> </bean> 3. 步骤三:创建对应的包结构和类 * com.kkb.demo1 * AccountService * AccountServlceImpl * AccountDao * AccountDaoImpl 4. 步骤四:引入Spring的配置文件,将类配置到Spring中 <bean id="accountService" class="com.kkb.demo1.AccountServiceImpl"> </bean> <bean id="accountDao" class="com.kkb.demo1.AccountDaoImpl"> </bean> 5. 步骤五:在业务层注入DAO ,在DAO中注入JDBC模板(强调:简化开发,以后DAO可以继承JdbcDaoSupport类) <bean id="accountService" class="com.kkb.demo1.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> </bean> <bean id="accountDao" class="com.kkb.demo1.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> 6. 步骤六:编写DAO和Service中的方法 public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { public void outMoney(String out, double money) { this.getJdbcTemplate().update("update t_account set money = money = ? where name = ?", money,out); } public void inMoney(String in, double money) { this.getJdbcTemplate().update("update t_account set money = money + ? where name = ?", money,in); } } 7. 步骤七:编写测试程序. @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class Demo1 { @Resource(name="accountService") private AccountService accountService; @Test public void run1(){ accountService.pay("冠希", "美美", 1000); } }
事务:指的是逻辑上一组操作,组成这个事务的各个执行单元,要么一起成功,要么一起失败!
读问题解决,设置数据库隔离级别
1. 说明:Spring为了简化事务管理的代码:提供了模板类 TransactionTemplate,所以手动编程的方式来管理事务,只需要使用该模板类即可!! 2. 手动编程方式的具体步骤如下: 1. 步骤一:配置一个事务管理器,Spring使用PlatformTransactionManager接口来管理事务,所以咱们需要使用到他的实现类!! <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> 2. 步骤二:配置事务管理的模板 <!-- 配置事务管理的模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> </bean> 3. 步骤三:在需要进行事务管理的类中,注入事务管理的模板 <bean id="accountService" class="com.itheima.demo1.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> <property name="transactionTemplate" ref="transactionTemplate"/> </bean> 4. 步骤四:在业务层使用模板管理事务: // 注入事务模板对象 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } public void pay(final String out, final String in, final double money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { // 扣钱 accountDao.outMoney(out, money); int a = 10/0; // 加钱 accountDao.inMoney(in, money); } }); }
声明式事务管理又分成两种方式
准备转账环境
业务层:
AccountService
AccountServiceImpl
持久层
AccountDao
AccountDaoImpl
spring配置
单元测试代码:
配置事务管理的AOP
平台事务管理器:DataSourceTransactionManager
事务通知:<tx:advice id="" transaction-manager="" />
AoP配置
<aop:config>
<aop:advisor advice-ref="" pointcut="" />
</aop:config>
Service类上或者方法上加注解:
类上加@Transactional:表示该类中所有方法都被事务管理
方法上加@Transactional:表示只有改方法被事务管理
开始事务注解: