目录
1.控制反转的概念
控制反转,也就是IoC(Inversion of Control),是反转两件事:
1.不在程序中采用硬编码方式来new对象
2.不在程序中采用硬编码方式来维护对象之间的关系
2.依赖注入
依赖注入又叫DI(Dependdency Injection),是对象A和对象B之间的关系靠注入的手段来维护,包括两种常见的方式:
1.set注入
2.构造方法注入
3.Spring的第一个入门程序
1.在pom文件中添加spring坐标,如:
<!--Spring基础依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
version>5.3.27</version>
</dependency>
2.在类路径下添加spring.xml核心配置文件,里面通过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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.duolaimi.spring.bean.User">
<property name="id" value="1"/>
<property name="name" value="张三"/>
<property name="age" value="18"/>
</bean>
</beans>
3.编写测试类,获取Spring容器,然后再根据bean的id获取对象:
public class SpringTest {
@Test
public void test(){
// 第一步:获取Spring容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
// 第二步:根据bean的id从Spring容器中获取这个对象
User user = applicationContext.getBean("user", User.class);
System.out.println(user);
}
}
4.启用日志框架Log4j2
1.引入依赖
<!--log4j2的依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
2.在类路径下新建log4j2.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<loggers>
<!--
level指定日志级别,从低到高的优先级:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
-->
<root level="DEBUG">
<appender-ref ref="springlog"/>
</root>
</loggers>
<appenders>
<!--输出日志信息到控制台-->
<console name="springlog" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss sss} [%t] %-3level %logger{1024} - %msg%n"/>
</console>
</appenders>
</configuration>
即可开启日志信息,如果自己的代码想要输出日志信息,可以使用如下代码:
@Test
public void testLog(){
Logger logger = LoggerFactory.getLogger(SpringTest.class);
logger.info("输出了");
}
5.bean的作用域(单例和多例)
可以在bean标签的scope属性设置单例或是多例
singleton:spring容器一创建就创建对象,所有对象都是同一个实例
prototype:spring容器创建时不会创建对象,new对象的时候才会创建对象,每次创建对象都是新的对象。
6.简单工厂模式(非23种设计模式)
简单工厂模式,是工厂方法模式的一种特殊实现,又被称为:静态工厂方法模式。
简单工厂模式解决的问题:
客户端程序不需要关心对象的创建细节,需要哪个对象时,只需要向工厂索要即可,初步实现了责任的分离。客户端只负责“消费”,工厂负责“生产”,生产和消费分离。
简单工厂模式中的角色:抽象产品角色、具体产品角色、工厂类角色。
比如传入什么参数就创建什么对象:
7.工厂方法模式(23种设计模式之一)
工厂方法模式可以解决简单工厂模式当中的OCP问题。
怎么解决的?一个工厂对应生成一种产品。这样工厂就不是全能类、上帝类了。另外,也符合OCP原则。
工厂方法模式中的角色:抽象产品角色、具体产品角色、抽象工厂角色、具体工厂角色
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加。
8.BeanFactory和FactoryBean的区别
他们是两个完全不一样的对象
BeanFactory:Spring IoC容器的顶级对象,BeanFactory被翻译为“Bean工厂",在Spring的IoC容器中,“Bean工厂”负责创建Bean对象。BeanFactory是工厂。
FactoryBean:它是一个Bean,是一个能够辅助Spring实例化其他Bean对象的一个Bean。
9.Bean的生命周期(五步法)
Bean生命周期的管理,可以参考Spring的源码:AbstractAutowireCapableBeanFactory类的doCreateBean()方法。
Bean生命周期可以粗略的划分为五大步:
1.实例化Bean(调用无参数构造方法)
2.Bean属性赋值(调用set方法)
3.初始化Bean(会调用Bean的init方法,这个init方法需要自己写,自己配)
4.使用Bean
5.销毁Bean(会调用Beand的destroy方法,这个destroy方法需要自己写,自己配)
10.Bean的生命周期(七步法)
在原有初始化Bean的前后额外添加两个处理:
1.实例化Bean(调用无参数构造方法)
2.Bean属性赋值(调用set方法)
3.执行“Bean后处理器”的befeore方法
4.初始化Bean(会调用Bean的init方法,这个init方法需要自己写,自己配)
5.执行“Bean后处理器”的after方法
6.使用Bean
7.销毁Bean(会调用Beand的destroy方法,这个destroy方法需要自己写,自己配)
Bean后处理器:实现BeanPostProcessor即可,需要重写里面的Before和After的相关方法。
11.Bean的生命周期(十步法)
在原有执行“Bean后处理器”的before方法前后加入了方法,在使用Bean后或者销毁Bean前加了方法。
添加的这三个点位的特点:都是在检查你这个Bean是否实现了某些特定接口,如果实现了这些接口,则Spring容器会调用这个接口中的方法。
Aware相关接口:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
1.实例化Bean(调用无参数构造方法)
2.Bean属性赋值(调用set方法)
3.检查Bean是否实现了Aware相关接口,并设置相关依赖
4.执行“Bean后处理器”的befeore方法
5.检查Bean是否实现了InitializingBean接口,并调用接口方法
6.初始化Bean(会调用Bean的init方法,这个init方法需要自己写,自己配)
7.执行“Bean后处理器”的after方法
8.使用Bean
9.检查Bean是否实现了DisposableBean接口,并调用接口方法
10.销毁Bean(会调用Bean的destroy方法,这个destroy方法需要自己写,自己配)
12.Bean的循环依赖问题
什么是循环依赖:A对象中有B属性,B对象中有A属性。
在singleton+setter模式下,为什么循环依赖不会出现问题,Spring是如何应付的?
主要的原因是,在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:
第一个阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行“曝光”【不等属性赋值就曝光】
第二个阶段:Bean曝光之后,再进行属性的赋值(调用set方法)
核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成。
注意:只有在scope是singleton的情况下,Bean才会采取提前“曝光”的措施。
当两个bean的scope都是prototype的时候,才会出现异常。其中一个是singleton的时候,就不会出现异常。
如果是构造注入,也是无法解决循环依赖问题的。
13.Bean循环依赖的源码分析
DefaultSingletonBeanRegistry类中有三个比较重要的缓存:
private final Map<String,0bject> singleton0bjects(一级缓存)
private final Map<String,0bject> earlySingleton0bjects(二级缓存)
private final Map<String, 0bjectFactory<?>> singletonFactories(三级缓存)
一级缓存存储的是:单例Bean对象。完整的单例Bean对象,也就是说这个缓存中的Bean对象的属性都已经赋值了,是一个完整的Bean对象。
二级缓存存储的是:早期的单例Bean对象,这个缓存中的单例Bean对象的属性没有赋值。只是一个早期的实例对象。
三级缓存存储的是:单例工厂对象。这个里面存储了大量的“工厂对象”,每一个单例Bean对象都会对应一个单例工厂对象。这个集合中存储的是,创建该单例对象时对应的那个单例工厂对象。
每次创建对象时会在三级缓存中先创建工厂对象,然后通过工厂对象创建早期Bean对象到二级缓存,并删除三级缓存中的工厂对象,然后找的话先从一级缓存中找,找不到再从二级缓存中找,再找不到就去三级缓存中创建对象工厂。
14.声明Bean的注解
负责声明Bean的注解,常见的包括四个:
@Componant
@Controller
@Service
@Repository
查看源码发现,后面三个注解全部是@Componant注解的别名,只是用于区分,增强程序可读性。
15.负责注入的注解
@Value:只能注入简单类型,可以用在属性上、set方法上、构造方法的形参上注入简单类型。
@Autowired:单独用时,默认是byType,也就是根据类型自动装配非简单类型。
@Qualifier:和@Autowired联合使用,指定需要注入的名称
@Resource:是JDK扩展包中的注解,是标准注解。默认根据名称装配byName,未指定name时,使用属性名作为name,通过name找不到的话会自动启用通过类型byType装配。
16.注解方式声明配置类
使用@Configuration声明该类是配置类,用来替代原先的xml配置文件
17.代理模式
1.静态代理
主要是通过构造方法中传入代理类对象,然后通过该代理类对象去调用方法实现。代码如下:
public interface OrderService {
void generate();
void modify();
void detail();
}
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
System.out.println("实现类生产");
}
@Override
public void modify() {
System.out.println("实现类修改");
}
@Override
public void detail() {
System.out.println("实现类详情");
}
}
public class OrderServiceProxy implements OrderService {
private OrderService orderService;
public OrderServiceProxy(OrderService orderService) {
this.orderService = orderService;
}
@Override
public void generate() {
System.out.println("增强代码");
orderService.generate();
}
@Override
public void modify() {
System.out.println("增强代码");
orderService.modify();
}
@Override
public void detail() {
System.out.println("增强代码");
orderService.detail();
}
}
@Test
public void testProxy(){
OrderService orderService = new OrderServiceImpl();
OrderServiceProxy proxy = new OrderServiceProxy(orderService);
proxy.generate();
proxy.modify();
proxy.detail();
}
优点:解决了OCP问题,采用代理模式的has a,可以降低耦合度。
缺点:类爆炸,每个接口都需要对应代理类,这样类会急剧膨胀,不好维护。
2.动态代理
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量,解决代码复用问题。常见的动态代理技术包括:JDK动态代理、CGLIB动态代理、Javassist动态代理技术。
1.JDK动态代理
只能代理接口
示例代码如下:
@Test
public void testJDKProxy(){
// 创建代理对象
OrderService orderService = new OrderServiceImpl();
/*
1.newProxyInstance:翻译为:新建代理对象
也就是说,通过调用这个方法可以创建代理对象。
本质上,这个Proxy.newProxyInstance()方法的执行,做了两件事情:
一:在内存中动态生成了一个代理类的字节码class
二:new对象了。通过内存中生成的代理类这个代码,实例化了代理对象。
2.关于newProxyInstance()方法的三个重要的参数,每一个什么含义,有什么用?
第一个参数:ClassLoader loader
类加载器。在内存当中生成的字节码也是class文件,要执行也得先加载到内存当中。加载类就需要类加载器,所以这里需要指定类加载器。
并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个
第二个参数:Class<?>[] interfaces
代理类和目标类要实现同一个接口或同一些接口
在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的
第三个参数:InvocationHandler h
InvocationHandler被翻译为:调用处理器,是一个接口,其中编写的就是增强代码
*/
OrderService proxyInstance = (OrderService)Proxy.newProxyInstance(orderService.getClass().getClassLoader(), orderService.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增强1");
Object invoke = method.invoke(orderService, args);
System.out.println("增强2");
return invoke;
}
});
proxyInstance.generate();
}
2.CGLIB动态代理
CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理好(底层有一个小而快的字节码处理框架ASM)
3.Javassist动态代理
Javassist是一个开源的分析、编辑和创建Java字节码的类库。通过使用Javassist对字节码操作为JBoss实现动态”AOP“框架。
18.面向切面编程的七大术语
连接点Joinpoint:在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。
切点Pointcut:在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)
通知Advice:通知又叫增强,就是具体你要织入的代码。
通知包括:前置通知、后置通知、环绕通知、异常通知、最终通知
切面Aspect:切点+通知就是切面
织入Weaving:把通知应用到目标对象上的过程
代理对象Proxy:一个目标对象被织入通知后产生的新对象
目标对象Target:被织入通知的对象
19.切点表达式
切点表达式用来定义通知(Advice)往哪些方法上切入。
切入点表达式语法格式为:
execution([访问控制权限修饰符] 返回值类型 [全限定类名] 方法名 (形式参数列表) [异常])
访问权限控制符:
- 可选项
- 没写,就是4个权限都包括
- 写public就表示只包括公开的方法
返回值类型:
- 必填项
- *表示返回值类型任意
全限定类名:
- 可选项
- 两个点“..” 代表当前包以及子包下的所有类
- 省略时表示所有的类
方法名:
- 必填项
- *表示所有的方法
- set*表示所有的set方法
形式参数列表:
- 必填项
- ()表示没有参数的方法
- (..)参数类型和个数随意的方法
- (*)只有一个参数的方法
- (*,String) 第一个参数类型随意,第二个参数是String的
异常:
- 可选项
- 省略时表示任意异常类型
20.AOP的注解式开发
在配置类上加入@EnableAspectJAutoProxy说明开启切面功能,在切面类上添加@Aspect说明这是一个切面类,并在里面写切面。
@Configuration
@ComponentScan("com.duolaimi.spring")
@EnableAspectJAutoProxy
public class SpringConfig {
}
@Component
@Aspect
public class LogAspect {
@Before("execution(* com.duolaimi.spring.service..*(..))")
public void logStart(){
System.out.println("日志开始记录");
}
@Around("execution(* com.duolaimi.spring.service..*(..))")
public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("前环绕");
joinPoint.proceed();
System.out.println("后环绕");
}
}
21.通知的顺序
可以使用@Order注解来指定通知的顺序,数字越小优先级越高。
22.事务的传播行为
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务的传播行为。
一共有七种传播行为:
代码中使用
@Transactional(propagation = Propagation.REQUIRED)
来指定传播行为。
默认的事务传播行为是REQUIRED。
23.只读事务
使用@Transactional(readOnly = true)将当前事务设置为只读事务,在该事务中只允许select语句执行,增删改语句均不可执行。该特性的作用是:启动spring的优化策略,提高select查询效率。如果该事务中没有增删改语句,建议设置为只读事务。
24.事务的全注解式开发
需要在配置类上加上@EnableTransactionManagement注解说明开启事务,并通过@Bean注解返回事务管理器对象。
@Configuration
@ComponentScan("com.duolaimi.spring")
@EnableAspectJAutoProxy
@EnableTransactionManagement
public class SpringConfig {
@Bean
public DruidDataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("");
dataSource.setUrl("");
dataSource.setUsername("");
dataSource.setPassword("");
return dataSource;
}
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager txManager = new DataSourceTransactionManager();
txManager.setDataSource(dataSource);
return txManager;
}
}
25.Spring对JUnit4的支持
在pom文件中添加spring-test依赖,再添加junit4版本的依赖,在测试类上添加上@RunWith和@ContextConfiguration注解指定配置文件的路径,这样就可以直接通过@Autowired等注入方式直接注入对象。