0%

一、概念

  • 轻量级的开源JavaEE框架
  • IOC:控制反转,把创建对象过程交给Spring进行管理
  • Aop:面向切面,不修改源代码进行功能增强

二、IOC容器

2.1 IOC底层原理

  • XML解析、工厂模式、反射

2.2 IOC容器实现方式

2.2.1 两个接口

  • BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供开发人员使用
  • ApplicationContext:BeanFactory的子接口,提供更多更强大的功能,一般由开发人员进行使用
    • ApplicationContext实现类:FileSystemApplicationContext/ClassPathXmlApplicationContext

2.3 Bean管理

2.3.1 两个操作

  • Spring创建对象
  • Spring注入属性

2.3.2 操作Bean管理对象(基础操作/基于XML)

(1)创建/获取对象

1
<bean id="user" class="com.company.spring5.User"></bean>
  • 在Spring配置文件中,使用bean标签,标签里面加对应属性,就可以实现对象创建
  • bean标签属性
    • id:唯一标识,不可以加特殊符号
    • class:类全路径(包类路径)
  • 创建对象时候,默认也是执行无参数构造方法完成对象创建
1
2
3
4
5
6
7
8
// 获取XML文件名获取应用上下文
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

// 根据bean的name属性获取到对象
User user = context.getBean("user", User.class);

// 调用对象方法
user.add();

(2)注入属性

  • DI:依赖注入,就是注入对象属性值

(3)注入方式

  • 第一种:使用set方法进行注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建类
public class Book{
private String bname;
private String bauthor;

// 采用set方法进行属性注入
public void setBname(String bname){
this.bname=bname;
}

public void setBauthor(String bauthor){
this.bauthor=bauthor;
}
}
1
2
3
4
5
6
7
8
<!--
使用property完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<bean id="book" class="com.company.spring5.User">
<property name="bname" value="活着"></property>
</bean>
  • 第二种:使用有参数构造进行注入
1
2
3
4
5
6
7
8
9
10
11
// 创建类
public class Orders{
private String oname;
private String address;

// 有参数构造进行属性注入
public Orders(String oname,String address){
this.oname = oname;
this.address = address;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--
使用constructor-arg完成有参数构造注入
name:类里面属性名称
value:向属性注入的值
index:类中属性的下标,如下里"0"表示oname,"1"表示address
如果不在bean中添加属性注入标签,则表示默认按照无参构造方式注入对象
所以如果不在类中显式的定义无参构造,则xml文件会报错,报错内容为bean中的class
-->
<bean id="orders" class="com.company.spring5.Orders">
<constructor-arg name="oname" value="蛋炒饭"></constructor-arg>
<constructor-arg name="address" value="东华">
<!--
<constructor-arg index="1" value="东华"></constructor-arg>
-->
</bean>
  • 第三种:p名称空间注入(了解)
1
2
3
4
5
6
7
8
9
10
11
<!--
在beans标签中添加p名称空间
xmlns:p="http://www.springframework.org/schema/p"
-->
<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="user" class="com.company.User" p:name="gy" p:age="10"></bean>
</beans>

(4) 注入其他类型属性

  • 字面量
    • null值
    • 属性值包含特殊符号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--
null标签为属性设置空值
-->
<bean id="book" class="com.company.spring5.User">
<property name="bname">
<null/>
</property>
</bean>

<!--
属性值包含特殊符号
<<>>会让属性值内容表示为标签
解决方法:
1. 把<>进行转义,如&lt、&gt
2. 把带特殊符号内容谢到CDATA
-->
<bean id="book" class="com.company.spring5.User">
<property name="bname">
<value><![CDATA[<<南京>>]]></value>
</property>
</bean>

(4) 注入外部bean

  • 比如Service层调用Dao层对象,需要在Bean中进行注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--
在这里进行Service层对象的注入
这样在Controller层,就可以调用userService对象提供的方法来处理我们的请求
-->
<bean id="userService" class="com.company.spring5.userService">
<!--
注入userDao对象
name属性:类里面属性名称
ref属性:创建userDao对象bean标签id值
-->
<property name="userDao" ref="userDaoImpl">
</property>
</bean>
<bean id="userDaoImpl" class="com.company.spring5.userDaoImpl">
</bean>

(5) 注入内部bean和级联赋值

  • 一对多关系:部门和员工

    • 一个部门有多个员工,一个员工属于一个部门
    • 在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性
  • 实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 部门类,生成set方法方便注入
public class Dept {
private String dname;

public void setDname(String dname) {
this.dname = dname;
}
}

// 员工类
public class Emp {
private String ename;
private String gender;

private Dept dept;

public void setEname(String ename) {
this.ename = ename;
}

public void setGender(String gender) {
this.gender = gender;
}

public void setDept(Dept dept) {
this.dept = dept;
}
}
  • 注入内部bean
1
2
3
4
5
6
7
8
9
10
11
<bean id="emp" class="com.company.Emp">
<property name="ename" value="jack"></property>
<property name="gender" value="man"></property>

<!--内部注入部门属性,以嵌套bean的方式实现-->
<property name="dept">
<bean id="dept" class="com.company.Dept">
<property name="dname" value="保安部"></property>
</bean>
</property>
</bean>
  • 级联赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--第一种写法-->
<bean id="emp" class="com.company.Emp">
<property name="ename" value="jack"/>
<property name="gender" value="man"/>

<!--引入外部bean的方式,为外部bean的属性赋值(级联赋值)-->
<property name="dept" ref="dept">
</property>
</bean>
<bean id="dept" class="com.company.Dept">
<property name="dname" value="保安部"/>
</bean>

<!--第二种写法-->
<bean id="emp" class="com.company.Emp">
<property name="ename" value="jack"/>
<property name="gender" value="man"/>

<!--引入外部bean的方式,为外部bean的属性赋值-->
<property name="dept" ref="dept"/>
<!--需要Dept类中属性有get方法-->
<property name="dept.dname" value="技术部"/>
</bean>
<bean id="dept" class="com.company.Dept">
</bean>

2.3.3 操作Bean管理对象(注入集合类型)

(1)注入数组/List/Map/Set类型

  • 为学生类注入集合属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Stu {
// 数据类型属性
private String[] courses;

// List类型属性
private List<String> list;

// Map类型属性
private Map<String,String> maps;

// Set类型属性
private Set<String> sets;

// 生成set方法
public void setSets(Set<String> sets) {
this.sets = sets;
}

public void setCourses(String[] courses) {
this.courses = courses;
}

public void setList(List<String> list) {
this.list = list;
}

public void setMaps(Map<String, String> maps) {
this.maps = maps;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!--
数组对应array>value标签
List对应list>value标签
Map对应map>entry[key,value]标签
Set对应set>value标签
-->
<bean id="stu" class="com.company.Stu">
<property name="courses">
<array>
<value>Java</value>
<value>数据结构</value>
</array>
</property>

<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>

<property name="maps">
<map>
<entry key="Java" value="世界上最好的语言"/>
<entry key="Php" value="世界上最好的语言"/>
</map>
</property>

<property name="sets">
<set>
<value>Mysql</value>
<value>Redis</value>
</set>
</property>
</bean>

(2)集合中注入对象类型值

1
2
3
4
5
6
7
8
9
public class Stu {
// 集合中对象类型注入
private List<Course> courses;

//set方法注入属性
public void setCourses(List<Course> courses) {
this.courses = courses;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--
List中的对象属性
list>ref.bean标签引入外部的对象
-->
<bean id="stu" class="com.company.Stu">
<property name="courses">
<list>
<ref bean="course1"/>
<ref bean="course2"/>
</list>
</property>
</bean>

<bean id="course1" class="com.company.Course">
<property name="cname" value="Java"/>
</bean>

<bean id="course2" class="com.company.Course">
<property name="cname" value="Mysql"/>
</bean>

(4)把集合注入部分提取出来(公共属性)

  • 在spring配置文件中引入util名称空间
  • 使用util:list创建属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--
添加xmlns:util
在xsi:schemaLocation中添加http://www.springframework.org/schema/util/spring-util.xsd
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

<!--提取公共属性-->
<util:list id="bookList">
<value>Java从入门到入土</value>
<value>Php从入门到入土</value>
</util:list>

<bean id="book" class="com.company.Book">
<!--
使用公共list实现属性注入,需要使用ref完成list引入
ref对应到util的id属性
-->
<property name="list" ref="bookList"/>
</bean>
</beans>

2.3.4 操作Bean管理对象(工厂Bean)

  1. Spring有两种类型bean,一种普通bean,另外一种工厂bean
  2. 普通bean:在配置文件中定义bean类型就是返回类型
  3. 工厂bean:在配置文件定义bean类型可以和返回类型不一样
    1. 第一步:创建类,让这个类作为工厂bean,实现接口FactoryBean
    2. 第二步:实现接口里面的方法,在实现的方法中定义返回的bean类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 创建工厂类,实现接口FactoryBean
public class MyBean implements FactoryBean<User> {
@Override
public boolean isSingleton() {
return false;
}

// 工厂可以获取到指定对象,这里获取的是User对象
@Override
public User getObject() throws Exception {
User user = new User();
user.setName("gy");
return user;
}

@Override
public Class<?> getObjectType() {
return null;
}
}

1
2
3
4
5
<!--
注入工厂对象,注入后会调用getObject方法获取到指定的对象
所以才说这里定义的bean,在返回的时候获取的对象可以不一样
-->
<bean id="myBean" class="com.company.factorybean.MyBean"/>
1
2
3
4
5
6
7
8
// 测试方法,根据applicationContext获取到的对象应该使用具体的类,这里使用的是User,所以根据注入的工厂bean获取到的的就是User对象
@Test
public void test1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml");
User myBean = applicationContext.getBean("myBean", User.class);
System.out.println(myBean);

}

2.3.5 操作Bean管理对象(Bean作用域)

(1)bean的性质

  • Spring中,默认情况下,创建的bean是单实例对象,也就是注入的一个bean被两个引用变量获取到后,引用的地址都是相同的

(2)设置单实例/多实例

  • 在spring配置文件标签里面有属性scope用于设置bean是单实例还是多实例
  • scope属性值
    • 第一个:默认值,singleton,表示实例为单实例对象
    • 第二个:prototype,表示是多实例对象
1
2
3
<bean id="book" class="com.company.spring5.User" scope="prototype">
<property name="bname" value="活着"></property>
</bean>

(3)singleton和prototype区别

  • singleton表示单实例,prototype表示多实例
  • 设置scope值是singleton时候,加载spring配置文件(java的ApplicationContext对象创建的时候)时,就会创建单实例对象,单实例对象不管获取多少次地址都是相同的
  • 设置scope值是prototype时,不是在加载spring配置文件时创建,而是在调用getBean方法的时候才创建多实例对象,多实例对象的地址都是不一样的

2.3.6 操作Bean管理对象(Bean生命周期)

(1)生命周期

  • 从对象创建到对象销毁的过程

(2)bean生命周期

  • 通过构造器创建bean实例(无参数构造)
  • 为bean的属性设置值和对其他bean的引用(调用set方法)
  • 把bean实例传递bean前置处理器
  • 调用bean的初始化的方法(需要进行配置)
  • 把bean实例传递bean后置处理器
  • bean可以被获取到了
  • 当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)

(3)演示bean生命周期

  • 实体类,配置无参构造,初始化方法initMethod,销毁方法destory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Order {
private String oname;

public Order(){
System.out.println("第一步:通过无参构造创建bean实例");
}

public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步:调用set方法设置属性");
}

public void initMethod(){
System.out.println("第三步:执行初始化方法");
}

public void destory(){
System.out.println("第四步:执行销毁方法");
}
}

  • bean注入
1
2
3
4
5
6
7
<!--
在bean中添加init-method、destroy-method属性
并添加上我们在类中实现的方法
-->
<bean id="myOrder" class="com.company.Order" init-method="initMethod" destroy-method="destory">
<property name="oname" value="123"/>
</bean>
  • 测试
1
2
3
4
5
6
7
8
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");

// 获取bean实例对象
Object myBean = context.getBean("myOrder");


// 手动让bean实例销毁,此方法只有ClassPathXmlApplicationContext有
context.close();
  • 结果

(4)后置处理器

  • bean实例初始化前后的操作,具体操作如下
  • 创建类,实现接口BeanPostProcessor,创建后置处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 继承BeanPostProcessor的类可以重写方法
// 这两个方法可以在bean实例初始化前后被调用,可以对bean实例进行操作
public class MyBeanPost implements BeanPostProcessor {
// 初始化前的操作
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return null;
}

// 初始化后的操作
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return null;
}
}
  • 配置bean
1
2
3
<bean id="myOrder" class="com.company.Order" init-method="initMethod" destroy-method="destory">
<property name="oname" value="123"/>
</bean>

2.4 xml自动装配

  • 根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入
1
2
3
4
5
6
7
8
<!--
bean标签属性autowire可以实现自动装配
autowire属性值有byName和byType
byName要求bean中的id要和对应类的属性名一致,才能自动装配该对象
byType要求类型一致,才能自动装配该对象,注意要有两个相同类型的bean就会报错
-->
<bean id="emp" class="com.company.Emp" autowire="byType"/>
<bean id="dept" class="com.company.Dept"/>

2.5 IOC操作bean管理(外部属性文件)

  • 使用情况:一般在spring项目要配置properties配置文件,填写数据库信息,这里就需要使用xml以引入外部文件的方式,配置连接数据库的bean
  • 直接配置数据库信息

    • 配置德鲁伊druid连接池
  • 引入外部属性文件配置数据库连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--
先引入druid.jar包,在xml中引入命名context空间
xmlns:util="http://www.springframework.org/schema/util"
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
通过context引入properties配置文件
在druid中通过${}引入配置文件中的信息,注入数据库连接池信息
-->

<context:property-placeholder location="classpath:application.properties"/>


<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"/>
<property name="url" value="${prop.url}"/>
<property name="username" value="${prop.userName}"/>
<property name="password" value="${prop.password}"/>
</bean>

2.6 IOC操作Bean管理(基于注解方式)

2.6.1 Spring提供的注解

  • @Component
  • @Service
  • @Controller
  • @Repository
  • 以上的注解功能都是一样的,都可以用来创建bean实例,主要是方面理解

2.6.2 注解方式创建对象

  • 第一步:引入spring-aop包
  • 第二步:在bean配置文件中,开启组件扫描
1
<context:component-scan base-package="包地址 包地址 ..." />
  • 第三步:在类上添加注解,spring即可扫描到该类并完成对象的注入

  • 默认注入bean的id为类名称首字母小写:如UserService=>userService

  • 注解Repository(value=”userSerivice”)中的value值等同于bean的id值

2.6.3 注解方式注入属性

  • 提供的注解
    • @Autowired:根据属性类型进行自动装配
    • @Qualifier:根据属性名称进行注入,@Qualifier(value=”注入类的value/id值”),需要和@Autowired一起使用,就是叠加在一起作用在属性上
    • @Resource:可以根据类型/名称注入,@Resource默认按照属性类型进行自动装配,@Resource(name=””)即可以按照属性名称进行注入
    • @Value:完成普通属性的注入,如@Value(value=”123”),可以为String类型属性注入属性值
  • 以上操作都需要在bean配置文件中开启扫描才能运行,意思是在获取注入的对象时,仍然要用ApplicationContext获取到bean配置文件

2.6.4 完全注解开发

  • 第一步:创建配置类config/,这一步完成后就可以删除bean配置文件,因为这一步的作用和bean配置文件是一样的
1
2
3
4
5
@Configuration
@ComponentScan(basePackages={"com.company"}) //配置需要扫描的包名
public class SpringConfig{

}
  • 第二步:获取对象
1
2
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class)
UserService userService = context.getBean("userService",UserService.class)

三、AOP

3.1 概念

  • 面向切面编程:在不修改源代码的情况下在主干功能里添加新的功能模块

3.2 AOP底层原理

3.2.1 动态代理

  • Spring5已经对动态代理做了封装,可以直接调用,需要的知识点:反射

  • 第一种:有接口情况,使用JDK动态代理

    • 创建接口实现类代理对象,增强类的方法
  • 第二种:没有接口情况,使用CGLIB动态代理

3.2.2 AOP(JDK动态代理)

  • JDK动态代理,使用Proxy类里面的方法创建代理对象
    • 调用newProxyInstance方法
  • newProxyInstance方法包含的三个参数
    • 第一个参数:类加载器
    • 第二个参数:增强方法所在的类,这个类实现的接口,支持多个接口
    • 第三个参数:实现接口InvocationHandler,创建代理对象,写增强的方法
  • 实现步骤:
    • 创建接口,定义方法,如UserDao
    • 创建接口实现类,实现方法,如UserDaoImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class JDKProxy{
public static void main(String[] args){
Class[] interfaces = {UserDao.class};
// 写法一:匿名内部类
/*
Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
return null;
}
});
*/
UserDaoImpl userDao = new UserDaoImpl();

// 写法二:创建代理对象
UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao));
dao.add(1,2); // 相加的方法

}
}


class UserDaoProxy implements InvocationHandler{
// 写Onject是为了代码更通用
private Object obj;

// 获取到代理对象所代理的对象
public UserDaoProxy(Object obj){
this.obj = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("方法执行前..."+method.getName+
"\n传递的参数..."+Arrays.toString(args));

// 执行增强方法
Object res = method.invoke(obj,args);

System.out.println("方法执行后...")
return res;
}
}

3.3 AOP专业术语

  • 连接点:类中可以被增强的方法,这些方法称为连接点
  • 切入点:真正被增强的方法,称为切入点
  • 通知(增强):实际增强的逻辑部分
    • 前置通知
    • 后置通知
    • 环绕通知
    • 异常通知
    • 最终通知 finally
  • 切面:动作,如权限判断
    • 把通知应用到切入点的过程

3.4 AOP操作准备

3.4.1 概述

  • Spring框架一般都是基于AspectJ实现AOP操作
  • AspectJ不是Spring组成部分,独立的AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作
  • 基于AspectJ实现AOP操作
    • 基于xml配置文件实现
    • 基于注解方式实现(使用)
  • 需要引入依赖包

3.4.2 切入点表达式

1
2
3
4
5
6
7
8
9
// execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
// 例1:对BookDao类里的add进行增强
execution(* com.company.dao.BookDao.add(...));

// 例2:对BookDao类里的所有方法进行增强
execution(* com.company.dao.BookDao.*(...));

// 例2:对包dao中所有类所有方法进行增强
execution(* com.company.dao.*.*(...));

3.5 AspectJ注解

3.5.1 操作步骤

  • 创建类,定义方法,如User类
  • 创建增强类,编写增强逻辑,如UserProxy
    • 在增强类中,创建方法,让不同方法代表不同通知类型
  • 进行通知的配置
    • 在Spring配置文件中,开启注解扫描
    • 使用注解创建User(@Component)和UserProxy对象
    • 在增强类上面添加注解@Aspect
    • 在Spring配置文件中开启生成代理对象
1
2
3
4
5
<!--开启注解扫描-->
<context:component-scan base-package="包地址 包地址 ..." />

<!--开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  • 配置不同类型的通知
    • 在增强类中,在通知方法上添加通知类型注解,使用切入点表达式进行配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Component
@Aspect
public class UserProxy{
// 前置通知
@Before(value = "execution(* com.company.User.add([参数]))")
public void before(){
System.out.println("before......")
}

// 最终通知,方法之后执行
@After(value = "execution(* com.company.User.add([参数]))")
public void after(){
System.out.println("after......")
}

// 后置通知,返回值之后执行,有异常时不执行
@AfterReturning(value = "execution(* com.company.User.add([参数]))")
public void afterReturning(){
System.out.println("afterReturning......")
}

// 异常通知,有异常才执行
@AfterThrowing(value = "execution(* com.company.User.add([参数]))")
public void afterThrowing(){
System.out.println("afterThrowing......")
}

// 环绕通知,此注解下的方法,在被增强方法执行之前和之后都执行
@Around(value = "execution(* com.company.User.add([参数]))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("around before......");

// 被增强方法执行
proceedingJoinPoint.proceed();

System.out.println("around after......");
}

}

3.5.2 相同切入点抽取

  • 将方法增强的切入点写成公共的部分,方便调用和切入点的统一修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Pointcur(value = "execution(* com.company.User.add([参数]))")
public void pointdemo(){

}

@Component
@Aspect
public class UserProxy{
// 前置通知,加入方法名称
@Before(value = "pointdemo()")
public void before(){
System.out.println("before......")
}
}

3.5.3 增强类设置优先级

  • 多个增强类可以增强同一个方法,因此可以为增强类设置优先级,
  • 方法:在增强类上面添加注解@Order(数值型),数字类型值越小,优先级越高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
@Aspect
@Order(1)
public class PersonProxy{
@Pointcur(value = "execution(* com.company.User.add([参数]))")
public void pointdemo(){

}

// 前置通知,加入方法名称
@Before(value = "pointdemo()")
public void before(){
System.out.println("before......")
}
}

3.6 AspectJ配置文件

1
2
3
4
5
6
7
8
9
10
<aop:config>
<!--配置切入点-->
<aop:pointcut id="p" expression="execution(* com.company.Book.buy(...))"/>

<!--配置切面,ref对应注入的增强类-->
<aop:aspect ref="bookProxy">
<!--配置切面,ref对应前面的切入点id-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>

3.7 完全注解开发

1
2
3
4
5
6
@Configuration
@ComponentScan(basePackages={"com.company"}) //配置需要扫描的包名
@EnableAspectJAutoProxy(proxyTargetClass=true) // 默认为true,开启Aspect生成代理对象
public class AopConfig{

}

四、JdbcTemplate

4.1 准备工作

  • 引入依赖包
    • mysql-connect
    • spring-jdbc
    • spring-tx:处理事务的包
  • 配置数据库连接池(druid)
  • 注入JdbcTemplate对象
1
2
3
4
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入连接池对象属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
  • 开启组件扫描

4.3 CURD实现

4.3.1 添加操作

  • 创建实体类User,创建Dao层UserDao,创建Service层UserService
1
2
3
4
5
6
7
8
9
10
11
12
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public void add(User user){
String sql = "insert into user values(?,?,?)";

Object[] args = {user.getUserId(),user.getUserName(),user.getPassword()}
jdbcTemplate.update(sql,args);
}
}

4.3.2 修改/删除操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public void update(User user){
String sql = "update user set user_name=?,password=? where user_id=?";

Object[] args = {user.getUserName(),user.getPassword(),user.getUserId()}
jdbcTemplate.update(sql,args);
}

@Override
public void delete(String id){
String sql = "delete from user where user_id=?";

jdbcTemplate.update(sql,id);
}
}

4.3.3 查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;

// 查询用户数
@Override
public int selctCount(){
String sql = "select count(*) from user";

Integer count = jdbcTemplate.queryForObject(sql,Integer.class);

return count;
}

// 根据id查询用户
@Override
public User findUserInfo(String id){
String sql = "select * from user where user_id=?";
// BeanPropertyRowMapper用于封装查询到的数据
User user = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<User>(User.class),id);

return user;
}

// 查询返回集合
public List<User> findAll(){
String sql = "select * from user";

List<User> users = jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class));

return users;
}
}

4.3.4 批量操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;

// 批量添加,List<Object[]>中的Object[]表示参数的集合
public int[] batchAdd(List<Object[]> barchArgs){
String sql = "insert into user values(?,?,?)";

// 返回的是影响的行数数组
int[] res = jdbcTemplate.batchUpdate(sql,batchArgs);

return res;
}

// 批量修改
public int[] batchUpdate(List<Object[]> barchArgs){
String sql = "update user set user_name=?,password=? where user_id=?";

// 返回的是影响的行数数组
int[] res = jdbcTemplate.batchUpdate(sql,batchArgs);

return res;
}

// 批量删除
public int[] batchDelete(List<Object[]> barchArgs){
String sql = "delete from user where user_id=?";

// 返回的是影响的行数数组
int[] res = jdbcTemplate.batchUpdate(sql,batchArgs);

return res;
}
}

五、事务管理

5.1 概念

  • 一组操作的组合
  • 事务的一些问题请转到数据库笔记中进行学习

5.2 事务特性ACID

  • 原子性
  • 一致性
  • 隔离性
  • 持久性

5.3 准备工作

  • 以银行转账操作为例
    • 创建数据库表,记录用户资金
    • 创建service、dao完成对象创建和注入
    • 创建jdbcTemplate对象
    • dao创建两个方法,某用户资金增加,另外用户资金减少
    • service创建转账方法,实现转账操作

5.4 事务操作

5.4 1 声明式事务管理

  • 基于注解方式
  • 基于xml方式

5.4.2 Spring事务管理API

  • Spring提供一个接口,代表事务管理器,这个接口针对不同框架提供不同的实现类

5.4.2 注解方式

  • 注入事务管理对象
1
2
3
4
5
<!--由于使用的式jdbc框架,所以注入DataSourceTransactionManager对象-->
<bean id="transactionManager" class="org.springframework.jdbc.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
  • 开启扫描
  • xml引入名称空间tx,同前面的引入操作
  • 开启事务注解
1
<tx:annotation-driven transacntion-manager="transactionManager"></tx:annotation-driven>
  • 在Service层添加事务注解@Transactional
    • @Transactional可以加到类上,也可以加到方法上
    • 加到类上,表示作用到类中的所有方法

5.4.3 注解的参数

(1)propagation:事务传播行为

  • 多事务方法直接进行调用,这个过程中事务是如何进行管理的,必须是对数据库中的数据产生变化的操作
  • 简单来说就是,就是添加了@Transaction事务管理的方法去调用未添加事务管理的方法,事务的操作过程是怎样的
  • 下表均已上图为例,在事务管理方法中调用非事务管理方法,非事务管理方法运行的情况
  • 配置方法:@Transactional(propagation = Propagation.REQUIRED)
传播属性 描述
REQUIRED(default) 有事务就在当前事务运行,没有事务就创建事务运行
REQUIRED_NEW 无论有没有事务,都会创建一个事务在里面运行
SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORT 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起
MANDATORY 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行

(2)ioslation:事务隔离级别

  • 隔离性:多事务操作之间不会产生影响
  • 多事务带来的问题:脏读、不可重复读、虚读
    • 脏读:未提交的事务读取另一个未提交事务的数据
    • 不可重复读:两个事务同时读到一条数据,一个事务读到了另一个事务提交之前的数据(数据不一致了)
    • 虚读:一个未提交的事务读取到另一个提交事务的添加数据
  • 配置方式:@Transactional(ioslation= Ioslation.READ_UNCOMMITTED)
脏读 不可重复读 虚读
READ UNCOMMITTED(读未提交)
READ COMMITTED(读已提交)
REPEATABLE READ(可重复读/default)
SERIALIZABLE(串行化)

(3)timeout:超时时间

  • 事务需要在一定时间内进行提交,如果不提交就进行回滚,默认是-1,单位是秒

(4)readOnly:是否只读

  • 读:查询操作,写:增删改

  • 默认为false

(5)rollbackFor:回滚

  • 设置出现哪些异常进行事务回滚
  • 设置该属性值为异常的class即可

(6)noRollbackFor:不回滚

  • 设置出现哪些异常不进行事务回滚

5.5 事务完全注解开发

  • 创建配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Configuration
@ComponentScan(basePackages = "com.kuang")
@EnableTransactionManagement // 开启事务
public class TxConfig{
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");

return dataSource;
}

//创建JdbcTemplate
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);

return jdbcTemplate;
}

// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}

六、Spring5新特性

一、基础练习

1.1 音阶练习

  • 指板音阶分布

1.2 节奏练习

  • 四分音符、八分音符、十六分音符、三十二分音符、休止符
  • 切分音符、附点八分音符、延音、三连音
  • 前八后十六、前十六后八
  • 扫弦节奏型

1.3 和声练习

  • 和弦转换、和弦转位、三和弦、七和弦
  • 大小调音阶
  • 分解和弦

1.3 技巧练习

  • 击弦、勾弦、滑弦、无尾滑音、闷弦、揉弦
  • 打板

二、进阶练习

一、概述

音乐风格经历了许多艺术家的发展,到如今合成器的泛滥,让音乐制作的门槛降低了许多,音乐风格发展的脉络主要有以下几种:

  • 古典音乐
  • 布鲁斯
  • 爵士,爵士布鲁斯的结合
  • 摇滚乐
  • 重金属

不同的音乐风格有着不同的特点,比如布鲁斯就用到了许多切分节奏,爵士乐则大量使用到了7和弦、9和弦等复杂和弦,听感上色彩比较丰富

二、布鲁斯

布鲁斯音阶:1、b3、4、b5、5、b7或者6、1、2、b3、3、5

标准的12小节E调布鲁斯进行如下:

Shuffle、Swing、Riff节奏

Shuffle/Swing:一种摇摆的节奏型,具体来说就是将三连音中间的音符换成延音或者休止符

Riff:对某一特定片段的重复演奏,可以用来丰富Swing的节奏感

强力和弦,在很多人的理解中是由两个音组成的,一个是根音,一个是向上的五音。其实,重力和弦应该有一个更准确的叫法,以这里的E重力和弦为例,我们可以称它为E5和弦,因为它没有3音

一、常见和声套路

1.1 卡农和弦

1
2
3
4
5
6
1 5 6 3 4 1 2(4) 5

C G Am Em F C F G
忽然 之间 天昏 地暗 世界可 以忽然什么 都没有

1 7 6 5 4 3 2 1(5)

一、Hystrix

1.1 服务雪崩

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

  • 服务降级:
    • 超时导致服务器变慢—->超时不再等待—->服务降级
    • 出错(宕机或程序运行出错)—->出错有兜底—->服务降级
  • 服务熔断:
  • 服务限流:

1.2 Hystrix概念

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性

“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

Hystrix的作用:

  • 服务降级
  • 服务熔断
  • 接近实时监控

1.3 服务端实现

1.3.1 依赖配置

  • 创建项目cloud-provider-hystrix-payment8001
  • 引入Hystrix依赖
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
  • 配置application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 8001

spring:
application:
name: cloud-provider-hystrix-payment

eureka:
client:
register-with-eureka: true #false表示不向注册中心注册自己。
fetch-registry: true #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka7001.com:7001/eureka/

1.3.2 创建业务类

  • 创建启动类
1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
  • 创建service层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class PaymentService {
//正常访问,肯定不会报错
public String paymentInfo_OK(Integer id){
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
}

// 模拟超时
public String paymentInfo_TimeOut(Integer id){
int timeNumber=3;
try{
TimeUnit.SECONDS.sleep(timeNumber); // 等待3秒
}catch(InterruptedException e){
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时"+timeNumber+"秒钟";
}
}
  • 创建controller层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;

@Value("${server.port}")
private String serverPort;

@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("*********result:",result);
return result;
}

@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("*********result:",result);
return result;
}
}

1.3.3 启动流程

注意启动时先将eureka服务注册中心修改为单机版,主要是为了简单

  • 启动eureka7001
  • 启动cloud-provider-hystrix-payment8001
  • 访问接口

1.4 高并发测试

1.5 客户端实现

  • 客户端和服务端的创建都是类似的
  • 创建工程cloud-consumer-feign-hystrix-order8080
  • 配置yml
1
2
3
4
5
6
7
8
9
server:
port: 8080

eureka:
client:
register-with-eureka: false #false表示不向注册中心注册自己。
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka7001.com:7001/eureka/
  • 启动类OrderHystrixMain8080
1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain8080 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain8080.class,args);
}

}
  • 业务接口PaymentHystrixService
1
2
3
4
5
6
7
8
9
@Service
@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);

@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
  • 请求类OrderHystrixController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;

@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}

@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}

1.6 服务降级

  • 服务降级可以配置在服务端,也可以配置客户端,一般会配置在客户端

  • 主启动类使用@EnableCircuitBreaker开启降级,并添加@EnableHystrix开启Hystrix

  • 方法上使用@HystrixCommand注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@Slf4j
public class OrderHystrixController {
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties={
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
public String paymentInfo_TimeOut(Integer id){
int timeNumber=5;
try{
TimeUnit.SECONDS.sleep(timeNumber); // 等待3秒
}catch(InterruptedException e){
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时"+timeNumber+"秒钟";
}

public String paymentInfo_TimeOutHandler(Integer id){
return "接口调用异常:\t"+"当前线程名称\t"+Thread.currentThread().getName();
}
}
  • yml配置
1
2
3
feign:
hystrix:
enable: true
  • 在接口类上添加注解@DefaultProperties启动默认服务降级方法配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@DefaultProperties(defaultFallback = "paymentInfo_TimeOutHandler")
public class OrderHystrixController{
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand // HystrixCommand还是要在需要降级的请求方法上加上
public String paymentInfo_TimeOut(Integer id){
int timeNumber=5;
try{
TimeUnit.SECONDS.sleep(timeNumber); // 等待3秒
}catch(InterruptedException e){
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时"+timeNumber+"秒钟";
}

public String paymentInfo_TimeOutHandler(Integer id){
return "接口调用异常:\t"+"当前线程名称\t"+Thread.currentThread().getName();
}
}

1.7 服务降级解耦

  • 因为目前兜底方法和接口方法写在了同一个接口类中,这样如果为每一个接口方法添加一个兜底方法就会导致代码冗余
  • 那么我们就可以考虑新建一个类用来实现业务接口,将兜底方法都写在这个类中,这样就解决了代码的冗余
  • 接下来的服务降级都配置在客户端,创建PaymentFallbackService实现业务接口
1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class PaymentFallbackService implements PaymentHystrixService{
@Override
public String paymentInfo_OK(Integer id){
return "------PaymentHystrixService fall back-paymentInfo_OK";
}

@Override
public String paymentInfo_TimeOut(Integer id){
return "------PaymentHystrixService fall back-paymentInfo_TimeOut";
}
}
  • 在业务类上的@FeignClient上添加fallback属性,将上面的实现类注册进Feign中
1
2
3
4
5
6
7
8
9
@Service
@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE" fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);

@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}

一、结构体

1.1 概述

C++中的结构体可以定义函数,C语言中的结构体只能定义变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Sales_data{
Sales_data() = default; // = default表示默认的构造函数
Sales_data(const std::string &s):bookNo(s){}
Sales_data(const std::string &s, unsigned n, double p):bookNo(s),units_sold(n),revenue(p*n){}
Sales_data(std::istream &); // 没有加形参名表示只是声明类型,但没有实现,实现时需要加上形参名

std::string isbn() const {return bookNo;}
Sales_data& combine(const Sales_data&);
double avg_price() const;
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};

// 类外实现
Sales_data::Sales_data(std::istream &is){
read(is, *this);
}

1.2 动态内存分配

  • new/delete分配堆空间可以调用类的构造函数/析构函数
  • malloc/free只是一个函数调用,不会调用构造函数/析构函数,malloc接受的参数是一个unsigned long类型
1
2
3
4
5
Time *t2 = new Time(0,0,0);
delete t2;

Time *t1 = (Time*)malloc(sizeof(Time));
free(t1);

二、类

2.1 类的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class ClassName
{
public:
//公共的行为或属性
ClassName(); // 构造函数
ClassName(string x);
void setName(string x);
string getName();
~ClassName(); // 析构函数

private:
//私有成员
string x;
};

// 类外实现
ClassName::ClassName() {
this->x = "hello world";
cout << "开始创建对象:" << this->x << endl;
}

ClassName::ClassName(string x) {
this->x = x;
}

void ClassName::setName(string x) {
this->x = x;
}

string ClassName::getName() {
return this->x;
}

ClassName::~ClassName() {
cout << "销毁对象:" << this->x << endl;
}

int main() {
ClassName *cn = new ClassName();

cn->setName("hello gaoyue");
cout<<cn->getName()<<endl;

delete cn;

return 0;
}
/*
开始创建对象:hello world
hello gaoyue
销毁对象:hello gaoyue
*/

2.1.1 对象的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 默认无参构造
// 显示使用
ClassName cn = ClassName(); // 无法delete,会调用构造函数,程序结束后会调用析构函数
// 隐式使用
ClassName cn;
// new创建
ClassName *cn = new ClassName;

// 有参构造
// 显示使用
ClassName cn = ClassName("hello gaoyue");
// 隐式使用
ClassName cn("hello gaoyue");
// new创建
ClassName *cn = new ClassName("hello gaoyue");

2.1.2 const成员函数

有时候,我们创建了一个对象,但是事实上,我们只希望这个对象初始化之后不被改变,它可以是一个真理或者是什么,就是不能被改变

然后我们就十分自然的想到const创建一个对象。以下面的代码为例,但是后面发现就算是调用对象中不会改变值的showInfo()函数的时候,也会报错。

1
const ClassName *cn = new ClassName();

如果这个常量对象想要调用不改变值的方法,那么这个方法上也必须加上const关键字

1
2
3
4
5
6
7
8
class ClassName{
public:
string getName() const;
}

string ClassName::getName() const {
return this->x;
}

2.1.3 友元函数

  • 某非成员函数在类中被声明为友元函数,其可以直接访问类中的成员变量(包括私有和保护)
  • 使普通函数能够访问类的友元
1
2
3
4
5
6
7
8
9
10
11
12
13
class INTEGER{
friend void Print(const INTEGER& obj);//声明友元函数
};

void Print(const INTEGER& obj){
   //函数体
}

void main(){
  INTEGER obj;
  Print(obj);//直接调用
}

2.1.4 友元类

  • 类Y的所有成员函数都为类X友元函数
  • 使用单个声明使Y类的所有函数成为类X的友元,它提供一种类之间合作的一种方式,使类Y的对象可以具有类X和类Y的功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class girl;

class boy{
public:
  void disp(girl &);
};

void boy::disp(girl &x){ //函数disp()为类boy的成员函数,也是类girl的友元函数
  cout<<"girl's name is:"<<x.name<<",age:"<<x.age<<endl;//借助友元,在boy的成员函数disp中,借助girl的对象,直接访问girl的私有变量
}

class girl{
private
  char *name;
  int age;
  friend boy; //声明类boy是类girl的友元
};

  • 成员函数有this指针,而友元函数没有this指针
  • 友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友
  • 友元函数的调用无需通过对象调用,而是同调用普通函数一样被调用

2.2 构造函数与析构函数

2.3 继承与多态

一、序列式容器

1.1 Vector

1.1.1 引入

1
2
#include < vector> 
using namespace std;

1.1.2 常用方法

(1)构造函数

  • vector():创建一个空vector
  • vector(int nSize):创建一个vector,元素个数为nSize
  • vector(int nSize,const t& t):创建一个vector,元素个数为nSize,且值均为t
  • vector(const vector&):复制构造函数
  • vector(begin,end):复制[begin,end)区间内另一个数组的元素到vector中

(2)增加函数

  • void push_back(const T& x):向量尾部增加一个元素X
  • iterator insert(iterator it,const T& x):向量中迭代器指向元素前增加一个元素x
  • iterator insert(iterator it,int n,const T& x):向量中迭代器指向元素前增加n个相同的元素x
  • iterator insert(iterator it,const_iterator first,const_iterator last):向量中迭代器指向元素前插入另一个相同类型向量的[first,last)间的数据

(3)删除函数

  • iterator erase(iterator it):删除向量中迭代器指向元素
  • iterator erase(iterator first,iterator last):删除向量中[first,last)中元素
  • void pop_back():删除向量中最后一个元素
  • void clear():清空向量中所有元素

(4)遍历函数

  • reference at(int pos):返回pos位置元素的引用
  • reference front():返回首元素的引用
  • reference back():返回尾元素的引用
  • iterator begin():返回向量头指针,指向第一个元素
  • iterator end():返回向量尾指针,指向向量最后一个元素的下一个位置
  • reverse_iterator rbegin():反向迭代器,指向最后一个元素
  • reverse_iterator rend():反向迭代器,指向第一个元素之前的位置

(5)判断函数

  • bool empty() const:判断向量是否为空,若为空,则向量中无元素

(6)大小函数

  • int size() const:返回向量中元素的个数
  • int capacity() const:返回当前向量张红所能容纳的最大元素值
  • int max_size() const:返回最大可允许的vector元素数量值

(7)其他函数

  • void swap(vector&):交换两个同类型向量的数据
  • void assign(int n,const T& x):设置向量中第n个元素的值为x
  • void assign(const_iterator first,const_iterator last):向量中[first,last)中元素设置成当前向量元素

1.2 deque

1.2.1 引入与使用

1
2
3
4
5
6
7
#include<deque>  // 头文件
deque<type> deq; // 声明一个元素类型为type的双端队列que
deque<type> deq(size); // 声明一个类型为type、含有size个默认值初始化元素的的双端队列que
deque<type> deq(size, value); // 声明一个元素类型为type、含有size个value元素的双端队列que
deque<type> deq(mydeque); // deq是mydeque的一个副本
deque<type> deq(first, last); // 使用迭代器first、last范围内的元素初始化deq

1.2.2 常用方法

  • deq[]:用来访问双向队列中单个的元素。
  • deq.front():返回第一个元素的引用。
  • deq.back():返回最后一个元素的引用。
  • deq.push_front(x):把元素x插入到双向队列的头部。
  • deq.pop_front():弹出双向队列的第一个元素。
  • deq.push_back(x):把元素x插入到双向队列的尾部。
  • deq.pop_back():弹出双向队列的最后一个元素。

1.2.3 与Vector的区别

  • Vector是单向开口的连续线性空间,deque则是一种双向开口的连续线性空间。
  • deque对象在队列的两端放置元素和删除元素是高效的,而向量vector只是在插入序列的末尾时操作才是高效的。
  • deque和vector的最大差异,一在于deque允许于常数时间内对头端进行元素的插入或移除操作,二在于deque没有所谓的capacity观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。换句话说,像vector那样“因旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque中是不会发生的。也因此,deque没有必要提供所谓的空间预留(reserved)功能

1.3 queue

  • 队列容器,只能在容器的末尾添加新元素,只能从头部移除元素
  • front():返回 queue 中第一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
  • back():返回 queue 中最后一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
  • push(const T& obj):在 queue 的尾部添加一个元素的副本。这是通过调用底层容器的成员函数 push_back() 来完成的。
  • push(T&& obj):以移动的方式在 queue 的尾部添加元素。这是通过调用底层容器的具有右值引用参数的成员函数 push_back() 来完成的。
  • pop():删除 queue 中的第一个元素。
  • size():返回 queue 中元素的个数。
  • empty():如果 queue 中没有元素的话,返回 true。
  • emplace():用传给 emplace() 的参数调用 T 的构造函数,在 queue 的尾部生成对象。
  • swap(queue\ &other_q):将当前 queue 中的元素和参数 queue 中的元素交换。它们需要包含相同类型的元素。也可以调用全局函数模板 swap() 来完成同样的操作。

1.4 stack

  • top():返回一个栈顶元素的引用,类型为 T&。如果栈为空,返回值未定义。
  • push(const T& obj):可以将对象副本压入栈顶。这是通过调用底层容器的 push_back() 函数完成的。
  • push(T&& obj):以移动对象的方式将对象压入栈顶。这是通过调用底层容器的有右值引用参数的 push_back() 函数完成的。
  • pop():弹出栈顶元素。
  • size():返回栈中元素的个数。
  • empty():在栈中没有元素的情况下返回 true。
  • emplace():用传入的参数调用构造函数,在栈顶生成对象。
  • swap(stack\ & other_stack):将当前栈中的元素和参数中的元素交换。参数所包含元素的类型必须和当前栈的相同。对于 stack 对象有一个特例化的全局函数 swap() 可以使用。

1.5 priority_queue

  • 维护一个堆的数据结构,可以是最大堆,也可以是最小堆
  • top() 访问队头元素

  • empty() 队列是否为空

  • size() 返回队列内元素个数
  • push() 插入元素到队尾 (并排序)
  • emplace() 原地构造一个元素并插入队列
  • pop() 弹出队头元素
  • swap() 交换内容
1
2
3
4
//升序队列,小顶堆
priority_queue <int,vector<int>,greater<int> > q;
//降序队列,大顶堆
priority_queue <int,vector<int>,less<int> >q;

1.6 List

1
#include <list>
  • assign() 给list赋值
  • back() 返回最后一个元素
  • begin() 返回指向第一个元素的迭代器
  • clear() 删除所有元素
  • empty() 如果list是空的则返回true
  • end() 返回末尾的迭代器
  • erase() 删除一个元素
  • front() 返回第一个元素
  • get_allocator() 返回list的配置器
  • insert() 插入一个元素到list中
  • max_size() 返回list能容纳的最大元素数量
  • merge() 合并两个list
  • pop_back() 删除最后一个元素
  • pop_front() 删除第一个元素
  • push_back() 在list的末尾添加一个元素
  • push_front() 在list的头部添加一个元素
  • rbegin() 返回指向第一个元素的逆向迭代器
  • remove() 从list删除元素
  • remove_if() 按指定条件删除元素
  • rend() 指向list末尾的逆向迭代器
  • resize() 改变list的大小
  • reverse() 把list的元素倒转
  • size() 返回list中的元素个数
  • sort() 给list排序
  • splice() 合并两个list
  • swap() 交换两个list
  • unique() 删除list中相邻重复的元素

二、关联式容器

2.1 map

1
2
// map和multimap都需要#include<map>
#include<map>
  • begin():返回指向map头部的迭代器
  • clear():删除所有元素
  • count():返回指定元素出现的次数
  • empty():如果map为空则返回true
  • end():返回指向map末尾的迭代器
  • equal_range():返回特殊条目的迭代器对
  • erase():删除一个元素
  • find():查找一个元素
  • get_allocator():返回map的配置器
  • insert():插入元素
  • key_comp():返回比较元素key的函数
  • lower_bound():返回键值>=给定元素的第一个位置
  • max_size():返回可以容纳的最大元素个数
  • rbegin():返回一个指向map尾部的逆向迭代器
  • rend():返回一个指向map头部的逆向迭代器
  • size():返回map中元素的个数
  • swap():交换两个map
  • upper_bound():返回键值>给定元素的第一个位置
  • value_comp():返回比较元素value的函数

2.2 unordered_map

1
2
3
4
5
6
7
8
9
10
11
12
#include<unordered_map>
unordered_map<string,int> mp;
//迭代器遍历
unordered_map<string,int>::iterator iter;
for(iter=mp.begin();iter!=mp.end();iter++){
cout<<iter->first<<": "<<iter->second<<endl;
}

//:遍历
for(auto &[name,value]:mp){
cout<<name<<": "<<value<<endl;
}

2.3 set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// constructing sets
#include <iostream>
#include <set>

bool fncomp (int lhs, int rhs) {return lhs<rhs;}

struct classcomp {
bool operator() (const int& lhs, const int& rhs) const
{return lhs<rhs;}
};

int main ()
{
std::set<int> first; // empty set of ints

int myints[]= {10,20,30,40,50};
std::set<int> second (myints,myints+5); // range

std::set<int> third (second); // a copy of second

std::set<int> fourth (second.begin(), second.end()); // iterator ctor.

std::set<int,classcomp> fifth; // class as Compare

bool(*fn_pt)(int,int) = fncomp;
std::set<int,bool(*)(int,int)> sixth (fn_pt); // function pointer as Compare

return 0;
}
  1. begin():返回指向第一个元素的迭代器
  2. clear():清除所有元素
  3. count():返回某个值元素的个数
  4. empty():如果集合为空,返回true
  5. end():返回指向最后一个元素的迭代器
  6. equal_range():返回集合中与给定值相等的上下限的两个迭代器
  7. erase():删除集合中的元素
  8. find():返回一个指向被查找到元素的迭代器
  9. get_allocator():返回集合的分配器
  10. insert():在集合中插入元素
  11. lower_bound():返回指向大于(或等于)某值的第一个元素的迭代器
  12. key_comp():返回一个用于元素间值比较的函数
  13. max_size():返回集合能容纳的元素的最大限值
  14. rbegin():返回指向集合中最后一个元素的反向迭代器
  15. rend():返回指向集合中第一个元素的反向迭代器
  16. size():集合中元素的数目
  17. swap():交换两个集合变量
  18. upper_bound():返回大于某个值元素的迭代器
  19. value_comp():返回一个用于比较元素间的值的函数

2.4 unordered_set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// constructing unordered_sets
#include <iostream>
#include <string>
#include <unordered_set>

template<class T>
T cmerge (T a, T b) { T t(a); t.insert(b.begin(),b.end()); return t; }

int main ()
{
std::unordered_set<std::string> first; // empty
std::unordered_set<std::string> second ( {"red","green","blue"} ); // init list
std::unordered_set<std::string> third ( {"orange","pink","yellow"} ); // init list
std::unordered_set<std::string> fourth ( second ); // copy
std::unordered_set<std::string> fifth ( cmerge(third,fourth) ); // move
std::unordered_set<std::string> sixth ( fifth.begin(), fifth.end() ); // range

std::cout << "sixth contains:";
for (const std::string& x: sixth) std::cout << " " << x;
std::cout << std::endl;

return 0;
}
  • 主要方法与set类似
  • emplace(key):插入元素

2.5 pair

2.5.1 概述

  • pair是c++的一种数据类型,在头文件utility中

2.5.2 使用

1
2
3
4
5
6
7
pair<string, string> a("James", "Joy");
string name,last;
name = pair.first;
lase = pair.second;

// 给属性赋值
newone = make_pair(name, last);

2.5.3 priority_queue中的pair

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<bits/stdc++.h>
using namespace std;

int main() {
priority_queue<pair<int, int> > a;
pair<int,int> b(1,5);
pair<int,int> d(1,2);
pair<int,int> c(6,1);

a.push(d);
a.push(c);
a.push(b);

while (!a.empty()) {
cout<<a.top().first<<" "<<a.top().second<<endl;
a.pop();
}

return 0;
}

/*
比较第一个,若相同,则比较第2个
6 1
1 5
1 2
*/

2.5.4 sort()

1
2
3
4
5
6
7
8
9
// lambda表达式自定义排序,以下为降序排列
sort(vec.begin(),vec.end(),[](const int a,const int b){return a>b;});

// 将自定义排序方式放在sort()外
bool cmp(int a,int b){
return a>b;//从大到小排序
}

sort(vec.begin(),vec.end(),cmp);

一、滕王阁序

1.1 原文

​ 豫章故郡,洪都新府。星分翼轸(zhěn),地接衡庐。襟(jīn)三江而带五湖,控蛮荆而引瓯(ōu)越。物华天宝,龙光射牛斗之墟;人杰地灵,徐孺下陈蕃之榻。雄州雾列,俊采星驰。台隍(huáng)枕夷夏之交,宾主尽东南之美。都督阎公之雅望,棨(qǐ)戟(jǐ)遥临;宇文新州之懿(yì)范,襜(chān)帷(wéi)暂驻。十旬休假,胜友如云;千里逢迎,高朋满座。腾蛟起凤,孟学士之词宗;紫电青霜,王将军之武库。家君作宰,路出名区;童子何知,躬逢胜饯(jiàn)。(豫章故郡 一作:南昌故郡;青霜 一作:清霜)

​ 时维九月,序属三秋。潦(lǎo)水尽而寒潭清,烟光凝而暮山紫。俨(yǎn)骖騑(cān fēi)于上路,访风景于崇阿。临帝子之长洲,得天人之旧馆。层峦耸翠,上出重霄;飞阁流丹,下临无地。鹤汀凫渚,穷岛屿之萦回;桂殿兰宫,即冈峦(luán)之体势。(天人 一作:仙人;层峦 一作:层台;即冈 一作:列冈;飞阁流丹 一作:飞阁翔丹)

​ 披绣闼(tà),俯雕甍(méng),山原旷其盈视,川泽纡其骇瞩。闾阎扑地,钟鸣鼎食之家;舸舰弥津,青雀黄龙之舳(zhú)。云销雨霁,彩彻区明。落霞与孤鹜齐飞,秋水共长天一色。渔舟唱晚,响穷彭蠡(péng lǐ)之滨,雁阵惊寒,声断衡阳之浦。(轴 通:舳;迷津 一作:弥津;云销雨霁,彩彻区明 一作:虹销雨霁,彩彻云衢)

​ 遥襟甫(fǔ)畅,逸(yì)兴遄(chuán)飞。爽籁(lài)发而清风生,纤歌凝而白云遏(è)。睢(suī )园绿竹,气凌彭泽之樽;邺(yè)水朱华,光照临川之笔。四美具,二难并。穷睇眄(dì miǎn)于中天,极娱游于暇日。天高地迥,觉宇宙之无穷;兴尽悲来,识盈虚之有数。望长安于日下,目吴会于云间。地势极而南溟(míng)深,天柱高而北辰远。关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。怀帝阍(hūn)而不见,奉宣室以何年?(遥襟甫畅 一作:遥吟俯畅)

​ 嗟乎!时运不齐,命途多舛。冯唐易老,李广难封。屈贾谊(yì)于长沙,非无圣主;窜梁鸿于海曲,岂乏明时?所赖君子见机,达人知命。老当益壮,宁移白首之心?穷且益坚,不坠青云之志。酌贪泉而觉爽,处涸辙(hé zhé)以犹欢。北海虽赊,扶摇可接;东隅已逝,桑榆(sāng yú)非晚。孟尝高洁,空余报国之情;阮籍猖狂,岂效穷途之哭!(见机 一作:安贫;以犹欢 一作:而相欢)

  勃,三尺微命,一介书生。无路请缨,等终军之弱冠;有怀投笔,慕宗悫(què)之长风。舍簪笏(zān hù)于百龄,奉晨昏于万里。非谢家之宝树,接孟氏之芳邻。他日趋庭,叨陪鲤对;今兹捧袂(pěng mèi),喜托龙门。杨意不逢,抚凌云而自惜;钟期既遇,奏流水以何惭?

  呜呼!胜地不常,盛筵(yán)难再;兰亭已矣,梓泽(zǐ zé)丘墟。临别赠言,幸承恩于伟饯;登高作赋,是所望于群公。敢竭鄙怀,恭疏短引;一言均赋,四韵俱成。请洒潘江,各倾陆海云尔。
  滕王高阁临江渚,佩玉鸣鸾(luán)罢歌舞。
  画栋朝飞南浦云,珠帘暮卷西山雨。
  闲云潭影日悠悠,物换星移几度秋。
  阁中帝子今何在?槛(jiàn)外长江空自流。

2.2 解字

一、反射的概念

Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。)语言的一个关键性质。

二、反射的作用

我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!

三、反射的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.ys.reflex;
public class Person {
//私有属性
private String name = "Tom";
//公有属性
public int age = 18;
//构造方法
public Person() {
}
//私有方法
private void say(){
System.out.println("private say()...");
}
//公有方法
public void work(){
System.out.println("public work()...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
//1、通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object
// 类型的对象,而我不知道你具体是什么类,用这种方法
  Person p1 = new Person();
  Class c1 = p1.getClass();

//2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高
// 这说明任何一个类都有一个隐含的静态成员变量 class
  Class c2 = Person.class;

//3、通过 Class 对象的 forName() 静态方法来获取,用的最多,
// 但可能抛出 ClassNotFoundException 异常
  Class c3 = Class.forName("com.ys.reflex.Person");

3.1 Class类的方法

  • getName():获得类的完整名字。
  • getFields():获得类的public类型的属性。
  • getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
  • getMethods():获得类的public类型的方法。
  • getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
  • getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
  • getConstructors():获得类的public类型的构造方法。
  • getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
  • newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//获得类完整的名字
String className = c2.getName();
System.out.println(className);//输出com.ys.reflex.Person

//获得类的public类型的属性。
Field[] fields = c2.getFields();
for(Field field : fields){
System.out.println(field.getName());//age
}

//获得类的所有属性。包括私有的
Field [] allFields = c2.getDeclaredFields();
for(Field field : allFields){
System.out.println(field.getName());//name age
}

//获得类的public类型的方法。这里包括 Object 类的一些方法
Method [] methods = c2.getMethods();
for(Method method : methods){
System.out.println(method.getName());//work waid equls toString hashCode等
}

//获得类的所有方法。
Method [] allMethods = c2.getDeclaredMethods();
for(Method method : allMethods){
System.out.println(method.getName());//work say
}

//获得指定的属性
Field f1 = c2.getField("age");
System.out.println(f1);
//获得指定的私有属性
Field f2 = c2.getDeclaredField("name");
//启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消
f2.setAccessible(true);
System.out.println(f2);

//创建这个类的一个对象
Object p2 = c2.newInstance();
//将 p2 对象的 f2 属性赋值为 Bob,f2 属性即为 私有属性 name
f2.set(p2,"Bob");
//使用反射机制可以打破封装性,导致了java对象的属性不安全。
System.out.println(f2.get(p2)); //Bob

//获取构造方法
Constructor [] constructors = c2.getConstructors();
for(Constructor constructor : constructors){
System.out.println(constructor.toString());//public com.ys.reflex.Person()
}

3.1 获取父类属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Parent {
public String publicField = "parent_publicField";
protected String protectField = "parent_protectField";
String defaultField = "parent_defaultField";
private String privateField = "parent_privateField";

}

public class Son extends Parent {
}

public class ReflectionTest {

@Test
public void testGetParentField() throws Exception{
Class c1 = Class.forName("com.ys.model.Son");
//获取父类私有属性值
System.out.println(getFieldValue(c1.newInstance(),"privateField"));
}

public static Field getDeclaredField(Object obj,String fieldName) {
Field field = null;
Class c = obj.getClass();
for(; c != Object.class ; c = c.getSuperclass()){
try {
field = c.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
}catch (Exception e){
//这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。
//如果这里的异常打印或者往外抛,则就不会执行c = c.getSuperclass(),最后就不会进入到父类中了
}
}
return null;
}
public static Object getFieldValue(Object object,String fieldName) throws Exception{
Field field = getDeclaredField(object,fieldName);

return field.get(object);
}
}

通过执行上述代码,我们获得了父类的私有属性值,这里要注意的是直接通过反射获取子类的对象是不能得到父类的属性值的,必须根据反射获得的子类 Class 对象在调用 getSuperclass() 方法获取父类对象,然后在通过父类对象去获取父类的属性值。

灵活使用反射能让我们代码更加灵活,这里比如JDBC原生代码注册驱动,hibernate 的实体类,Spring 的 AOP等等都有反射的实现。但是凡事都有两面性,反射也会消耗系统的性能,增加复杂性等,合理使用才是真!

一、动词

1.1 结构

  • 体言:包括名词、代词、数词(一般指没有变形的词语)
  • 用言:用来叙述事物的动作,作用,存在,性质,状态等,可以作为独立的谓语,词尾是有活用的。(即有变形的词)

  • 动词构成:动词=词干+词尾

    • 洗(あら)う 書(か)く 泳(およ)ぐ 話(はな)す 立(た)つ 死(し)ぬ 叫(さけ)ぶ 読(よ)む 終(お)わる 始(はじ)まる
  • 动词的分类:五段动词、一段动词、サ变动词、カ变动词
  • 动词活用:用言词尾的变化,以便连接后项补助成分。动词、形容词、形容动词都可发生相应的应用。因为在日语中动词本身是无法表达“正在”、“不在”这种对动作状态的描述,因此需要在动词上添加补助的成分来表示否定或者其他的状态

    •  + ない ===> 書ない(不写)
    •  + ている ===> 書ている(正在写)
  • 活用形:用言词尾变化之后的形式,活用形没有实际含义,需配合后项补助成分使用,比如:書==>書

  • 活用形的类型:未然形、连体形、连用形、假定形、终止形等
  • 动词具体含义:活用形+补助成分

1.2 分类

  • 未然形:用于动词的否定的情况。“动词未然形+否定助动词ない、ぬ(ん)”构成了动词的否定形。
  • 连用形:动词在表示主体的行为、动作或状态时,经常是只用一个词就说不清楚,必须连接其他用言和助词、助动词等来达到充分说明主体的目的。连用形主要用于这种情况。
  • 仮定形:表示假定条件和构成惯用形。除个别词以外,不能单独使用,后接接续助词ば
基本形 未然形+ない 连用形+ます 仮定形+ば/れば
五段动词 書く 書か+ない 書き+ます 書け+ば
読む 読ま+ない 読み+ます 読め+ば
一段动词 食(た)べる 食べ+ない 食べ+ます 食べ+れば
起(お)きる 起き+ない 起き+ます 起き+れば
サ变动词 する し+ない し+ます す+れば
勉強(べんきょ)する 勉強し+ない 勉強し+ます 勉強す+れば
カ变动词 来(く)る 来(こ)+ない 来(き)+ます 来(く)+れば

1.3 判断

  • カ变动词:来(く)る,只有这一个
  • サ变动词:する及以する为词尾的动词
    • 如:勉強する 買い物(かいもの)する 出席(しゅっせき)
  • 一段动词:词语最后假名为;る前假名为い、え段假名
    • 如:見(み)せる 始(はじ)める 起(お)きる 借(か)りる
    • 26个特殊的“一段动词”:帰(かえ)る 嘲(あざけ)る 焦(あせ)る 入(はい)る;虽然这些满足一段动词的格式,但是这些都是按照五段动词来使用的
  • 五段动词:
    • 词语最后假名为非る的う段假名:洗(あら)う 書(か)く 泳(およ)ぐ 話(はな)す 立(た)つ 死(し)ぬ 叫(さけ)ぶ 読(よ)む
    • 词尾为る,る前假名为あ、う、お段假名:始まる 終(お)わる 売(う)る 怒(おこる)る
    • 26个特殊的“一段动词”:帰(かえ)る 嘲(あざけ)る 焦(あせ)る 入(はい)る;

二、ます的使用方式

2.1 概述

动词基本形作谓语,表示经常性、习惯性、将来性、真理性

  • 私たちは 毎日学校(がっこう)へ 行く(我每天都去学校)

  • 明日(あした)、王(わん)さんは六時(ろくじ)に 起(お)きる(小王明天六点起床)

  • 地球(ちきゅう)は 太陽(たいよ)を 回(まわ)る(地球围绕太阳旋转)

ます:敬体助动词,表示对他人的尊重。接于动词连接形之后

  • 私たちは 毎日学校(がっこう)へ 行きます【行く==>行きます】(我每天都去学校)

  • 明日(あした)、王(わん)さんは六時(ろくじ)に 起(お)きます【起きる==>起きます】(小王明天六点起床)

  • 地球(ちきゅう)は 太陽(たいよ)を 回(まわ)ります【回る==>回ります】(地球围绕太阳旋转)

2.2 动词连用形的变化方法

  • 五段动词:将词尾う段假名变为其所在行上い段假名
    • 洗う==>洗い 話(はな)す==>話(はな)し 立(た)つ==>立(た)ち 終(お)わる==>終(お)わり
  • 一段动词:将词尾去掉
    • 始(はじ)める==>始(はじ)め 見(み)せる==>見(み)せ 落(お)ちる==>落(お)ち
  • サ变动词:する==>し
    • 食事(しょくじ)する==>食事(しょくじ)し
  • カ变动词:来(く)る==>来(き)

三、叙述句

叙述句(动词谓语句)一般用来表示经常性、将来性、真理性的动作

  • Aは ~ます:A 要做什么
  • Aは ~ません:A 不要做什么

四、自动词/他动词

  • 自动词:不需要宾语便可以完整表达含义,多用来表示事物的状态或变化

    • 动作性自动词
    • 状态性自动词
  • 他动词:需要宾语帮助完成含义的具体表达

    • を:格助词,对他动词的宾语进行提示
    • 动宾关系:宾语+を+他动词