spring 源码
一. IOC 加载
IOC:控制反转 以前我们创建对象需要new,现在交给IOC做,用的时候,我们自己去取
IOC容器,用来存放bean,按照map (ConcurrentHashMap) 结构存放bean
IOC容器工作流程
1. 撰写配置文件(可以是xml,yaml,json,properties)
2. 抽象读取配置文件接口,定义配置文件的规范(BeanDefinitionReader)
3. 读取bean的定义信息(BeanDefinition)
4. 通过反射取实例化bean对象
5. 初始化bean对象
6. 得到完整的bean对象
1. Environment 读取环境变量,加载系统信息
2. 修改bean定义信息中的变量(${aaa})
将其替换为 真实的 内容
例如:<bean id="druid" class="com.alibaba.druid.xxx">
<propertis name="username" value="${jdbc.username}"/>
</bean>
那么在spring实例化对象 druid 之前,参数username就必须在实例化之前替换就位。
那么这个就位的过程,就应该在 BeanDefinition 获取 Bean的配置信息的时候,
针对不同的对象(例如druid),去写一个实现类,实现 BeanFactoryPostProcessor ,
从而重写 方法,从而替换配置文件中的 ${xx} 的内容。
3. 实例化对象步骤
建立在 IOC container 之上的 BeanFactory ,实例化对象
1. 获取class
(1) Class clazz = Class.forName("完全限定名");
(2) Class clazz= 对象.getClass();
(3) Class clazz = 类.class;
2. 反射获取对象
Constructor ctor = clazz.getDeclareConstructor();
Object obj = ctor.newInstance();
4. 初始化bean
5. spring 源码的加载
AOP:面向切面,遵循 ocp原则 对扩展开放,修改关闭
AOP 动态代理的方法有 cglib、jdk
Application.context
依赖注入
spring 使用
Spring 是一个工厂,专门负责生成Bean (对象)
提供AOP编程,可以方便的对程序进行权限拦截,运行监控等
只需要通过配置就可以完成对事务的管理,无需手动编程
通过注解对程序进行测试
不排斥其他框架
降低了JAVAEE的使用难度,内部集成了JDBC、JavaMail、远程调用等
如果要,要引入 aopalliance(aop联盟)、spring-aop (spring整合aop)、aspectjweaver、spring-aspects context jar包
简单了解spring
#UserImpl.class
public class UserImpl implements IUser {
@Override
public void add() {
System.out.println("添加用户");
}
}
#IUser.interface
public interface IUser {
public void add();
}
#bean.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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--配置一个bean对象-->
<bean id = "userBean" class="com.zx.service.impl.UserImpl"></bean>
</beans>
#Lesson01.class
public class Lesson01 {
@Test
public void test(){
// 加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// 从spring容器中获取Bean对象
IUser user = (IUser)context.getBean("userBean");
user .add();
}
}
依赖注入
要求:属性要生成get、set方法
<bean id = "userBean" class="com.zx.service.impl.UserImpl">
<!--依赖注入-->
<property name="name" value="zx" ></property>
</bean>
装配Bean的三种方法
1.通过new实现类
#Lesson01
/*
* 1.通过new实现类
* */
// 加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// 从spring容器中获取Bean对象
IUser user = (IUser)context.getBean("userBean");
user .add();
#IUser.interface
package com.zx.service;
public interface IUser {
public void add();
}
#UserImpl.class
package com.zx.service.impl;
import com.zx.service.IUser;
public class UserImpl implements IUser {
private String name ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void add() {
System.out.println("添加用户+"+name);
}
}
#Bean.xml
<!--配置一个bean对象-->
<bean id = "userBean" class="com.zx.service.impl.UserImpl">
<!--依赖注入-->
<property name="name" value="zx" ></property>
</bean>
2.通过静态工厂
#Lesson01.class
/*
* 2.通过静态工厂
* */
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
IUser user = (IUser)context.getBean("staticFactoryUserBean");
user.add();
#createStaticFactory.class
public class createStatFactory {
public static IUser createBean(){
return new UserImpl();
}
}
#IUser.interface 、UserImpl.class 同上
#bean.xml
<!--静态工厂-->
<bean id = "staticFactoryUserBean" class="com.zx.service.createStatFactory" factory-method="createBean"></bean>
3.实例工厂
#Lesson01.class
/*
* 3.实例工厂
* */
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
IUser user = (IUser)context.getBean("facthroyUserBean");
user.add();
#createFactory.java
public class createFactory {
public IUser createUser(){
return new UserImpl();
}
}
#IUser.interface 、 UserImpl.class 同上
#bean.xml
<!--实例工厂-->
<bean id = "factoryUser" class ="com.zx.service.createFactory" ></bean>
<bean id = "facthroyUserBean" factory-bean="factoryUser" factory-method="createUser"> </bean>
4.指定bean加载时的初始化方法与销毁方法
<bean id = "factoryUser" class ="com.zx.service.createFactory" init-method="init" destory-method="destory" ></bean>
5.bean标签 scope取值singleton(默认)单例,prototype多例。
6.构造方法注入:<bean xx> <constructor-arg name="xx" value="xx" /><beans/>
7.set方法注入,类需要生成set方法 <bean xx> <property name="xx" value=xx"/> </bean>
8.集合注入,
set / list / array方法
<bean xx>
<property name ="xx" >
<set / list / array>
<value></value>
</set / list / array>
</property >
</bean>
map方法
<bean xx>
<property name ="xx" >
<map>
<entry key="xx" value=" xx"/>
</map>
</property >
</bean>
IDEA 快捷键 Ctrl+Alt +T 试一试就知道
注解的使用
如果要使用注解,要引入 spring-aop、context、spring-aspects 、aspectjweaver、spring-aop、aopalliance(aop联盟) jar包
context 注解
aspectj 通知
aop 面向切面的编程 、事务管理,横向抽取机制(动态代理)(注解必须要的 jar 包)
tx 事务通知
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:aop="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--开启注解-->
<context:annotation-config />
<!--配置扫描注解的位置-->
<context:component-scan base-package="com.zx"></context:component-scan>
@RestController 与Controller 差不多,返回的字符串不通过试图解析器,直接显示到页面
@Component 泛指组件 不好归类时用Component(一般会在最后实例化) 用法:@Component、@Component ("beanName“)
@Qualifier 根据指定的id注入属性
@Value 设置属性的值,放在属性的上方 用法:@Value、@Value("123")
@PostConstruct 指定Bean加载时初始化调用方法
@PreDestory 指定Bean销毁时调用方法
@Scope 指定单例或者多例 用法:@Scope||@Scope("singleton") 单例 @Scope("prototype") 多例
通知 需要jar包:aopalliance(aop联盟)、spring-aop (spring整合aop)、aspectjweaver、spring-aspects
<!--配置aop-aspectj拦截注解生效-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
通知 (aspectj .jar )
@Aspect 声明切面类
@Before 前置通知 用法:@Before(value="execution(* com.zx.dao..*(..))")
@AfterReturning 后置通知 用法:@AfterReturning(value="execution(* com.zx.dao..*(..))",returning="方法返回接收参数名")
@Around 环绕通知,开始执行之前,和执行之后所执行的方法 用法:@Around(value="execution(* com.zx.dao..*(..))")
@AfterThrowing 异常抛出通知 用法:同上或者同上上
@After 最终final通知,无论是否存在异常,都会执行,相当与finall代码块的作用 用法:同上上
通知的xml配置
Before 可以在前置通知方法中加上形参JoinPoint 获得调用方法信息,例如:
@Before("execution(* com.zx.dao..*(..))")
public void beforMethod(JoinPoint joinPoint){
System.out.println(joinPoint);
}
输出:execution(void com.zx.dao.UserDao.selectUser())
作用:可以知道此时是那个方法调用了通知
AfterReturning 可以在后置通知方法中加上形参Object 获得方法返回值(可不获取返回值),例如:
@AfterReturning(value="execution(* com.zx.dao..*(..))",returning="returnValue")
public void afterMethod(Object returnValue){
System.out.println(returnValue);
}
即returnValue的值为,所调用方法的返回值
Around 环绕通知,简单的选择是否执行目标方法
@Around(value="execution(* com.zx.dao..*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("前置通知");
Object obj = joinPoint.proceed(); //执行目标方法,如果不执行该句,目标方法不会被执行
System.out.println("后置通知");
reutrn obj;
}
最后要将返回值返回回去,才算执行完方法
即returnValue的值为,所调用方法的返回值
web mvc 注解的使用 注:注解中的value属性值,是上一层的本层对象变量名
@Controller web 层注解 用法:@Controller、@Controller("beanName")
@Service Service层注解 用法:@Service、@Service("beanName")
@Repository Dao层注解 用法:@Repository、@Repository("beanName")
@Autowired 注入参数,默认按照参数类型注入,如果存在两个相同Bean类型相同,则按照名称注入 用法:@Autowired
@Qualifier 一般和@Autowired配合使用,用户指定注入Bean的名称 用法:@Qualifier("beanName")
@Resource 作用等于@Autowired+@Qualifier 用法:@Resource(name="beanName")
!--配置扫描注解的位置-->
<context:component-scan base-package="com.zx"></context:component-scan>
<!--开启aop注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
AOP面向切面的编程 预编译方式各运行期间动态代理实现程序的统一维护的一种技术
OOP面向对象的编程
纵向-----继承 耦合度高
横向-----AOP
Listener监听器,监听ServletContext启动时,在web.xml中加载applicationContext.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--配置spring的配置文件加载路径-->
<!--默认是找/WEB-INF/applicationContext.xml 路径下的文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--监听器-->
<!--加载ApplicationContext 防止重复加载,出现多个对象-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
配置定时任务
添加注解
<task:annotation-driven/>
如果不能识别,则需要在配置文件中添加xsi
@Component
public class TaskUtil{
@Scheduled(cron = "0 0/1 * * * ?")
public void task() {
String dateStr = BaseDateUtil.getFormatString(new Date(), "yyyy-MM-dd HH:mm:ss");
System.out.println("每分钟执行一次:" + dateStr);
}
}
1.顺序
* * * * * * *
格式: [秒] [分] [小时] [日] [月] [周] [年]
2.序号
是否必填 允许填写的值 允许的通配符
1 秒 是 0-59 , - * /
2 分 是 0-59 , - * /
3 小时 是 0-23 , - * /
4 日 是 1-31 , - * ? / L W
5 月 是 1-12 or JAN-DEC , - * /
6 周 是 1-7 or SUN-SAT , - * ? / L #
7 年 否 empty 或 1970-2099 , - * /
3.通配符
* 表示所有值. 例如:在分的字段上设置 "*",表示每一分钟都会触发。
? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?
- 表示区间。例如 在小时上设置 "10-12",表示 10,11,12点都会触发。
, 表示指定多个值,例如在周字段上设置 "MON,WED,FRI" 表示周一,周三和周五触发
/ 用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。在月字段上设置'1/3'所示每月1号开始,每隔三天触发一次。
L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示“本月最后一个星期五"
W 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 "1W",它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,"W"前只能设置具体的数字,不允许区间"-").
小提示
'L'和 'W'可以一组合使用。如果在日字段上设置"LW",则表示在本月的最后一个工作日触发(一般指发工资 )
# 序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六.注意如果指定"#5",正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了)
4.常用示例:
*/30 * * * * ? 每30秒
0 0/1 15,* * * ? 每分钟0/30 * * * * ? 每30触发一次
0 0 12 * * ? 每天12点触发
0 15 10 ? * * 每天10点15分触发
0 15 10 * * ? 每天10点15分触发
0 15 10 * * ? * 每天10点15分触发
0 15 10 * * ? 2005 2005年每天10点15分触发
0 * 14 * * ? 每天下午的 2点到2点59分每分触发
0 0/5 14 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)
0 0/5 14,18 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发) 每天下午的 18点到18点59分(整点开始,每隔5分触发)
0 0-5 14 * * ? 每天下午的 2点到2点05分每分触发
0 10,44 14 ? 3 WED 3月分每周三下午的 2点10分和2点44分触发 (特殊情况,在一个时间设置里,执行两次或两次以上的情况)
0 59 2 ? * FRI 每周5凌晨2点59分触发;
0 15 10 ? * MON-FRI 从周一到周五每天上午的10点15分触发
0 15 10 15 * ? 每月15号上午10点15分触发
0 15 10 L * ? 每月最后一天的10点15分触发
0 15 10 ? * 6L 每月最后一周的星期五的10点15分触发
0 15 10 ? * 6L 2002-2005 从2002年到2005年每月最后一周的星期五的10点15分触发
0 15 10 ? * 6#3 每月的第三周的星期五开始触发
0 0 12 1/5 * ? 每月的第一个中午开始每隔5天触发一次
0 11 11 11 11 ? 每年的11月11号 11点11分触发(光棍节)
自定义注解
@Target 表明该注解可以应用的java元素类型
Target类型 | 描述 |
ElementType.TYPE | 应用于类、接口(包括注解类型)、枚举 |
ElementType.FIELD | 应用于属性(包括枚举中的常量) |
ElementType.METHOD | 应用于方法 |
ElementType.PARAMETER | 应用于方法的形参 |
ElementType.CONSTRUCTOR | 应用于构造函数 |
ElementType.LOCAL_VARIABLE | 应用于局部变量 |
ElementType.ANNOTATION_TYPE | 应用于注解类型 |
ElementType.PACKAGE | 应用于包 |
ElementType.TYPE_PARAMETER | 1.8版本新增,应用于类型变量) |
ElementType.TYPE_USE | 1.8版本新增,应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型) |
@Retention 表明该注解的生命周期
生命周期类型 | 描述 |
RetentionPolicy.SOURCE | 编译时被丢弃,不包含在类文件中 |
RetentionPolicy.CLASS | JVM加载时被丢弃,包含在类文件中,默认值 |
RetentionPolicy.RUNTIME | 由JVM 加载,包含在类文件中,在运行时可以被获取到 |
@Document 表明该注解标记的元素可以被Javadoc 或类似的工具文档化
@Inherited 表明使用了@Inherited注解的注解,所标记的类的子类也会拥有这个注解
@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SoutAnnotation {
String value() default "default value";
}
1.使用反射读取
public class ReflexAnnotation {
public static void main(String[] args) {
ReflexAnnotation a = new ReflexAnnotation();
a.test("strTest");
}
@SoutAnnotation("testSout")
public void test(String str){
try{
Class stuClass = this.getClass();
Method stuMethod = stuClass.getMethod("test",String.class);
if(stuMethod.isAnnotationPresent(SoutAnnotation.class)){
System.out.println("ReflexAnnotation类上配置了SoutAnnotation注解!");
//获取该元素上指定类型的注解
SoutAnnotation cherryAnnotation = stuMethod.getAnnotation(SoutAnnotation.class);
System.out.println("name: " + cherryAnnotation.value());
}else{
System.out.println("Student类上没有配置SoutAnnotation注解!");
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
2. 用注解方式加aspect切面 配置操作动作 (推荐)
@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SoutAnnotation {
String value() default "default value";
}
@Component
@Aspect
public class SoutAspect {
@Around(value="@annotation(com.tencent.gov.egb.app.test.annotation.SoutAnnotation)")
public Object soutAround(ProceedingJoinPoint joinPoint) throws Throwable {
//执行目标方法,如果不执行该句,目标方法不会被执行
Object obj = joinPoint.proceed();
System.out.println("后置通知");
return obj;
}
}
@RestController
public class SoutController {
@GetMapping("/sout")
@SoutAnnotation
public String sout(@RequestParam("a")String a ){
System.out.println("sout");
return "sout";
}
}
那么如果方法添加了 @SoutAnnotation 注解,就会被切面类拦截,并执行切面方法。