一、IOC容器
1、IOC(控制反转)
即控制反转,它不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
(1)控制了什么?
是 bean 的创建、管理的权利,控制 bean 的整个生命周期。
(2)反转了什么?
把这个权利交给了 Spring 容器,而不是自己去控制,就是反转。由之前的自己主动创建对象,变成现在被动接收别人给我们的对象的过程,这就是反转。
(3)bean 又是什么?
Bean 其实就是包装了的 Object,无论是控制反转还是依赖注入,它们的主语都是 object,而 bean 就是由第三方包装好了的 object。
2、DI(依赖注入)
即依赖注入:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
(1)依赖什么?
程序运行需要依赖外部的资源,提供程序内对象的所需要的数据、资源。
(2)注入什么?
配置文件把资源从外部注入到内部,容器加载了外部的文件、对象、数据,然后把这些资源注入给程序内的对象,维护了程序内外对象之间的依赖关系。
3、IOC和DI总结
控制反转是通过依赖注入实现的。但是它们是有差别的,像是从不同角度描述的同一件事。
-
IoC 是设计思想,DI 是具体的实现方式;
-
IoC 是理论,DI 是实践;
从而实现对象之间的解藕。
当然,IoC 也可以通过其他的方式来实现,而 DI 只是 Spring 的选择。IoC 和 DI 也并非 Spring 框架提出来的,Spring 只是应用了这个设计思想和理念到自己的框架里去。
(1)为什么使用IOC
解藕。
它把对象之间的依赖关系转成用配置文件来管理,由 Spring IoC Container 来管理。在项目中,底层的实现都是由很多个对象组成的,对象之间彼此合作实现项目的业务逻辑。但是,很多很多对象紧密结合在一起,一旦有一方出问题了,必然会对其他对象有所影响,所以才有了解藕的这种设计思想。
优质博客:依赖注入和控制反转的理解,写的太好了。-优快云博客
4、代码实现
(1)使用xml实现注入
-
目录结构
-
Bean
Address类:@Data @AllArgsConstructor @NoArgsConstructor public class Address { String name; }
User类(包含address类)
@Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private Address address; private String[] book; private List<String> hobby; private Map<String,String> card; private Set<String> games; private String wife; private Properties info; }
-
xml
其中bean标签的作用是把对应的类注入容器,id是类的唯一标识,name是类的别名,都可以以此为根据实例化。
property标签是类的属性,name和对应属性名相同,如果类型是String就可以用value直接指定值,如果是另外一个已经注入容器的Bean就可以使用ref指定Bean的id,其他类型的注入如下<?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 http://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="user" class="com.zhangxin.spring.pojo.User" name="U,u"> <property name="name" value="zhangxin"/> <property name="address" ref="address"/> <property name="book"> <array> <value>《红楼梦》</value> <value>《水浒传》</value> <value>《三国演义》</value> </array> </property> <property name="hobby"> <list> <value>唱歌</value> <value>跳舞</value> </list> </property> <property name="card"> <map> <entry key="身份证" value="pwd1"/> <entry key="银行卡" value="pwd2"/> </map> </property> <property name="games"> <set> <value> cf</value> <value> 王者</value> <value> cf</value> </set> </property> <property name="wife"> <null/> </property> <property name="info"> <props> <prop key="学号">20210222</prop> <prop key="专业">软件工程</prop> </props> </property> </bean> <bean id="address" class="com.zhangxin.spring.pojo.Address" > <property name="name" value="郑州"/> </bean> </beans>
-
Test
public class MyTest { @Test public void testUser(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); User user = context.getBean("u",User.class); System.out.println(user); } }
测试结果:
(2)使用注解实现注入
-
Bean
Dog类@Component public class Dog { public void shut(){ System.out.println("狗叫!"); } }
Cat类
@Component public class Cat { public void shut(){ System.out.println("猫叫!"); } }
People类
@Data @AllArgsConstructor @NoArgsConstructor @Component public class People { @Value("张三") String name; @Autowired Cat cat; @Autowired Dog dog; }
-
Test
public class MyTest { @Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); People person = context.getBean("people", People.class); person.getCat().shut(); person.getDog().shut(); System.out.println(person); } }
-
xml
这个xml文件的核心就是 <context:component-scan base-package=“com.zhangxin.pojo”/> 这一行,下面可以使用定义SpringConfig配置类实现无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 http://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="com.zhangxin.pojo"/> </beans>
-
Spring的Config类
@Configuration 指定这个是配置类,@ComponentScan(“com.zhangxin.pojo”) 指定需要扫描bean的包使其注解生效。@Configuration @ComponentScan("com.zhangxin.pojo") public class MyConfig { }
-
xml和config配置类从容器中取出bean的区别
xml,需要使用spring配置文件ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); People person = context.getBean("people", People.class);
config,需要对应config的Class
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class); People people = context.getBean("people", People.class);
-
注解解释
@Component
表示这个类被spring接管了,即这个类已经注册为bean了
@Componen的衍生注解:
@Repository 该注解用于数据访问层的bean定义,数据访问层也就是我们常写的dao层、mapper层
@Service 该注解用于业务逻辑层的bean定义,即service层
@Controller 该注解用于表现层,控制层的bean定义,即controlle层等
这四个注解的功能一样的都是把类注入spring,装配bean@Configuration
表示这个类是一个配置类,也会注入spring,因为也是一个@Component@ComponentScan(“com.zhangxin.pojo”)
表示要扫描的包com.zhangxin.pojo下的所有注解,不然这个包的注解不生效,相当于xml的 < context:component-scan base-package=“com.zhangxin.pojo”/> ,该注解只能使用1次,如果有多个需要扫描的包,使用下述方式实现@ComponentScan({"com.zhangxin.dao","com.zhangxin.service"})
@Bean
用于方法上,通常是是以一个方法的返回值的形式注入一个Bean,当然前提是这个方法对应的类是一个组件,比如下面的代码就是成功注入了Cat类,Cat类并没有使用@Component标签
@Component , @Repository , @ Controller , @Service 这些注解只局限于自己编写的类,而@Bean注解能把第三方库中的类实例加入IOC容器中并交给spring管理public class Cat { public void shut(){ System.out.println("猫叫!"); } } @Component class A{ @Bean Cat cat(){ return new Cat(); } }
@Value
普通类型直接用value赋值,直接在属性上面@Resource(name=" ")
如果没有指定name属性,当注解写在字段上时,默认取字段名,按照名称查找。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。@Autowired
默认是byType可以使用@Qualifier指定Name,@Resource默认ByName如果找不到则ByType@Autowired可以和@Override配合使用
比如某个属性类是个接口但是有多个实现类,默认情况是@Autowired标签会自动加载其中的一个实现类,然后使用这个实现类。这时候就需要特定加载某个类了- 第一步将实现类注册为一个Bean(@Service、@Repository标签都可以将类注册为bean)
- 第二步使用@Qualifier(“beanId”)标签将bean的Id作为参数使用,加载特定的类。Id一般默认是bead名称首字母小写
@Component // 指明bean名称 @Qualifier("fooFormatter") public class FooFormatter implements Formatter { public String format() { return "foo"; } } @Component // 指明bean名称 @Qualifier("barFormatter") public class BarFormatter implements Formatter { public String format() { return "bar"; } } @Component public class FooService { @Autowired // 同样使用@Qualifier进行指明注入 @Qualifier("fooFormatter") private Formatter formatter; }
@Import(ConfigB.class)
可以在一个配置类引入另一个配置类
二、AOP
1、基本概念
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
将方法注入到接口调用的某个地方(切点)。这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。
红框处,就是面向切面编程。
2、相关名词
Aspect(切面): 切面 声明类似于 Java 中的类声明,在 切面中会包含着一些切点 以及相应的通知。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 连接点。
Pointcut(切点):表示一组连接点,这些连接点 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(通知):通知 定义了在 切点 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象):织入 通知的目标对象.。
Weaving(织入):将 切面 和其他对象连接起来, 并创建 Adviced object 的过程
3、代码实现
(1)导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.9.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.21</version>
<scope>runtime</scope>
</dependency>
(2)pojo
Person
public interface Person {
String buy();
}
Boy
@Repository
public class Boy implements Person{
@Override
public String buy() {
System.out.println("男孩购买游戏机");
return "游戏机";
}
}
Girl
@Repository
public class Girl implements Person {
@Override
public String buy() {
System.out.println("女孩购买衣服");
return "衣服";
}
}
(3)切面类
切面类依旧需要使用 @Component注解实现Spring托管,使用@Aspect注解声明为切面类, @Befor、@After、@AfterReturning、@Around注解都是通知类型,括号内是AspectJ的切点表达式,@Pointcut注解可以直接封装一个切点表达式,方便后续使用。
切点表达式规则:
@Aspect
@Component
public class MyAdvice {
@Pointcut("execution(* com.zhangxin.pojo.Person.buy(..))") // 定义命名的切点
public void performance() {
}
@Before("execution(* com.zhangxin.pojo.Person.*(..))")
public void before(){
System.out.println("before...");
}
@After("performance()")
public void after() {
System.out.println("After ...");
}
@AfterReturning("performance()")
public void afterReturning() {
System.out.println("AfterReturning ...");
}
@Around("performance()")
public void around(ProceedingJoinPoint pj) {
try {
System.out.println("Around 1");
pj.proceed();
System.out.println("Around 2");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
(4)配置类
出现了之前没有的注解@EnableAspectJAutoProxy(proxyTargetClass = true),表示开启AOP,因为spring默认是关闭的,另外需要扫描两个包。
@Configuration
@ComponentScan({"com.zhangxin.pojo","com.zhangxin.advice"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class MyConfig {
}
(5)Test
public class MyTest {
@Test
public void test1(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
Boy boy = context.getBean("boy", Boy.class);
Girl girl = context.getBean("girl", Girl.class);
boy.buy();
girl.buy();
}
}
测试结果:
(6)利用@Around读取参数
pojo
@Component
public class User {
public void test(String string){
System.out.println("传入参数是:"+string);
}
}
切面类、
@Aspect
@Component
public class MyAdvice2 {
@Pointcut("execution(* com.zhangxin.pojo.User.test(String))&& args(string)") // 定义命名的切点
public void performance(String string) {
}
@Around("performance(string)")
public void around(ProceedingJoinPoint pj,String string) {
try {
pj.proceed();
System.out.println("读取的参数是:"+string);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
test
@Test
public void test2(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User user = context.getBean("user", User.class);
user.test("1212121");
}
结果
三、整合mybatis
1、导入依赖
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- MySQL驱动 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.24</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、xml配置版
(1)mapper(dao)
public interface UserMapper {
List<User> selectAllUser();
}
public class UserMapperImp2 extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> selectAllUser() {
return getSqlSession().getMapper(UserMapper.class).selectAllUser();
}
}
(2)pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private long id;
private String username;
private String password;
private long age;
private String sex;
private String email;
}
(3)jdbc、log4j配置
jdbc
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/MyBatis?serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=1234567
log4j
#将Mybatis log4j运行级别调到DEBUG可以在控制台打印出Mybatis运行的sql语句
log4j.rootLogger=DEBUG,Console
### 把日志信息输出到控制台 ###
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=Sun: %m%n
#log4j.appender.Console.layout.ConversionPattern=data:%d{yyyy-MM-dd HH:mm:ss}%n
#log4j.logger.(写接口的全限定名,即对应于dao中的接口)可以打印出更详细的结果
#log4j.logger.dao.StudentDao=TRACE
(4)mybatis-config
需要设置类的别名和引入mapper.xml文件路径
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- <properties resource="jdbc.properties"/>-->
<!-- 引入log4j 配置-->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
<!-- 批量设置别名,会自动的将该包下的所有类定义了别名,别名就是其自身且不区分大小 -->
<typeAliases>
<typeAlias type="com.zhangxin.pojo.User" alias="user"/>
</typeAliases>
<!--引入映射文件-->
<mappers>
<!-- <mapper resource="mappers/UserMapper.xml"/>-->
<!-- 引入整个包的mapper.xml,需要目录结构目录名字,文件名和相关接口所在的包名一致-->
<package name="com.zhangxin.mapper"/>
</mappers>
</configuration>
(5)sping配置文件
需要注册SqlSessionFactory,SqlSessionFactory是创建SqlSession的工厂,它是个单个数据库映射关系经过编译后的内存镜像。
SqlSession可以通过getMapper()方法获得获取mapper,mapper接口可以映射到mapper.xml中写好的SQL语句。
<?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
http://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:property-placeholder ignore-unresolvable="true" location="classpath:jdbc.properties"/>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 定义 sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 绑定myBatis配置文件-->
<property name="configLocation" value="classpath:myBatis-config.xml"/>
</bean>
<bean id="userMapper2" class="com.zhangxin.mapper.UserMapperImp2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
(6)mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhangxin.mapper.UserMapper">
<select id="selectAllUser" resultType="com.zhangxin.pojo.User" >
select * from user;
</select>
</mapper>
(7)Test
public void test2()throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
// System.out.println(userMapper.selectAllUser());
userMapper.selectAllUser().forEach(user -> System.out.println(user));
}
3、纯注解版
(1)加入druid连接池依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
(3)dao层
之前的mapper+mapper.xm,用@Select等标签代替xml文件
public interface UserMapper {
@Select("select * from user")
List<User> selectAllUser();
}
(4)service层
service层调用dao层
public interface UserService {
List<User> selectAllUser();
}
@Service
public class UserServiceImp implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> selectAllUser() {
return userMapper.selectAllUser();
}
}
(5)config类(核心)
@PropertySource引入需要的资源文件,对应spring.xml:
< context:property-placeholder ignore-unresolvable=“true” location=“classpath:jdbc.properties”/>
@MapperScan扫描mapper包,对应mybatis-congig.xml:< package name=“com.zhangxin.mapper”/>
dataSource()方法和sqlSessionFactoryBean(DataSource dataSource)方法都是使用返回值的方式动态注入Bean。
@Configuration //声明该类是核心配置类
@ComponentScan("com.zhangxin") //开启spring注解扫描
@PropertySource("classpath:jdbc.properties") //引入properties文件
@MapperScan("com.zhangxin.dao") //MyBatis扫描dao接口
public class MyConfig {
//定义属性 为属性注入数据(数据的来源上面引入的db.properties文件)
@Value("${jdbc.driver}")
private String driverClass;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
//创建数据源返回数据源,Spring会自动调用该方法,并将该对象交给IOC容器管理
@Bean
public DataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClass);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
//创建SqlSessionFactoryBean对象,设置形参,Spring会自动去调用IOC容器中已有的数据源
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
}
(6)Test
public class MyTest {
@Test
public void test1()throws IOException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
UserServiceImp userService = context.getBean("userServiceImp", UserServiceImp.class);
List<User> users = userService.selectAllUser();
System.out.println(users);
}
}
四、声明式事物
要么都成功,要么都失败,把一组业务当成一个业务来处理。
1、事物四大特性
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用;
- 一致性(Consistency):一旦事务完成(不管是成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏;
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏;
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中;