总结一句话: Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.14</version> </dependency>
package pojo; // 只有无参构造函数 public class Hello { private String str; /* public Hello(String name){ this.str = name; } */ public String getStr() { return str; } public void setStr( String str ) { this.str = str; } @Override public String toString() { return "Hello{" + "str='" + str + '\'' + '}'; } }
<?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="hello" class="pojo.Hello"> <!-- value放基本类型 --> <property name="str" value="Spring"/> </bean> <bean id="hello2" class="pojo.Hello2"> <!-- ref放Spring容器中已经创建好的对象 --> <property name="hel" ref="hello"/> </bean> <!-- 有参构造 --> <!-- 第一种,下标赋值 --> <bean id="user" class="com. kuang.pojo.User"> <constructor-arg index="0" value="狂神说Java" /> </bean> <!-- 第二种方式:通过类型创建,不建议使用! --> <bean id="user" class="com. kuang.pojo.user"> <constructor-arg type="java.lang.String" value="ginjiang"/> </bean> <!-- 第三种,直接通过参数名来设置,推荐 --> <bean id="user" class="com.kuang.pojo.User"> <constructor-arg name="name" value="秦疆"/> </bean> </beans>
import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import pojo.Hello; public class MyTest { @Test public void Test() { // 获取Spring的上下文对象! ApplicationContext context = new ClassPathXmlApplicationContext( "beans.xml" ); // 我们的对象现在都在Spring中的管理了,我们要使用,直接去里面取出来就可以! Hello hello = (Hello) context.getBean( "hello" ); System.out.println( hello.toString() ); //输出:Hello{str='Spring'} } }
<!-- Beans.xml中 --> <!-- 方式1 --> <alias name="user" alias="userNew" />
ApplicationContext context = new ClassPathXmlApplicationContext( "beans.xml" ); // 以下两种均可 Hello hello = (Hello) context.getBean( "user" ); Hello hello = (Hello) context.getBean( "userNew" );
<!-- id : bean的唯一标识符,也就是相当于我们学的对象名 class : bean对象所对应的全限定名:包名+类型 name :也是别名,而且name可以同时取多个别名,空格、逗号、分号都能做分隔 --> <bean id="userT" c1ass="com.kuang.pojo.userT" name="user2 u2,u3;u4"> <property name="name" value="西部开源"/> </bean>
这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个配置文件。
假设,现在项目中有多个人开发,这三个人复制不同的类开发,不同的类需要注册在不同的bean中。
我们可以利用import将所有人的beans.xml合并为一个总的!
<import resource="beans1.xml"/> <import resource="beans2.xml"/> <import resource="beans3.xml"/>
使用的时候,使用总的配置就可以了
正式的总配置文件名为applicationContext.xml
见3.2 Spring配置文件Beans.xml
public class Student { private String name; private Address address; private String[] books; private List<String> hobbys; private Map<String, string> card; private Set<String>games; private String wife; private Properties info; }
<bean id="student" class="com.kuang.pojo.Student"> <!-- 第一种,普通值注入, value --> <property name="name" value="xx"/> <!-- 第二种,Bean注入,ref --> <property name="address" ref="address" /> <!-- 数组 --> <property name="books"> <array> <value>红楼梦</value> <value>西游记</value> <value>水浒传</value> <value>三国演义</value> </array> </property> <!-- List --> <property name="hobbys"> <list> <value>听歌</value> <value>敲代码</value> <value>看电影</value> </list> </property> <!-- Map --> <property name="card"> <map> <entry key="身份证" value="111111222222223333"/> <entry key="银行卡" value="1321231312312313123"/> </map> </property> <!-- Set --> <property name="games"> <set> <value>LOL</value> <value>coc</value> <value>BOB</value> </set> </property> <!-- null --> <property name="wife"> <nu11/> </property> <!-- 或者 --> <property name="wife" value="" /> <!-- Properties --> <property name="info"> <props> <prop key="driver">xxx</prop> <prop key="ur1">xxx</prop> <prop key="username">root</prop> <prop key="password">123456</prop> </props> </property> </bean>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bar" class="x.y.Bar"/> <!-- p命名空问注入,可以直接注入属性的值: property --> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://localhost:3306/mydb" p:username="root" p:password="masterkaoli" p:test-ref="bar" /> </beans>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> <!-- 传统声明 --> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> <constructor-arg value="foo@bar.com"/> </bean> <!-- c命名空间声明 --> <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/> </beans>
单例模式 Singleton(Spring默认机制)
<bean id="user2" class="pojo.user" c:age="18" scope="singleton" />
原型模式 Prototype :每次从容器中getBean的时候,都会产生一个新对象!
<bean id="user2" class="pojo.user" c:age="18" scope="prototype" />
其余的request、session、application只能在Web开发中使用到。
<!-- 传统注入 --> <beans> <bean id="cat" class="pojo.cat" /> <bean id="dog" class="pojo.Dog" /> <bean id="people" class="com.kuang.pojo.people"> <property name="name" value="xxxx"/> <property name="dog" ref="dog" /> <property name="cat" ref="cat"/> </bean> </beans>
<beans> <bean id="cat" class="pojo.cat" /> <bean id="dog" class="pojo.Dog" /> <!-- byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean id --> <bean id="people" class="com.kuang.pojo.people" autowire="byName"> <property name="name" value="xxxx"/> </bean> </beans>
<beans> <bean class="pojo.cat" /> <bean class="pojo.Dog" /> <!-- byType:会自动在容器上下文中查找,和自己对象属性类型(class)相同的bean --> <bean id="people" class="com.kuang.pojo.people" autowire="byType"> <property name="name" value="xxxx"/> </bean> </beans>
小结:
byName 的时候,需要保证所有 bean 的 id 存在且唯一,并且这个 bean 需要和自动注入的属性的 set 方法的值一致!
byType 的时候,需要保证所有 bean 的 class 存在且唯一,并且这个 bean 需要和自动注入的属性的类型一致!
使用注解须知:
导入约束
配置注解的支持:<context:annotation-config/>
<?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 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/> <beans> <bean>...</bean> </beans> </beans>
直接在属性上使用!也可以在set方式上使用!
使用@Autowired 我们可以不用编写Set方法了,前提是你这个自动装配的属性Type在 Spring 容器中存在,且符合名字byName!
<beans> <bean id="cat11" class="pojo.cat"/> <bean id="cat111" class="pojo.cat" /> <bean id="dog22" class="pojo.Dog" /> <bean id="dog222" class="pojo.Dog" /> <bean id="people" class="pojo.People" /> </beans>
public class People { @Autowired private cat cat; //@Autowired无法实现时,要配合@qualifier(value="")来针对某一个bean进行装配 @Autowired @Qualifier(value="dog222") private Dog dog; private string name; }
public class People { @Resource(name = "cat11") private cat cat; @Resource private Dog dog; }
@Resource 和 @Autowired 的区别:
小结:
@Autowired 先byType,如果同类型个数大于1,再byName。
dog只注册了一个的时候,dog的id可以随便取都能自动装配相当于byType。当dog注册了多个时,只能绑定id和person属性名一样的bean相当于byName,如果多个都没有对应的id就报错
如果 @Autowired 自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候、我们可以使用@Qualifier(value="xxx") 去配置 @Autowired 的使用,指定一个唯一的bean对象注入!
这个value对应的是bean的id,相当于byName。
在 Spring 4 之后,要使用注解开发。必须要保证 aop 的包导入了!
使用注解需要导入 context 约束,增加注解的支持。
<?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 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:component-scan base-package="pojo"/> <context:annotation-config/> </beans>
//等价于:<bean id="user" class="pojo.user"/> @Component public class User { public string name; public void setName(string name) { this.name = name; } }
@component public class User { //相当于 <property name="name" value="kuangshen" /> @Value( "kuangshen2") public string name; public void setName( String name ) { this.name = name; } }
@component //确定作用域:单例或原型 @scope("prototype") public class User { public string name; @Value( "kuangshen2") public void setName( String name ) { this.name = name; } }
@Component有几个衍生注解,我们在web开发中,会按照MVC三层架构分层!
这四个注解功能都是一样的,都是代表将某个类注册到 Spring 容器中,装配Bean。
我们现在要完全不使用 Spring 的 XML 配置了,全权交给 Java来做!
JavaConfig 是 Spring 的一个子项目,在 Spring 4 之后,它成为了一个核心功能
package config; import pojo.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; // 这个也会由Spring容器托管,注册到容器中,因为他本来就是一个@Component // @Configuration代表这是一个配置类,就和我们之前看的applicationContext.xml一样 @configuration //相当于<beans> @ComponentScan( "pojo" ) //扫描某个包下的所有@Component @Import(AppConfig2.class) public class AppConfig { // 注册一个bean ,就相当于我们之前写的一个<bean>标签 // 这个方法的名字,就相当于bean标签中的id属性 // 这个方法的返回值,就相当于bean标签中的class属性 @Bean public User user() { return new User(); } }
public class MyTest { public static void main(string[] args) { // 如果完全使用了配置类方式去做,我们就只能通过 AnnotationConfig 上下文来获取容器 // 通过配置类的class对象加载! Applicationcontext context = new AnnotationConfigApplicationContext(Appconfig.class); User user = (user) context.getBean("user"); System.out.println(user.getName()); } }
需要了解两个类: Proxy:代理 Invocationhandler:调用处理程序
//等我们会用这个类,自动生成代理类! public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private object target; public void setTarget(object target) { this.target = target; } //生成得到代理类 public object getProxy(){ return Proxy.newProxyInstance(this.getclass().getclassLoader(), target.getclass().getInterfaces(), this); } //处理代理实例,并返回结果: public object invoke(0bject proxy,Method method,object[] args) throws Throwable { log(method.getName()); object result = method.invoke(target, args); return result; } public void log(string msg){ system.out.println("执行了" + msg + "方法"); } }
public class client { public static void main( string[ ] args) { //真实角色 (实现类) UserServiceImpl userService = new UserServiceImpl(); //创建代理生成类的实例 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setTarget(userservice); //设置要代理的对象 //代理角色 (动态生成代理类) UserService proxy = (UserService) pih.getProxy(); proxy.query(); } }
动态代理相对于静态代理的好处:
无需编写代理类:对于静态代理,每个类都要写对应的代理类,会多出许多冗余的类;而动态代理,仅需通过一个代理生成类,动态生成对应类的对应代理对象,减少了代码量。
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>
<!-- 注册bean --> <bean id="userService" class="com.kuang.service.UserServiceImpl"/> <!-- com.kuang.log.beforeLog 里面实现了 MethodBeforeAdvice 接口的 before 方法 --> <bean id="beforeLog" class="com.kuang.log.Log"/> <!-- com.kuang.log.afterLog 里面实现了 AfterReturningAdvice 接口的 afterReturning 方法 --> <bean id="afterLog" class="com.kuang.log.AfterLog"/> <!-- 方式一:使用原生Spring API接口 --> <!-- 配置aop:需要导入aop的约束 --> <aop:config> <!-- 切入点: expression:表达式,execution(要执行的位置!* * ***) --> <aop:pointcut id="pointcut" expression="execution(* com.kuang. service.UserServiceImpl.*(..))"/> <!-- 执行环绕增加! --> <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut" /> </aop:config>
<!-- applicationContext.xml --> <bean id="diy" class="com.kuang.diy.DiyPointCut" /> <aop:config> <!-- 自定义切面, ref要引用的类 --> <aop:aspect ref="diy" > <!-- 切入点 --> <aop:pointcut id="point" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <!-- 通知 --> <aop:before method="before" pointcut-ref="point" /> <aop:after method="after" pointcut-ref="point"/> </aop:aspect> </aop:config>
<bean id="annotationPointCut" class="com.kuang.diy.AnnotationPointCut" /> <!-- 开启注解支持! --> <aop:aspectj-autoproxy />
//方式三:使用注解方式实现AOP import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; @Aspect //标注这个类是一个切面 public class AnnotationPointcut { @Before( "execution(* com.kuang.service.UserServiceImpl.*(..))") public void before(){ system.out.println( "=======方法执行前======" ); } @After( "execution(* com.kuang.service.UserServiceImpl.*(..))") public void after(){ system.out.println( "=======方法执行后======" ); } // 在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点; // 可以获取切入点的相关信息。 @Around( "execution(*com.kuang.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable { system.out.println("环绕前"); object proceed = jp.proceed(); //执行方法 system.out.println("环绕后"); } }
Before 前置通知
AfterReturning 后置通知
Around 环绕通知
AfterThrowing 异常通知
After 最终通知
五种通知的执行顺序
在目标方法没有抛出异常的情况下
前置通知 → 环绕通知的调用目标方法之前的代码 → 目标方法 → 环绕通知的调用目标方法之后的代码 →
后置通知 → 最终通知
在目标方法抛出异常的情况下
前置通知 → 环绕通知的调用目标方法之前的代码 → 目标方法 → 抛出异常 异常通知 → 最终通知
详细内容可以查看文档
导入相关Jar包
编写 Spring 、Mybatis 配置文件
<!-- spring-dao.xml --> <!-- 数据库的配置,一般不改,直接import进 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" 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"> <!-- DataSource:使用Spring的数据源替换Mybatis的配置 c3p0 dbcp druid 我们这里使用Spring提供的JDBC:org.springframework.jdbc.datasource --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value "jdbc:mysql://localhost:3306/mybatis?useSSL=true"/> <property name="username" value="root" /> <property name="password" value="123456"/> </bean> <!-- sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="datasource" ref="datasource"/> <!-- 绑定Mybatis配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml" /> <property name="mapperLocations" value="classpath:com/kuang/mapper/*.xml"/> </bean> <!-- sqlSessionTemplate:就是我们使用的sqlSession --> <bean id="sqlSession" class="org.mybatis.spring.sqlsessionTemplate"> <!-- 只能使用构造器注入sqlSessionFactory,因为它没有set方法 --> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean> </beans>
<!-- appplicationContext.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" 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"> <import resource="spring-dao.xml"/> <!-- Mapper的实现类 --> <bean id="userMapper" class="com.kuang.mapper.UserMapperImpl"> <property name="sqisession" ref="sqlsession" /> </bean> </beans>
写 Mapper.xml(sql语句)
......
写 Mapper 的实现类 MapperImpl
import org.mybatis.spring.sqlSessionTemplate; import java.util.List; public class UserMapperImpl implements UserMapper { private sqlsessionTemplate sqlsession; public void setSqlSession(sqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectuser(); } }
import org.mybatis.spring.sqlSessionTemplate; import java.util.List; public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper { public List<User> selectUser() { return getSqlSession().getMapper(UserMapper.class).selectuser(); } }
同时,在 spring-dao.xml 中去掉:
<!-- sqlSessionTemplate:就是我们使用的sqlSession --> <bean id="sqlSession" class="org.mybatis.spring.sqlsessionTemplate"> <!-- 只能使用构造器注入sqlSessionFactory,因为它没有set方法 --> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
并修改:
<!-- Mapper的实现类 --> <bean id="userMapper" class="com.kuang.mapper.UserMapperImpl"> <property name="sqisession" ref="sqlsession" /> </bean> <!-- 修改为 --> <bean id="userMapper" class="com.kuang.mapper.UserMapperImpl"> <!-- 父类 SqlSessionDaoSupport 需要 sqlSessionFactory工厂实例 --> <property name="sqlSessionFactory" ref="sqlsessionFactory" /> </bean>
为什么需要事务?
如果不配置事务,可能存在数据提交不一致的情况。
如果我们不在 Spring 中去配置声明式事务,我们就需要在代码中手动配置事务!
事务在项目的开发中十分重要,设计到数据的一致性和完整性问题,不容马虎!
<!-- spring-dao.xml --> <!--配置声明式事务--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 结合AOP实现事务的织入 --> <!-- 配置事务通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 给哪些方法配置事务(需要结合事务的方法丢进去) --> <!-- 配置事务的传播特性: propagation --> <tx:attributes> <tx:method name="add" propagation="REQUIRED"/> <tx:method name="delete" propagation="REQUIRED"/> <tx:method name="update" propagation="REQUIRED" /> <tx:method name="query" read-only="true" /> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 配置事务切入 --> <aop:config> <aop:pointcut id="txPointCut" expression="execution(* com.kuang.mapper.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" /> </aop:config>