目录
1. spring简介
spring在不同的上下文中有不同的含义,它可以指spring框架项目本身,也可以指整个项目家族。这篇文章关注于spring框架本身。
spring是的创建Java企业应用程序变得很容易,它提供了在企业环境中使用Java语言所需的一切。
2. spring ioc容器和Beans介绍
2.1 ioc是什么
ioc(控制反转),简单举例来说,就是在获取Java对象的时候,对象的属性不会在类中就定义好,而是会通过set方法来允许用户自己设置。即:对象属性不再由程序员硬编码去控制,而是让用户去控制。
其实,一般我们也是这样做的。
2.2Beans是什么
Bean,其实也就相当于Java对象。使用spring后,获得一个实体对象不再需要new关键字了,你只需要获得一个ApplicationContext的容器,然后需要什么对象就直接用getBean方法就可以获取,不过别忘了强转类型。
也就是说,容器在创建的时候,就已经把在它注册文件里注册的对象 new出来了。并且,通过容器获得对象,这使用的是单例模式,也就是说,你用同样的id获得的多个对象,实际上都是同一个。对其中一个对象的属性进行操作,会使你获得的其它对象的属性也随之更改。
2.3 获得容器及对象
2.3.1 实现如下实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Good {
private int id;
private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private Good good;
}
2.3.2 配置元数据,实例化容器
spring ioc容器使用 一种配置元数据的形式。这个配置元数据告诉spring容器如何在应用程序中实例化、配置和组装对象。这篇文章中,配置元数据以简单直观的xml格式提供。
配置元数据文件applicationContext.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">
<!-- class 要使用完全限定名-->
<bean id="good" class="com.plane.pojo.Good">
<property name="id" value="1"></property>
<property name="name" value="辣条"></property>
</bean>
<bean id="user" class="com.plane.pojo.User">
<property name="name" value="宏彬"></property>
<property name="id" value="1"></property>
<property name="good" ref="good"></property>
</bean>
<!-- more bean definitions go here -->
</beans>
利用配置元数据new 一个ClassPathXmlApplicationContext 对象, 获得一个ApplicationContext对象,这个ApplicationContext对象就是我们的spring容器。可以通过它的getBean方法获得对象。(ApplicationContext是ClassPathXmlApplicationContext的超类)
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
2.3.3 获得对象,进行测试
在测试中,通过user获得了user1和user2两个对象,对其中一个的属性进行修改,输出另外一个,发现两个的属性都变了
User user1 = (User) context.getBean("user");
System.out.println(user1);
User user2 = (User)context.getBean("user");
System.out.println(user1==user2);
user2.setName("宏猪");
System.out.println(user1);
2.3.4最后,附上一个测试
ApplicationContext context1 = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext context2 = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("context1 和 context2是否是同一个对象:");
System.out.println(context1==context2);
System.out.println("=======================================");
User user1 = (User)context1.getBean("user");
User user2 = (User)context2.getBean("user");
System.out.println("c1 和 c2 获得的同id对象是不是同一个:");
System.out.println(user1==user2);
System.out.println("=================================");
System.out.println("改变user1的属性,user2的属性是否会跟着改变:");
user1.setName("5555555555555555555");
System.out.println(user2);
===================
@Data
public class Constructor {
public Constructor(){
System.out.println("Constructor对象被创建出来了");
}
}
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
3. DI
3.1简介
DI,即依赖注入,就是bean对象的创建依赖于容器,bean对象的属性依赖于容器注入。
前面的文章中已经提到了基本类型属性和bean对象属性的注入,这一章节将会讲解其它类型的注入。
list | set | map | props | value | null
3.2 测试
创建如下实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private String name;
private List<String> hobbys;
private Set<String> books;
private Map<String, String> cards;
private Properties info;
private String wife;
}
编写student.xml文件
<bean id="student" class="com.plane.pojo.Student">
<property name="name" value="哼哼哼"></property>
<property name="info">
<props>
<prop key="身高">172</prop>
<prop key="体重">110</prop>
</props>
</property>
<property name="hobbys">
<list>
<value>看视频</value>
<value>写代码</value>
<value>记笔记</value>
<value>写博客</value>
</list>
</property>
<property name="books">
<set>
<value>java核心技术</value>
<value>csapp</value>
<value>jvm</value>
</set>
</property>
<property name="cards">
<map>
<entry key="身份证" value="555555111111110202"></entry>
<entry key="学生卡" value="0000555111"></entry>
</map>
</property>
<property name="wife">
<null></null>
</property>
测试
// 输出结果
// Student(
// name=哼哼哼,
// hobbys=[看视频, 写代码, 记笔记, 写博客],
// books=[java核心技术, csapp, jvm],
// cards={身份证=555555111111110202, 学生卡=0000555111},
// info={身高=172, 体重=110},
// wife=null)
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("student.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student);
}
4.自动装配
通过前面的文章我们已经知道,如何通过配置文件获得对象。这一章节将会讲到,当对象所需要的属性也是一个对象,且这个属性对象已经在配置文件中注册为bean的时候,如何将这个属性对象注入到所需对象,成为它的一个属性。
4.1配置文件实现自动装配
定义如下所需类
@Data
@AllArgsConstructor
public class Dog {
public void shout(){
System.out.println("汪汪汪...");
}
}
@Data
@NoArgsConstructor
public class Cat {
public void shout(){
System.out.println("喵喵喵...");
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Cat cat;
private Dog dog;
}
配置文件如下
<?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="cat" class="com.plane.pojo.Cat"></bean>
<bean id="dog" class="com.plane.pojo.Dog"></bean>
<bean id="person" class="com.plane.pojo.Person" autowire="byName">
<property name="name" value="红猪"></property>
</bean>
</beans>
测试结果
ApplicationContext context = new ClassPathXmlApplicationContext("person.xml");
Person person = context.getBean("person", Person.class);
System.out.println(person.getName());
person.getCat().shout();
person.getDog().shout();
红猪
喵喵喵...
汪汪汪...
4.2注解实现自动装配
@Autowired 自动地从.xml文件中查找已注册的bean,当环境较为复杂、无法找到唯一合适的bean时,可以通过与@Qualifier(value = "xxx")配合使用,装配指定id的bean
注解实现自动装配需要时,beans标签需要引入依赖,并开始注解支持
<!--beans标签中导入了使用注解需要的依赖-->
<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/>
<bean id="cat" class="com.plane.pojo.Cat"></bean>
<bean id="cat22" class="com.plane.pojo.Cat"></bean>
<bean id="dog" class="com.plane.pojo.Dog"></bean>
<bean id="dog22" class="com.plane.pojo.Dog"></bean>
<!-- person的cat属性和dog属性都没有赋值,将使用自动装配-->
<bean id="person" class="com.plane.pojo.Person">
<property name="name" value="红猪"></property>
</bean>
</beans>
5.spring注解开发
@Configuration使用在一个类上,假定这个类是AppConfig,则AppConfig类就相当于<beans><bean>标签了。通过new AnnotationConfigApplicationContext(AppConfig.class)获得一个context,然后就可以通过context获得bean了
@Bean使用在Appconfig的一个方法上,这个方法就相当于一个被注册的bean了。方法的返回类型相当于bean的class,方法名相当于bean的id。
@Component 这个注解用在类上,相当于把这个类注册成了一个bean。在spring中,一般使用mvc三层架构,@Component注解也多出来了几个衍生注解---->dao(@Repository), service(@Service), controller(@Controller)。
注意一点,在使用Spring注解开发的时候,需要在配置文件中声明注解支持
<!--扫描包下的注解--> <context:component-scan base-package="com.plane"></context:component-scan> <!--开启注解支持--> <context:annotation-config/>
最佳实践:关于在spring中使用配置文件和还是使用注解开发,一般会使用配置文件来获得bean,使用注解来是属性的注入。
6. aop
aop,也就是切面编程。例如,service层调用dao层业务的时候,需要输出调试信息,但是又不想改变已有的代码,这个时候就可以增加一个代理层,代理层调用dao层的相应业务并且输出调试信息,service层不再调用dao层的业务而是调用代理层的业务就好了。
形象地说,aop就相当于在一个垂直的业务结构中,横向地插入了一个业务,且不影响已有的代码。
这一章会讲解spring实现aop的三种方式:Java api实现aop、自定义代理类实现aop以及注解实现aop
6.1java api实现aop
如下,实现UserService接口和实现类
public interface UserService {
public void add();
public void delete();
public void query();
public void update();
}
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void query() {
System.out.println("查询了用户");
}
public void update() {
System.out.println("修改 了用户");
}
}
定义实现Java api接口的代理类:MethodBeforeAdvice和AfterReturningAdvice都是Java内的接口
public class BeforeLog implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+",对象是"+target.getClass().getName());
}
}
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+ method.getName()+"方法, 返回了"+returnValue);
}
}
配置文件如下
<?xml version="1.0" encoding="UTF-8"?>
<!--beans里面引入了使用aop的依赖-->
<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"
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">
<bean id="userService" class="com.plane.service.UserServiceImpl"></bean>
<bean id="beforeLog" class="BeforeLog"></bean>
<bean id="afterLog" class="AfterLog"></bean>
<!-- 方式一 java api 实现aop-->
<aop:config>
<!-- 定义切入点 execution(返回类型, 包名, 类名, 方法名, 方法参数列表) -->
<aop:pointcut id="point" expression="execution(* com.plane.service.UserServiceImpl.*(..))"/>
<!-- 进行环绕增强-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="point"></aop:advisor>
<aop:advisor advice-ref="afterLog" pointcut-ref="point"></aop:advisor>
</aop:config>
</beans>
测试代码及结果
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// aop的原理就是动态代理,在动态代理中,代理的是接口,所以getBean方法的第二个参数是接口的类型
UserService userService = context.getBean("userService", UserService.class);
userService.add();
执行了add,对象是com.plane.service.UserServiceImpl
增加了一个用户
执行了add方法, 返回了null
6.2自定义代理类实现aop
自定义一个代理类
public class MyLog {
public void before(){
System.out.println("=============方法执行前=============");
}
public void after(){
System.out.println("=============方法执行后=============");
}
}
配置文件相关代码
<!-- 方式二:自定义类实现aop -->
<bean id="myLog" class="com.plane.MyLog"></bean>
<aop:config>
<!-- 定义一个切面-->
<aop:aspect ref="myLog">
<!-- 通知:在哪里切入 -->
<aop:pointcut id="point" expression="execution(* com.plane.service.UserServiceImpl.*(..))"/>
<!-- 什么时候切入 -->
<aop:after method="after" pointcut-ref="point"></aop:after>
<aop:before method="before" pointcut-ref="point"></aop:before>
</aop:aspect>
</aop:config>
测试结果
=============方法执行前=============
增加了一个用户
=============方法执行后=============
6.3注解实现aop
自定义一个代理类,并加上注解
//将当前类声明为一个切面
@Aspect
public class MyAspect {
@Before("execution(* com.plane.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("=============方法执行前---MyAspect============");
}
@After("execution(* com.plane.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("=============方法执行后---MyAspect============");
}
}
配置文件
<!-- 方式三:注解实现aop-->
<bean id="myAspect" class="com.plane.MyAspect"></bean>
<!-- 开启注解支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
测试结果
=============方法执行前---MyAspect============
增加了一个用户
=============方法执行后---MyAspect============
很容易看出来,第三种方式和第二种方式几乎一样,不过是把使用配置文件改为使用注解而已。
7.Spring整合Mybatis
7.1简述
Mybatis的实现步骤是这样的:
1)定义一个实体类如User,实体类属性与数据库表中字段对应
2)定义一个Mapper接口,如UserMpper,接口中声明对数据库的操作方法
3)编写mapper.xml,如userMapper.xml,实现UserMapper接口中对数据库操作的方法
4)编写mybatis-config.xml文件,配置连接数据库的元数据,并把mapper.xml注册在里面
5)使用SqlSessionFactory类通过mybatis-config.xml文件获得SqlSession,利用SqlSesion,getMapper()方法获得UserMapper接口对象,UserMapper对象调用自己声明的对数据库进行操作
Spring的实现步骤是这样的:
1)定义一个类
2)编写applicationContext.xml文件,把定义的类注册在bean容器中
3)通过CPXMAC类获得一个ApplicationContext对象context,用context.getBean()方法获得需要 的对象
简单来说,mybatis是通过配置文件获得sqlSession对象,利用sqlSession获得定义的接口对象,接口对象调用方法对数据库进行操作。而spring则是利用配置文件获得一个context对象,用context.getBean()方法获得需要的类对象,进而进行操作。
在spring中,一切的实体对象都不再需要用户new出来了,而是通过context.getBean()方法获得。
spring整合mybatis用的也是这个思想,把需要通过new获得的mybatis对象都在spring的配置文件里注册好,然后通过spring的context.getBean()方法获取实体类对象,再用实体类的方法对数据库进行操作。接下来进行项目演示
7.2spring整合mybatis演示
数据库表和实体类如下
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
编写UseMapper的接口
public interface UserMapper {
public List<User> getUserList();
}
编写userMapper.xml文件
<mapper namespace="com.plane.mapper.UserMapper">
<select id="getUserList" resultType="User">
select * from user
</select>
</mapper>
编写mybatis-config.xml文件【元数据的配置都放在spring的applicationContext.xml文件中了
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<typeAlias type="com.plane.pojo.User" alias="User"></typeAlias>
</typeAliases>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
编写applicationContext.xml文件
<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="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</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>
</bean>
<!-- SqlSession的注册-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
</bean>
</beans>
这些都准备好后,就先通过applicationContext.xml获得context,再用context.getBean()获得sqlSession,sqlSession.getMapper()获得mapper, mapper调用自己的方法对数据库进行操作,具体代码如下:
@org.junit.Test
public void test1() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SqlSession sqlSession = context.getBean("sqlSession", SqlSession.class);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
}
User(id=1, name=宏猪, pwd=000000)
User(id=2, name=plane, pwd=000000)
User(id=3, name=xiaoming, pwd=000000)
User(id=6, name=宏猪, pwd=000000)
8. Spring中的事务
8.1简述
事务的概念在操作数据库中特别重要,目的就是为了保证数据的原子性、一致性、隔离性和持久性。那么Spring操作数据库的时候,如果中间发生了意外,比如说一个插入和删除一条数据中间出现问题,会造成什么样的结果呢?这一章节就会讲解这个问题。
8.2测试
在这个测试中,会插入和删除一条数据,但是删除的sql语句是错误的以模拟操作出现意外的情况。
为UserMapper接口添加两个方法:add()和delete
public int add(User user);
public int delete(int userId);
在userMapper.xml文件中映射,delete语句写错模拟出现意外情况
<insert id="add" parameterType="User">
insert into user(id, name, pwd) VALUES (#{id}, #{name}, #{pwd})
</insert>
<delete id="delete" parameterType="int">
deletes from user where id = #{id}
</delete>
测试代码中插入一条数据并且删除一条数据,删除数据失败但插入数据成功,不符合acid原则
User user = context.getBean("user", User.class);
user.setId(4);
user.setName("plane");
user.setPwd("5555555");
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.add(user);
userMapper.delete(5);
8.3声明式事务
Spring已经给我们提供了事务管理,只需要配置即可。使用声明式事务,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。将声明式事务作为横切关注点,通过aop方法模块化。
spring 的 applicationContext.xml恩济头导入tx的约束
xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
在spring 容器中注册事务
<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="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
配置aop,织入事务
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.kuang.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
注意:事务的概念需要在一个方法中出现的对数据库的操做才符合