Spring框架
Spring基本配置
1、导入对应jar包:
官方文档:
The org.springframework.beans and org.springframework.context packages
are the basis for Spring Framework’s IoC container.
我这里是使用的Maven管理项目。实际上这两个包还依赖以下几个包:
spring-aop、spring-core、spring-expression、spring-jcl
maven会帮我们自动导入。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
2、编写配置文件
很多人不知道基础的xml配置文件哪里找。
实际上在官方文档里就一节专门写了几种[SpringXMLSchema](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#appendix)。
这里我们进行最基础的配置,使用“10.1.4. The Beans Schema”中的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 https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person1" name="person1" class="beans.Person">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
</beans>
3、进行测试
ClassPath指向target/classes/下。
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Person person1 = (Person) context.getBean("person1");
System.out.println(person1);
Spring基础知识
Bean的id和name的区别
配置一个bean的时候,我们可以不设置id,也可以不设置name,spring默认会使用类的全限定名作为bean的标识符。
id和name都必须唯一。
只有id或name时,id/name就是Bean的标识符。
既有id又有name时,name相当于Bean的别名。
name可以写多个值,id只能写一个值。
name属性设置多个值。不设置id,那么第一个被用作标识符,其他的被视为别名。如果设置了id,那么name的所有值都是别名。
为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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person1" name="person1" class="beans.Person">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<alias name="person1" alias="p1"/>
</beans>
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Person person1 = (Person) context.getBean("p1");//p1是person1的别名
System.out.println(person1);
三种组装Bean的方式
若无任何配置,Spring容器会使用无参的构造函数进行实例化Bean。
1、构造器注入(根据设置的参数,调用不同的构造函数,没有符合的构造函数时,配置文件就会报错)。
<!--构造器注入-->
<bean name="person-constructor" class="beans.Person">
<constructor-arg name="age" value="20"/>
<constructor-arg name="name" value="Mark"/>
<constructor-arg name="nation" value="China"/>
</bean>
2、静态工厂注入。
<!--静态工厂注入-->
<!--factory-method指明由哪个方法实例化-->
<bean name="person-static-factory" class="beans.Person" factory-method="factory">
</bean>
//该方法要写在Person类中
public static Person factory() {
return new Person("Mark", 18, "China");
}
3、实例工厂注入。
<!--实例工厂注入-->
<!--工厂bean-->
<bean id="InstancingFactory" class="beans.PersonFactory"/>
<!--待实例化bean-->
<bean name="person-instancing-factory" class="beans.Person" factory-bean="InstancingFactory" factory-method="InstancingPerson"/>
//实例工厂
public class PersonFactory {
public Person InstancingPerson(){
return new Person("a",18,"b");
}
}
依赖注入
如下代码所示,Circle类依赖Point类。
public class Circle {
private Point center;
private Float radius;
}
如何向Circle类注入Point类?
1、基于构造器的依赖注入。编写带有Point参数的构造函数,实例化时就可以注入。
<!--构造器依赖注入(本质上就是实例化复合bean)-->
<bean name="circle-constructor-DI" class="beans.Circle">
<constructor-arg name="center" ref="center"/>
<constructor-arg name="radius" value="18.5"/>
</bean>
<bean id="center" name="center" class="beans.Point"/>
2、基于set方法的依赖注入。编写center的set方法。
<!--set方法依赖注入-->
<bean name="circle-set-DI" class="beans.Circle">
<property name="center" ref="center"/>
<property name="radius" value="16.5"/>
</bean>
注意流程:想要使用类的set方法,该类首先得实例化。
set注入属性可以和组装bean一起使用。先组装bean后使用set方法。
若类没有提供对应的set方法,那么配置文件就会报错。
set注入集合示例
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
设置空字符串和null值
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
等同于:exampleBean.setEmail("");
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
等同于:exampleBean.setEmail(null);
depends-on
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
确保manager在beanOne实例化之前被实例化。
lazy-init
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
确保在第一次使用该bean时,才实例化
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
为全局添加lazy-init属性
Scope
1、singleton(默认)
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
spring容器只会创建一个实例对象。
2、prototype
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
每次引用都会创建一个新对象。
3、request
4、session
5、application
6、websocket
后四种详细介绍请看官网:
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes
初始化和销毁函数
自定义初始化和销毁函数
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
init-method = "函数方法名"指定函数名称
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
实现初始化和销毁函数的方式不只这一种,
还可以通过实现特定接口:The InitializingBean and DisposableBean callback interfaces
和注解的方法:The @PostConstruct and @PreDestroy annotations.
来实现
注意:这三种方法设定的函数互不影响,会按指定的顺序执行:Combining Lifecycle Mechanisms(该标题下有详细的顺序)
同时可以对整个容器的bean设置
<beans default-init-method="init">
Bean的继承
Bean定义的继承
child会继承parent的属性(除开: depends on, autowire mode, dependency check, singleton, and lazy init.)
同一属性会覆盖
<!--Bean Definition inherent-->
<bean name="parent-person" class="beans.Person">
<property name="age" value="18"/>
<property name="name" value="aaa"/>
</bean>
<bean name="child-person" class="beans.Person" parent="parent-person">
<property name="age" value="25"/>
<property name="nation" value="China"/>
</bean>
使用注解配置Bean
使用新配置文件
10.1.3. The context Schema
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--<context:annotation-config/>-->
<!--根据官方文档,该配置能隐式启动上面的开启注解驱动配置,所以只需要这一个即可-->
<context:component-scan base-package="beans"/>
</beans>
Spring整合Junit单元测试
1、添加jar。
<!--开启测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version> 5.1.5.RELEASE</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13-beta-2</version>
<scope>compile</scope>
</dependency>
2、编写测试方法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:application-annotation.xml"})
public class TestAnnotation {
/**
* @Autowired
* 自动装配
* @Qualifier
* 当配置文件中有多个同类型的Bean时,可加上Qualifier标签,唯一标志一个bean
* 同时在要自动注入的bean上加上@Qualifier注解,表明要导入那个bean
*/
@Autowired
@Qualifier("bread")
private Food food;
@Test
public void test2(){
System.out.println(food);
}
}
后续测试都在整合了的单元测试中进行。
@Autowired和@Qualifier
<!--注册Food为Bean-->
<bean name="bread" class="beans.Food">
<property name="name" value="面包"/>
<property name="price" value="18.5"/>
<qualifier value="breadA"/>
</bean>
/**
* @Autowired
* 自动装配,根据类型匹配。当有多个同类型的Bean时,需加上@Qualifier指定要装配的Bean的name。
* Autowired在有多个同类型bean存在时,默认匹配与当前变量名同名的bean,没有或者有多个则需要
* Quanlifier注解
*
* @Qualifier
* 当配置文件中有多个同类型的Bean时,可加上Qualifier标签,唯一标志一个bean
* 同时在要自动注入的bean上加上@Qualifier注解,表明要导入那个bean
*/
@Autowired
@Qualifier("breadA")
private Food food;
@Autowired还可以加在set方法和构造器上
@Autowired
Person jack
等同于
@Autowired
@Quanlifer("jack")
Person XXX
@Autowired
public void setAnimal(Animal animal){
this.animal = animal;
}
public BussinessPerson(@Autowired @Qualifier("dog") Animal animal){
this.animal = animal;
}
@Primary
@Component
@Primary
public class Cat implements Animal {
@Override
public void name() {
System.out.println("cat");
}
}
@Component()
public class Dog implements Animal{
@Override
public void name() {
System.out.println("dog");
}
}
/**
* Dog和Cat都实现了Animal接口并且使用注解注册为bean
* 在当前情况下除了使用@Qualifier进行指定还可以使用@Primary
* Spring会加载使用了@Primary注解的类
* 本例中会加载Cat类
*/
@Autowired
private Animal animal;
@Resource
<bean name="coke" class="beans.Food">
<property name="name" value="可乐"/>
<property name="price" value="2.5"/>
</bean>
/**
* @Resource(name="beanName")
* 功能与Autowired一致,按照beanName进行自动注入
* 若不指定name属性,则会根据变量名进行匹配
*/
@Resource(name = "coke")
private Food cokeByName;
@Resource
private Food coke;
@PostConstruct和@PreDestroy
/**
* @PostConstruct
* 定义初始化方法
* @PreDestroy
* 定义销毁方法
*/
@PostConstruct
public void init(){
System.out.println("init:"+this.name);
}
@PreDestroy
public void destroy(){
System.out.println("destroy:"+this.name);
}
@Repository、@Service、@Component、@Controller
需要开启包扫描
/**
* 通过注解注册Bean
* @Repository
* @Service
* @Component
* @Controller
* 这四个注解功能是一样的
*
* @Component("name")可以指定装配Bean的name,如果不指定,则会使用类名首字母小写作为name
*
* 必须先开启包扫描(可通过XML或者配置类的形式开启)
* Spring才会主动扫描包下添加了注解的类
*
* 想要使用注解,必须开启注解驱动
* 想要使用注解注册Bean必须配置包扫描
*
* 包扫描的配置隐式启动了注解驱动配置,所以只配置这一个即可(配置了包扫描就无需显式开启注解驱动)
* There is usually no need to include the <context:annotation-config> element when using <context:component-scan>.
*/
@Autowired
private MyDao myDao1;
@Autowired
private MyDao myDao2;
@Repository
public class MyDao {
}
@Scope
可以配置Bean的单例和多例模式(实际上Scope的属性不止这几种)
@Scope("prototype")
@Scope("singleton")
加在类上即可。
@Scope("prototype")
public class MyDao {
}
SpringAOP
基本概念
连接点:可以被切入方法的点。
切入点:已经被切入了方法的点。
通知:指切入的方法。分为前置、后置、等通知。
切面:切入点和通知的结合。
织入:指把通知应用到目标对象创建代理对象的过程
简单来讲,通知是要对原始类进行强化的方法,切点是决定通知加在那里,而切面正是包含了通知和切点。
切面表现为一个类,类中配置了切点,和通知(通知表现为类方法)。
执行顺序
注意
只有被当作bean注入的类的方法,才会被aop织入
单纯new一个类执行方法不会得到预期的结果
配置
1、引入jar包。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
2、配置文件。
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<aop:aspectj-autoproxy/>
<context:component-scan base-package="aop"/>
</beans>
采用注解方式配置
@Service
public class MyService {
public void doServiceWork(String param){
System.out.println("service进行业务逻辑操作");
}
}
@Component
@Aspect //声明切面
@Order(1) //存在多个切面时,指定切面执行的顺序
public class ServiceExecutor {
//execution表达式可参照 Declaring a PointCut中的Example章节
@Pointcut("execution(* aop.MyService.*(..))")
public void myPointcut(){}
//一种是写切入点,由切入点找到要切入的方法
@Before("myPointcut()")
public void beforeMyPointcut(){
System.out.println("beforeMyPointcut");
}
//一种是写要切入的方法
// joinPoint可以获取代理对象方法的各种信息,包括参数
// args()可以将代理方法的对应参数传入通知中
@Before("execution(* aop.MyService.*(..)) && args(param)")
public void before(JoinPoint joinPoint,String param){
System.out.println("前置通知,方法调用前执行");
}
@AfterReturning("execution(* aop.MyService.*(..))")
public void afterReturning() {
System.out.println("后置通知,返回后调用,遇到异常时不调用");
}
@AfterReturning(value="execution(* aop.MyService.*(..))", returning="retVal")
public void afterReturningWithReturn(Object retVal) {//可以获取返回对象
System.out.println("后置通知,该方法返回值是:"+retVal);
}
@AfterThrowing("execution(* aop.MyService.*(..))")
public void afterThrowing() {
System.out.println("方法发生异常时被执行");
}
@AfterThrowing(value="execution(* aop.MyService.*(..))",throwing="ex")
public void afterThrowingWithEx(Exception ex) {//异常通知可以获取异常
System.out.println("异常通知:"+ex.getMessage());
ex.printStackTrace();
}
@After("execution(* aop.MyService.*(..))")
public void after() {
System.out.println("最终通知,方法返回后执行,无论是否发生异常都会执行");
}
/**
* ProceedingJoinPoint pjp
* 可以获取更多目标对象执行的方法的信息
* getThis(): Returns the proxy object.
* getArgs(): Returns the method arguments.
* getTarget(): Returns the target object.
* getSignature(): Returns a description of the method that is being advised.
* toString(): Prints a useful description of the method being advised.
*/
@Around("execution(* aop.MyService.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知,方法开始前");
Object retVal = pjp.proceed(); //retVal是目标对象执行方法返回的结果
System.out.println("环绕通知,方法返回后");
return retVal;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:application-aspect.xml"})
public class TestAOP {
/**
*只有被当作bean注入的类的方法,才会被aop织入
*单纯new一个service执行操作不会得到预期的结果
*/
@Autowired
private MyService service;
@Test
public void test1(){
service.doServiceWork();
}
}
采用XML方式配置
//定义切面类,里面的方法为通知
public class ServiceExecutorXML {
public void before(){
System.out.println("this is xml before");
}
}
在配置文件中配置切面
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<aop:aspectj-autoproxy/>
<context:component-scan base-package="aop"/>
<!--基于XML编写切面-->
<!--将切面注册为bean-->
<bean id="serviceExecutorXML" class="aop.ServiceExecutorXML"/>
<!--aop配置结点-->
<aop:config>
<!--配置切面-->
<aop:aspect id="aspect" ref="serviceExecutorXML">
<!--定义切点-->
<aop:pointcut id="p1" expression="execution(* aop.MyService.*(..))"/>
<!--编写通知-->
<aop:before method="before" pointcut-ref="p1"/>
</aop:aspect>
</aop:config>
</beans>
配置事务
针对Service层进行事务管理。
事务只针对范围(由execution表达式确定)里的方法起作用,一个方法里的行为要么全都成功要么全都失败。
所以一套完整的业务流程要写在一个方法中。
XML方式配置
1、导入jar包。
<!--配置事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.20.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5</version>
</dependency>
2、事务管理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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置事务的schema,需要导入spring-tx.jar-->
<!--配置事务管理器-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--REQUIRED:支持当前事务,如果不存在则新建,用于增删改业务-->
<!--SUPPORTS:支持当前事务,如果不存在,不新建,用于查询业务-->
<!--name:要添加事务的方法名,可以有通配符-->
<tx:method name="doServiceWork" propagation="REQUIRED"/>
<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--切面(将通知织入切入点)-->
<aop:config>
<!--切入点-->
<aop:pointcut id="MyServiceTXPointCut" expression="execution(* aop.MyService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="MyServiceTXPointCut"/>
</aop:config>
<!--引入外部properties文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据源-->
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!--还可以通过property配置更多属性,如最大连接数-->
</bean>
</beans>
可以看见Spring事务管理使用了AOP(XML配置形式)。
注解形式(很少使用)
1、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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置事务的schema,需要导入spring-tx.jar-->
<!--配置事务管理器-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启注解事务管理-->
<tx:annotation-driven/>
<!--引入外部properties文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据源-->
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!--还可以通过property配置更多属性,如最大连接数-->
</bean>
</beans>
2、为Service层类或方法添加注解。
@Service
//为类中方法添加事务
@Transactional
public class MyService {
//为单个方法添加事务
@Transactional(propagation = Propagation.REQUIRED)
public String doServiceWork(){
System.out.println("service进行业务逻辑操作");
return "SUCCESS";
}
}