JavaWeb基础小知识
前端
JSON
JSON在前端
JSON字符串转JS对象:
JS对象 = JSON.parse(JSON对象);
JS对象转JSON字符串:
JSON对象 = JSON.stringify(JS对象);
BOM对象
浏览器对象模型
封装了以下对象:
Window:浏览器窗口对象
Navigator: 浏览器对象
Screen: 屏幕对象
History: 浏览器记录对象
Location: 地址栏对象
Window
获取
直接使用window调取即可,其中如window.alert(“aa”);中的window.是可以省略的。
属性
history
location
navigator
是对其他对象的引用
方法
alert():显示消息和确认信息
confirm():显示消息和确认与取消信息
setInterval():按照指定的周期调用函数或表达式
setTimeout():指定的时间后调用函数或表达式
DOM
文档对象模型
Core DOM 封装了以下对象:
Document:整个文档对象
Element:元素对象,每一个标签
Attribute: 属性对象,标签的属性
Text:文本对象,标签的文本
Comment:注释对象
除此之外还有XML DOM是用于XML文档的标准模型、HTML DOM是用于HTML文档的标准模型包含如Image、Button对象。
JS通过DOM对HTML进行操作:1、改变HTML元素的内容 2、改变元素的样式 3、对HTML DOM事件做出响应 4、添加删除HTML元素
获取元素对象
在DOM中元素对象是通过Document对象获取的,而Document对象是通过Window对象获取的
var h1 = document.getElementsById('h1');//ID
var h1 = document.getElementsByTagName('h1');//标签
var h1 = document.getElementsByName('h1');//名称
var h1 = document.getElementsByClassName('h1');//类名
事件
事件绑定:
通过HTML标签中的属性进行绑定。
<input type = "button" onclick="on()" value="按钮1">
<script>
function on(){
alert("被点了");
}
</script>
通过DOM元素进行绑定
<input type = "button" id="byd" value="按钮2">
<script>
document.getElementById('byd').onclick()=function(){
alert("被点了");
}
</script>
VUE
基于MVVM(Model-View-ViewModel)的数据双向绑定模型框架。
MVVM的核心思想是通过ViewModel将Model和View的数据变化双向绑定起来,无论哪一个发生了变化对应的另一个的数据将同步变化。
普通步骤:
1、新建HTML页面,引入Vue.js文件
<script src="js/vue.js"></script>
2、在js代码区域创建vue的核心对象,定义数据类型
<script>
new Vue({
//挂载点,vue的接管区域
el: "#app",
data:{
message:"Hello Vue!",
url:""
}
})
</script>
3、编写视图
<div id="app">
<input type="text" v-model="message">
{{ message }}
</div>
插值表达式
形式:{{ 表达式 }}
其内容可以是:变量、三元运算符、函数调用、算数运算
常用指令
v-bind :
为HTML标签绑定属性值,如动态绑定href、css等,绑定的数据必须在数据模型中声明
<a v-bind:href="url"></a>
//也可以简写成以下形式:
<a :href="url"></a>
v-model :
在表单元素上创建双向数据绑定,绑定的数据必须在数据模型中声明
<input type="text" v-model="url">
v-on :
为HTML标签绑定事件
<input type="button" value="按钮" v-on:click="handle()">
//也可以简写成以下形式:
<input type="button" value="按钮" @click="handle()">
同时必须在vue中声明对应的方法
<script>
new Vue({
el: "#app",
data:{
//...
},
methods:{
handle:function(){
alert('被点击了');
}
}
})
</script>
v-if :
v-else-if :
v-else :
条件渲染,判定为true时加载到DOM中
v-show :
影响对应元素的display属性,条件为true时才展示。
v-for :
列表渲染,遍历容器的元素或者对象的属性。
<div v-for ="addr in addrs">{{ addr }}</div>
<div v-for ="(addr,index) in addrs">{{ index+1 }}:{{addr}}</div>
生命周期
beforeCreate、created、beforeMout、mouted、beforeUpdate、updated、beforeDestroy、destroyed
创建过程主要进行data、method的初始化、绑定属性
挂载过程主要对el目标进行挂载,完成模板对真正DOM的渲染
更新过程主要重新构建虚拟dom与上一次的虚拟dom树利用diff算法进行对比之后重新渲染
示例:
<script>
new Vue({
el: "#app",
data:{
//...
},
mounted(){
console.log("Vue挂载完毕,请求获取数据")
},
methods:{
},
})
</script>
Ajax
作用:
1、数据交换:给服务器发送请求,并获取服务器响应的数据
2、异步交互:可以不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术。
原生Ajax(了解)
//1、创建XMLHttpRequest对象,即创建一个异步调用对象.
var xmlHttpRequest = new XMLHttpRequest();
//2、创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
//XMLHttpRequest.open(method,URL,flag,name,password);
xmlHttpRequest.open("get","http://www.",true);
//3、设置响应HTTP请求状态变化的函数.
//设置当XMLHttpRequest对象状态改变时调用的函数,注意函数名后面不要添加小括号
xmlHttpRequest.onreadystatechange = function(){
//判断XMLHttpRequest对象的readyState属性值是否为4,如果为4表示异步调用完成
if(xmlHttpRequest.readyState==4){
if(xmlHttpRequest.status == 200 || xmlHttpRequest.status == 0){//设置获取数据的语句
document.write(xmlHttpRequest.responseText);//将返回结果以字符串形式输出
//docunment.write(xmlHttpRequest.responseXML);//或者将返回结果以XML形式输出
}
}
}
//4、发送HTTP请求.
XMLHttpRequest.send(data);
//5、获取异步调用返回的数据.
//即在其他地方调用上述函数
//6、使用JavaScript和DOM实现局部刷新.
Axios
对原生Ajax进行封装,简化书写、快捷开发。
1、引入Axios的js文件
<script src="js/axios-0.18.0.js"><script>
2、使用Axios发送请求,并获取响应结果
axios({
method:"get",//请求方法
url:"http://www.",
}).then((result)=>{//成功响应的回调函数
console.log(result.data);
});
axios({
method:"post",
url:"http://www.",
data:"id=1",
}).then((result)=>{
console.log(result.data);
});
前端工程化
把前端开发使用的工具、技术、流程、经验进行标准化、流程化。
Mock
用于模拟后端返回的数据使得前端可以自己调试。常用的有YAPI
环境
Vue-cli用于Vue的项目模板快速生成,他提供了以下功能:
1、统一的目录结构 2、本地调试 3、热部署 4、单元测试 5、集成打包上线
依赖环境:NodeJS
Vue项目
目录结构
node_modules :项目的依赖
public :项目的静态文件
src :源代码
asset :静态资源
components :可重用的组件
router :路由配置
views :视图组件(页面)
App.vue :入口页面(根组件)
main.js :入口js文件
package.json :模块基本信息,项目开发需要的模块,版本信息
vue.config.js :保存vue配置的文件,如代理、端口的配置等
指令
npm run serve 运行
配置
在vue.config.js 文件中进行配置
配置端口号:
devServer:{
port: 7000,
}
工作流程解析
main.js是入口js文件
//导入组件
import Vue from 'vue'
import App from './App.vue'
import router from './router'
//设置 Vue 的全局配置项,设置为 false,以阻止在生产环境中显示生产提示。
Vue.config.productionTip = false
new Vue({
//传入配置对象
//实际上是router: router,是将Vue的router属性赋值为router因为一致所以可以省略
router,
//使用 render 函数渲染 App 组件即创建虚拟DOM
render: h => h(App),
}).$mount('#app')//挂载到id为app的元素上,此处的app为public下index.html文件中的元素
App.vue入口页面(根组件)
模板部分,由它生成HTML代码。定义原生HTML
<template>
此处的app和其他文件无关,用于本文件中的元素定位
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
控制模板中的数据来源和行为
<script>
import HelloWorld from './components/HelloWorld.vue'
当前组件导出成模块
export default {
name: 'App',
components: {
HelloWorld
},
data(){
return {
message: "Hello Vue",
},
},
methods:{
}
}
</script>
css样式
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Vue中使用axios
安装:
npm install axios
导入
import axios from ‘axios’;
在挂载完毕的钩子方法中加载信息
mounted(){
axios.get("http://..").then((result)=>{
this.tableData = result.data.data;
})
}
Vue路由
点击菜单栏改变URL,前端路由:URL中的hash(#号)与组件之间的对应关系。
组成:
VueRouter:路由器类,更具路由请求在路由视图中动态渲染选中的组件
<router-link>: 请求连接组件,浏览器会解析成<a>,即点击后会让视图组件跳转到对应的组件。
<router-view>: 同台视图组件,用来渲染展示与路由路径对应的组件。即页面的展示组件,跳转后的组件将会加载到这个组件上。
使用:
安装:npm install vue-router@版本号
导入: 在main.js中导入
import router from './router'
new Vue({
el: '#app',
router,
render: h => h(App)
})
定义路由:
在router下的index.js中定义相关路由信息。
{
path: '',
component: Layout,
redirect: 'index',
children: [
{
path: 'index',
component: () => import('@/views/index'),
name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true }
}
]
},
在使用router后Vue项目的加载过程会发生变化,之前是通过main.js进入app.vue。使用之后将会进一步在app.vue中根据默认的 ’ '路由跳转对应的组件,这个组件需要你去配置。
打包部署
使用相关命令打包。
将打包好 的dist目录下的文件复制到nginx安装目录的html目录下。双击启动nginx即可。nginx的核心配置文件在config目录下的nginx.conf中。
Element UI
1、安装(在当前项目中)
npm install element-ui@2.15.3
2、引入组件库
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
3、访问官网,复制组件代码并调整
在官网相关组件的页面底端有相关属性、事件等的说明。
4、使用组件标签
需要查找文档学习的部分组件
container 布局容器
对话框
表单
Maven
分模块开发
1、创建对应的各个模块
2、开发对应各个模块
3、模块间相互使用时,引入依赖
4、通过Maven安装模块到本地仓库(install 命令,最好是聚合实现)
依赖
依赖传递的时候可以加上optional选项,此时将隐藏本项目使用的该依赖,不具备依赖传递。(不给别人)
<groupId>com.itheima</groupId>
<artifactId>maven_e3_pojo</artifactId>
<version>1.0-SNAPSHOT</version>
<!--可选依赖是隐藏当前工程所依赖的资源,隐藏后对应资源将不具有依界-->
<optional>true</optional>
可视化依赖:在pom.xml文件中右键选中Diagrams选择show dependencies
排除依赖:在对应的dependency中
<exclusions>
<exclusion>
<groupId></groupId>
<artifactId></artifactId>
</exclusion>
</exclusions>
依赖范围
使用scope标签设置作用范围
默认:compile(主程序、测试、运行)
test:测试
provided:主程序、测试
runtime:测试、运行
继承
概念 : 继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承。
操作:
1、配置父工程,说明所有子模块通用的依赖,设置打包方式是pom包,对于springboot项目往往还需要继承spring-boot-starter-parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/>
</parent>
<groupId>com.itheima</groupId>
<artifactId>tlias-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
2、配置子模块继承自父模块
<!--配置当前工程继承自parent 工程-->
<parent>
<groupId>com.itheima</groupId>
<artifactId>maven_e1_parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../ maven_01_parent/pom.xml</relativePath>
</parent>
3、在父工程中配置子工程中可选的依赖,进行统一的依赖版本管理
<!--定义依赖管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
4、在子工程中引用对应的依赖,此时无需加上版本信息。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
聚合
聚合:将多个模块组织成一个整体,同时进行项目构建的过程称为聚合
聚合工程 : 通常是一个不具有业务功能的“空”工程(有且仅有一个pom文件,通常是父工程)
作用︰使用聚合工程可以将多个工程编组,通过对聚合工程进行构建,实现对所包含的模块进行同步构建
当工程中某个模块发生更新(变更)时,必须保障工程中与已更新模块关联的模块同步更新,此时可以使用聚合工程来解决批量
模块同步构建的问题
操作:
1、设置管理的模块名称
<!--设置管理的模块名称-->
<modules>
<module>../ maven_e2_ssm</module>
<module>../ maven_e3_pojo</module>
<module>../maven_e4_dao</module>
</modules>
属性
在pom中定义属性
<!--定义属性-->
<properties>
<spring.version>5.2.10.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<mybatis-spring.version>1.3.0</mybatis-spring.version>
<jdbc.ur1>jdbc:mysql://127.8.0.1:3306/ssm_db</jdbc.ur1>
</properties>
在pom中引用属性
<version>${junit.version}</version>
配置文件加载属性
扩大Maven的控制范围,在build中说明资源配置路径
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
此时在resources文件夹下的文件就能使用${}调用pom中的属性了
打war包时无需web.xml
添加插件,使他在无web.xml时不报错
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<failonMissingwebxml>falsek/failonMissingwebxml>
</configuration>
</plugin>
多环境开发
1、在pom中配置不同环境的属性信息
<!--配置多环境-->
<profiles>
<!--开发环境-->
<profile>
<id>env_dep</id>
<properties>
<jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url>
</properties>
<!--没定是否为默认启动环境-->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!--生产环境-->
<profile>
<id>env_pro</id>
<properties>
<jdbc.ur1>jdbc :mysql://127.2.2.2:3306/ssm_db</jdbc.ur1>
</properties>
</profile>
<!--测试环境-->
<profile>
<id>env_test</id>
<properties>
<jdbc.ur1>jdbc :mysql://127.3.3.3:3306/ssm_db</jdbc.ur1>
</properties>
</profile>
2、在执行Maven时的命令时使用多环境
mvn 指令 -P 环境定义 id
范例:
mvn install -P pro_env
私服
团队内部开发时的相互依赖关系解决。
私服简介:私服是一台独立的服务器,用于解决团队内部的资源共享与资源同步问题。
使用私服的找依赖的过程:本地Maven仓库->私服->中央仓库。
Nexus:Sonatype公司的一款maven私服产品。
使用
启动:在对应文件夹下启动cmd 执行命令nexus.exe /run nexus
在浏览器打开 8081端口,其他如下
启动服务器(命令行启动)
nexus.exe /run nexus
访问服务器(默认端口:8081)
http://localhost:8081
修改基础配置信息
安装路径下etc目录中nexus-default.properties文件保存有nexus基础配置信息,例如默认访问端口修改服务器运行配置信息
安装路径下bin目录中nexus .vmoptions文件保存有nexus服务器启动对应的配置信息,例如默认占用内存空间
操作流程
上传时给出放到什么仓库中,下载时直接向仓库组调用。
私服仓库分类
仓库类别 | 英文名称 | 功能 | 关联操作 |
---|---|---|---|
宿主仓库 | hosted | 保存自主研发+第三方资源 | 上传 |
代理仓库 | proxy | 代理连接中央仓库 | 下载 |
仓库组 | group | 为仓库编组简化下载操作 | 下载 |
配置私服访问
1、配置Maven文件夹下conf中的setting.xml文件
2、配置对私服的访问权限,id就是私服中仓库的名称所有用到的仓库都需要配置权限
<server>
<id>私服中的服务器id名称</id>
<username>repofser</username>
<password>repopwd</password>
</server>
</servers>
3、创建私服中的仓库
4、将创建的仓库放入仓库组进行管理,找到类型为group的仓库
5、配置私服的访问路径;
<mirror>
<id>mirrorid</id>
<mirrorof>LepositoryId</mirrorOf>
<url>http://my.repository.com/repo/path</ur1>
</mirror>
Id代表仓库组的id即group的名称,mirrorOf表示什么样的来自私服,通常*指代全部,url则是将group中的url粘贴即可。
私服的上传下载
1、在pom中配置保存在私服中的具体位置
<!--配置当前工程保存在私服中的具体位置-->
<distributionManagement>
<repository>
<id>itheima-release</id>
<url>http://localhost:8081/repository/itheima-release/</url>
</repository>
<snapshotRepository>
<id>itheima-snapshot</id>
<url>http://localhost:8081/repository/itheima-snapshot/</url>
</snapshotRepository>
</distributionManagement>
2、执行Maven命令 deploy发布,此时私服也会下载用到的依赖
3、此时工程版本号为snapshot则会上传到snapshot类型的仓库,release则上传到release仓库
中央仓库调用阿里镜像
点击Maven-central,修改Proxy - Remote storage
SpringBoot
Bean 管理
获取Bean
默认情况下,Spring项目启动时,会把bean都创建好放在IOC容器中,如果想要主动获取这些bean,可以通过如下方式:
先注入IOC容器对象
@Autowired
private ApplicationContext applicationcontext; //IOC容器对象
根据name获取bean:
Object getBean(String name)
DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
根据类型获取bean:
<T>TgetBean (Class<T>requiredType)
DeptController bean2 = applicationContext.getBean(DeptController.class);
根据name获取bean(带类型转换):
<T> T getBean(String name,Class<T> requiredType)
DeptController bean3 = applicationContext.getBean("deptcontroller",DeptController.class);
Bean作用域与初始化
Spring支持五种作用域,后三种在web环境才生效:
singleton:容器内同名称的 bean只有一个实例(单例)(默认)
prototype:每次使用该 bean 时会创建新的实例(非单例)
可以通过@Scope注解来进行配置作用域,配置在整个文件上面:
可以通过@Lazy注解来延迟初始化,延迟到第一次使用的时候进行初始化
第三方Bean配置
如果要管理的bean对象来自于第三方〈不是自定义的),是无法用@Component及衍生注解声明bean的,就需要用到@Bean注解。
在启动类或者配置类当中使用@Bean注解返回交给IOC容器。Bean容器的名称默认是方法名。
@Configuration
public class CommonConfi {
@Bean//将方法返回值交给IOC容器管理,成为IOC容器的bean对象
public SAXReader saxReader(){
return new SAXReader();
}
}
注意事项
通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名。
如果第三方bean需要依赖其它bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。
起步依赖
当前项目继承自Spring-boot-starter-parent而Spring-boot-starter-parent继承自Spring-boot-dependencies
Spring-boot-dependencies 中配置了大量的依赖,并配置了对应的版本号,此时写对应依赖时不需要加版本,自动继承。
当前项目只需要配置各种起步依赖即可,起步依赖中继承了大量的依赖,通过依赖传递就能有大量依赖。
自动配置
SpringBoot的自动配置就是当spring容器启动后,一些配置类、bean对象就自动存入到了IOC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。
原理:
启动类能自动扫描本包,若要扫描到第三方依赖。
1、使用@ComponentScan({})组件扫描到本包和对应的包(不推荐)。
2、使用@Import导入使用@Import导入的类会被Spring加载到IOC容器中,导入形式主要有以下几种:
1导入普通类:
2导入配置类:该配置类当中的所有Bean对象都会导入到IOC容器当中。@Import({})
3导入ImportSelector接口实现类:将哪些类交给IOC,给出全类名。@Import({MyImportSelector.class})其中要实现ImportSelector接口的
public class MyImportSelector implements Importselector {
public string[] selectImports (AnnotationMetadata importingclassMetadata){
return new string[] { "com.example.HeaderConfig"};
4、使用第三方依赖提供的@Enable****注解(最推荐),在配置类当中添加该种注解,具体可以先引入相关包后查看报错提示信息,根据信息添加注解。
源码跟踪(启动类)
启动类封装了
1、配置类注解。
2、封装了使用importSelector的注解,它的参数是META-INF/spring.factories以及另一个文件(org.springframework…import)下的配置文件的全类名。值得注意的是不是所有都生效的,查看下节@Conditional
3、组件扫描注解。
@Conditional 条件声明Bean对象
作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring lOC容器中。
位置:方法、类
@Conditional本身是一个父注解,派生出大量的子注解:
@ConditionalOnClass:判断环境中是否有对应字节码文件,才注册bean到IOC容器。
@ConditionalOnClass(name = "io.jsonwebtoken.Jwts")//环境中存在指定的这个类,才会将该bean加入Ioc容器中
@ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器
心
@ConditionalonMissingBean//不存代该类型的bean,才会将该bean加入IoC容器中--指定类型(value属性)或名称(name属性)
@ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。
@ConditionalOnProperty(name = "name",havingValue = "itheima")//配置文件中存在指定的属性与值,才会将bean加入Ioc容器中
!!(重要)案例:自定义starter
在实际开发中,经常会定义一些公共组件,提供给各个项目团队使用。而在SpringBoot的项目中,一般会将这些公共组件封装为SpringBoot 的starter。
需求:自定义aliyun-oss-spring-boot-starter,完成阿里云OSS操作工具类Aliyunossutils的自动配置。
目标:引入起步依赖引入之后,要想使用阿里云OSS,注入AliyunosSUtils直接使用即可。
步骤:
1、创建aliyun-oss-spring-boot-starter模块
2、创建aliyun-oss-spring-boot-autoconfigure模块,在starter中引入该模块
3、在aliyun-oss-spring-boot-autoconfigure模块中的定义自动配置功能,并定义自动配置文件META-INF/spring/xox.imports
具体操作细节很重要,详细见相关视频。
一、创建新模块,starter、autoconfigure
二、starter除了pom和.iml文件的其他文件都不需要。autoconfigure中无需启动类、测试类等,并在starter的pom文件中引入autoconfigure
三、将阿里云OSS操作工具类需要的依赖放入autoconfigure模块中。
四、将阿里云OSS操作工具类以及它的引用类、配置类放入autoconfigure模块。处理这些类以及相关报错信息:
1、对于需要引入依赖的引入
2、生成getter、setter方法,或引入
3、删除@component注解、删除原有的@Autowired注入
五、在autoconfigure中定义自动配置类,声明为配置类、加载配置文件、根据配置文件生成对象、将当前对象交给IOC容器管理。
@Configuration
@EnableConfigurationProperties(AliOSSProperties.class)//为什么使用这个注解可以参考在@configurationProperties注解报错的提示上的提示信息
public class AliOSSAutoConfiguration{
@Bean
public AliOSSUtils aliossutils(Ali0SSProperties aliosSProperties){
AlioSsutils aliossutils = new AliOSSUtils();
aliossUtils.setAliOSSProperties(aliOsSProperties);
return aliossUtils;
}
}
六、在resources下创建二级目录META-INF/spring。创建文件org.***.import(具体名称参考其他启动类的文件),并配置本模块下的自动配置类的全类名,比如com.aliyun. oss.Ali0SSAutoConfiguration
流程解析
在AutoConfiguration中定义了需要自动配置的Bean,在org.***.import这份固定的文件中配置了自动配置类的全类名,将来boot启动的时候会自动加载这个文件,根据文件内容将自动配置类加载到IOC容器当中。
使用
在其他项目中如果想要使用,则只需要在引入starter依赖后,直接注入工具类对象即可使用。值得注意的是使用本模块需要哪些配置信息需要查看**Properties类封装了哪些对象。
接收参数
原始方式
通过HttpServletRequest 对象获取
name = request.getParameter("name");
简单参数
参数名和形参变量名相同,自定义形参即可接收参数,类型会自动转化。
//http://.../simpleParam?name=Tom&age=10
@RequestMapping("/simpleParam")
public String simpleParam(String name,Integer age){
return "ok";
}
形参名称和请求参数名称不匹配,可以使用@RequestParam完成映射
//http://.../simpleParam?name=Tom&age=10
@RequestMapping("/simpleParam")
public String simpleParam(@RequestParam(name = "name") String userName, Integer age){
return "ok";
}
默认参数是必须传递的,若是可选参数那么应该添加注解@RequestParam(required = false)
实体参数
封装的实体对象必须有对应的参数同名的属性才能使用接收到对应的参数
//http://.../simplePojo?name=Tom&age=10
@RequestMapping("/simplePojo")
public String simplePojo(User user){
return "ok";
}
public class User{
String name;
Integer age;
}
对于嵌套的复杂参数可以用 . 访问嵌套POJO属性参数,若有报错则注意生成对应的getter与setter以及tostring。
//http://.../simplePojo?name=Tom&age=10&address.province=北京&address.city=朝阳
@RequestMapping("/simplePojo")
public String simplePojo(User user){
return "ok";
}
public class User{
String name;
Integer age;
Address address;
}
public class Address{
String province;
String city;
}
数组参数:
直接用数组变量名作为参数,写对应的各个元素的值即可
集合参数:
参数同数组,在编写业务类时注意以下格式,即添加@RequestParam。
//http://.../simplePojo?hobby=game&hobby=java
@RequestMapping("/listParam")
public String listParam(@RequestParam list<String> hobby){
sout(hobby);
return "ok";
}
日期参数
使用@DateTimeFormat注解完成日期参数格式转化
//http://.../dataTime?updateTime=game&hobby=java
@RequestMapping("/dateTime")
public String dateTime(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDate updateTime){
sout(updateTime);
return "ok";
}
Json 参数
注意Json的参数应该在请求体当中配置,添加注解@RequestBody说明是请求体中的参数.
JSON数据键名和形参对象属性名相同,定义POJO类型形参接收参数
@RequestMapping("/listParamForJson")
public String listParamForJson(@RequestBody User user){
sout(user);
return "ok";
}
public class User{
String name;
Integer age;
Address address;
}
public class Address{
String province;
String city;
}
路径参数
通过请求URL直接传递参数,使用{…}来标识该路径参数,需要使用@PathVariable获取路径参数
@RequestMapping("/path/{id}/{name}")
public String pathParam(@PathVariable Integer id, @PathVariable String name){
sout(id,name);
return "ok";
}
响应数据
在Controller方法/类上加上@ResponseBody,他的主要作用是将方法返回值直接响应,如果返回值类型是实体对象/集合,将会转化为JSON格式。
如果使用REST风格那么只需要加上@RestController,他等于@Controller+@ResponseBody。
统一响应结果
正常的返回类型过多,为了方便前后端沟通,封装成统一的响应结果较好。一种封装如下:
public class Result{
private Integer code;
private String msg;
private Object data;
}
//封装sucess方法
//其他code对应的方法
单元测试
对于测试类加上@SpringBootTest注解声明为单元测试。
对于测试方法加上@Test注解
对于日志输出可以在文件前加上@Xsl4j注解,然后直接调用log如log.info(“输出信息”);
配置文件
常见三种方式,配置文件生效循序:properties>yml>yaml
Java系统属性:-D** = **
命令行参数 :–** = **
这两者可以在 Run configuration中配置VM option和Program argument。
打包后 java -Dserver.port = 9000 -jar *.jar --server.port = 1010
生效顺序是 命令行参数>Java系统属性>配置文件
properties
1、application.properties 文件
直接写上server.port=80
2、新建文件application.yml
写上
Server:
Port: 80
3、新建文件application.yaml
写上
Server:
Port: 80
自动提示
将新建配置文件加入Spring管理
在项目的Facet中选择对应模块的Configuration Files并选中+号添加新的文件
Yaml格式
enterprise:
name: it
age: 14
sunbject:
-Java
-前端
-大数据
读取yaml:
1、使用@Value读取,用 . 分级,用 [] 读取数组
@Value("${enterprise.sunbject[0]}")
String sunbject0;
2、使用环境变量+getProperty
@Autowired
private Enviroment enviroment;
enviroment.getProperty("lesson");
3、常用;定义domain与配置文件管理,并在使用时采用自动装配
@Component
@ConfigurationProperties(prefix = "enterprise")
public class Enterprise{
//对应的配置项名
}
@Autowired
Enterprise ente;
entr.对应的配置项名;
若有警告,则加入依赖
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor </artifactId>
分层解耦合
控制反转 IOC:对象创建控制权由程序本身转移到外部(容器),被称为IOC容器或者Spring容器。
依赖注入 DI:容器为应用程序提供运行时所依赖的资源。
Bean对象: IOC容器中创建、管理的对象称为Bean
IOC & DI 入门
1、Service与Dao层的实现类,交给IOC容器管理:为相关对象加上@Component 注解,将当前类交给IOC容器成为bean
2、为Service与Controller注入运行时依赖的对象:为相关对象加上@Autowired 注解,自动装配相关对象
3、运行测试
注意事项
其中@Component 下有三个子注释:@Controller,@Service,@Repository
在使用自动装配时自动装配的对象应当是对应服务的接口,这样若实现类发生变化也无需修改这个文件。
IOC详解
Bean的声明
@Component 下有三个子注释:@Controller,@Service,@Repository,通常不属于以上三类时才使用@Component
@Controller标注于控制器类上
@Service标注于业务类上
@Repository标注于数据访问类上。
在声明bean的时候可以通过value属性指定bean的名字,若没有指定默认为首字母小写。
Bean组件扫描
想要使得IOC容器添加对应的Bean还应该使用@ComponentScan()注解扫描,在SpringBoot中虽然没有显示配置但是实际上包含在了启动类的注解@SpringBootApplication 中,默认范围是启动了所在的包及其子包。
如果不在这个范围内需要使用注解显示配置对应的包,配置时是重载的,所以不要忘记了启动类所在的包也要加上。但是这个方法不推荐。
DI详解
@Autowired注解默认按照类型装配,若有多个实现类会报错,即是单例的。
解决方案:
1、使用@primary,在多个实现类中选择需要使用的那个加上@primary。
2、使用@Qualifier(value=" "),在自动装配时配合 @Qualifier(value=“实现类的名称”)来指定实现类
3、使用@Resource(name= “”),在自动装配时使用@Resource(name= “实现类”)代替@Autowired来指定实现类。
注意:
@Resource不是Spring框架下的注解,而是JDK提供的注解。
Mybatis
入门
持久层框架,简化开发。
在 java中编写,发送给数据库解析。
1、准备工作(创建SpringBoot工程、数据库表、实体类)
2、引入Mybatis相关依赖,配置Mybatis
3、编写sql语句(xml/注解)
建议字段名和属性名相同,可以自动实现对应关系。
@Mapper
public interface UserMapper{
@Select("select * from user")
public List<User> list();
}
上述文件示例中@Mapper在运行时会自动生成该接口的实现对象(代理对象),并将该对象交给IOC容器管理。
配置SQL提示
默认编写SQL语句是不识别的,可以选中语句后右键点击Show Context Actions -> Inject language or reference -> mysql
在IDEA中配置数据库连接识别表信息。
数据库连接池
数据库连接池是个容器,负责分配、管理数据库连接。他允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。释放空闲事件超过最大空闲时间的连接,避免没有释放连接引起的数据库连接漏洞。
优势:资源重用、提升系统响应速度、避免数据库连接遗漏
标准接口:DataSource
常见产品:c3p0、DBCP、Druid、Hikari(追光者);后两者常用,最后一个是SpringBoot的默认
切换Druid数据库连接池:引入相关依赖
<dependency>
<gropId>com.alibaba</gropId>
<aritifactId>druid-spring-boot-starter</aritifactId>
<version>1.2.8</version>
</dependency>
lombok
使用lombok类库简化开发,可以代替生成代码
@Data==@Getter+@Setter+@Tostring+@EqualsAndHashCode
@NoArgsConstructor无参构造器方法
@AllArgsConstuctor带有个参数的构造器方法
使用:
0、安装lombok插件
1、引入依赖
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
2、对应位置加上注解@Data以及其他需要的注解
3、在其他位置正常使用getter、setter方法。
基础操作
配置输出控制台
在配置中配置
mybatis.configration.log = org.apache.ibatis.logging.stdout.StdOutImpl
将会在log文件中输出预编译sql
删除
1、设计对应的Mapper接口,以及对应的sql语句。
@Delete("delete from emp where id = #{id}")
public void delete(Integer id);
值得注意的是一定要采用# {}将内容替换为?并生成预编译的sql。若是采用${}进行拼接sql可能会出现sql注入问题。
2、在使用它的service中自动装配对应的接口,通过该对象使用对应方法。
插入
@Insert("insert into emp(username,name) "+"value(#{username},#{name})")
public void insert(Emp emp)
其中值得注意的一点在于在sql语句中#{内容}的内容应当是封装对象Emp的对应的属性的名称。
主键返回
在数据添加成功后许需要获取插入数据库数据的主键。如添加套餐数据时还需要维护套餐菜品的关系表数据。
在默认情况下是不会返回主键值的,特别是对于形如自增的主键的数据库而言,我们在程序中只能得到null。
解决方法:
使用@Options注解
@Options(keyProperty = "id",useGeneratedKeys = true)
以上的注解将会将自动生成的主键值复制给emp对象的id属性。
更新
@Update("update emp set username = #{username},name = #{name}" +"where id = #{id}")
public int update(Emp emp);
查询
@Select("select * from emp where id = #{id}")
public Emp getById(Integer id);
@Select("select * from emp where name like '%${name}%' and age between #{begin} and #{end} order by update_time desc")
public list<Emp> getList(String name,LocalDate begin,LocalDate end);
值得注意的是在上述查询的多条件查询中,模糊查询使用的是$而不是#是因为#无法在引号中使用,因为不能生成预编译sql了,但是使用$可以拼接字符串,可以使用。但是可能出现sql注入,解决方法如下:
使用concat拼接字符串
@Select("select * from emp where name like concat("%",#{name},"%") and age between #{begin} and #{end} order by update_time desc")
数据封装
若实体类属性名和数据库表查询返回的字段名一致,会自动封装。
若不一致则有以下方案:
1、给字段起别名,让别名与实体类属性一致。
@Select("select dept_id deptId from emp where id = #{id}")
2、通过@Results注解手动映射封装。将不一致的进行封装。
@Results({
@Result(colum = "dept_id",property = "deptId"),@Result(colum = "",property = ""),@Result(colum = "",property = "")
})
@Select("select * from emp where id = #{id}")
public Emp getById(Integer id);
3、开启mybatis驼峰命名自动映射开关
配置配置文件如下:
mybatis.configuration.map-underscore-tocamel-case = true
XML映射文件
规范
1、XML映射文件的名称与Mapper接口名称一致,并且与接口放在相同包下。(同包同名)
2、XML文件的namespace属性为Mapper接口全类名一致
3、XML文件中的sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致。
操作
resultType指的是单条记录所封装的类型,注意此处的id 和Mapper接口的方法名一致
<select id = "getList" resultType = "com.mx.pojo.Emp">
select * from emp where name like concat('%',#{name},'%') and age between #{begin} and #{end} order by update_time desc
</select>
动态SQL
随着用户的输入或外部条件的变化而变化的sql语句。
if 标签
只有当条件成立时才会拼接该sql语句。
条件在test属性中声明。
<if test = "name!=null">
name like concat('%',#{name}, '%')
</if>
where 标签
对于多条件若不使用where标签,对于第一个为空的情况,仅拼接第二个条件可能出现结果如select * from sc where and age=1这种情况,会出现sql语法错误。
使用where标签会根据子标签自动生成正确的条件语句的sql内容
<where>
<if test = "name!=null">
name like concat('%',#{name}, '%')
</if>
<if>
</if>
</where>
set 标签
类似于where标签,在update方法中使用set标签代替硬编码的set 可以使得自动去除多余的" , "
foreach 标签
循环遍历
collection属性:遍历的集合
item属性:遍历出来的元素
separator:分隔符
open:遍历开始前冰洁的SQL片段
close:遍历结束后拼接的SQL片段
<delete id = "deleteByIds">
delete from emp where id in
<foreach collection = "ids" item = "id" separator = "," open = "(" close = ")">
#{id}
</foreach>
</delete>
sql 标签 &include 标签
对于表名、字段名是硬编码的程序而言,我们如果要修改表明或者字段名会很麻烦,甚至出错。因此使用sql和include标签提高代码的复用性。使用sql抽取,include用于将抽取的片段进行引用。
在文件的头部或者其他显眼的位置定义sql片段
<sql id = "commonSelect">
select id, username , password , name
from emp
</sql>
在需要的地方进行引用
<include refid = "commonSelect"/>
MybatisX
IDEA的插件,可以快速定位sql语句和接口方法。
使用后在接口方法下按住alt+enter可以快速生成对应的xml语句。
登录验证
在服务器端接收到请求后,在服务器端检查是否登录了。
登录标记
在每次请求中都可以获取到登录澈哥哥之后的标记。
会话技术
会话:用户打开浏览器访问web的资源,会话建立,知道一方断开连接会话结束。一次会话中包含多次请求和响应。
会话跟踪:维护浏览器状态的方法,服务其识别多次请求来自于统一浏览器们一边统一次会话的多次请求间共享数据。
cookie
保存在本地,每次请求发送给服务端。在响应头中setcookie,在请求头中cookie。
在调试台的Application中可以查看到对应的cookie。
cookie不安全、不能跨域,且移动端无法使用。
response.addCookie(new Cookie("login_username" , "mx"));
Cookie[] cookies = request.getCookies();
session
服务器端创建的,浏览器携带保存了sessionId的cookie,服务器将会根据这个cookie存储的sessionId找到对应创建的session对象。
但是session只能存储在一个服务器上,对于多个服务器无法共享session。如果服务器集群并使用负载均衡服务器数据会出错。
令牌技术
为用户生成一个令牌,令牌可以存储在cookie或其他当中,服务器发送令牌给浏览器存储在本地。浏览器每次响应数据都要携带令牌,如果令牌检验成功则成功响应。
可以将共享的数据存储在令牌当中。
支持PC端和移动端,解决了集群环境下的认证问题,减轻了服务器的存储压力。
使用密码学校验令牌的完整性,来确定没有被修改过。
需要自己实现令牌。
JWT
是一种间接的自暴寒的格式,用于通信双发以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
组成:
Header:记录令牌类型、签名算法。
Payload:携带自定义信息、默认信息等
Signature:反转Token被篡改、确保安全性。
前两个部分是BASE64编码的数据,并没有加密。
使用:
1、引入jwt依赖
2、生成
//JwtUtils工具类
void GenerateJwt(Map<String, Object> claims){
/*
Map<String, Object> claims = new HashMap<>();
claim.put("id",1);
claim.put("name","mx");//设置自定义内容
*/
Jwts.builder()//链式编程
.signWith( SignatureAlgorithm.HS256 ,"密钥")//签名算法
.setClaims(claim)//自定义内容
.setExpiration(new Date(System.currentTimeMillis() + 3600*1000 ))//设置有效期
}
3、登录成功后使用JSON下发令牌
//LoginController
public Result login(@RequestBody Emp emp){
Emp e =empService.login(emp);
//下发令牌
if(e!=null){
Map<String, Object> claims = new HashMap<>();
claim.put("id",e.getId());
claim.put("name", e.getName());
String Jwt = JwtUtils.generateJwt(claims);
return Result.success(Jwt)
}
return Result.error("用户名或密码错误");
}
4、前端每次请求时在请求头中携带令牌
5、解析令牌
//JwtUtils工具类
void parseJwt(){
Claims claim = Jwts.parser()
.serSigningKet("密钥")
.parseClaimsJws("JWT令牌")
.getBody();
}
6-1、使用过滤器拦截请求,检测是否登录
@Xslf4j
@WebFilter(urlPatterns = '/*')
class LoginCheckFilter implements Filter{
public void doFilter(ServletRequest request ,ServletReponse response FilterChain chain){
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
//1获取请求url
String url = req.getRequestURL().toString();
//2判断url中是否包含login,是则是登录,放行
if(url.contains("login")){
log.info("登录放行");
chain.doFilter(request,response);
return;
}
//3获取请求头中的令牌
String jwt = req.getHeader("token");
//4判断令牌是否存在
if(StrinUtils.haslength(jwt)){
log.info("token为空");
Result error = Result.error("NOT_LOGIN");
//手动转化JSON对象,记得导入fast-json包
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//解析token
try{
JwtUtils.parseJwt(jwt);
}catch(Exception e){
e.printStackTrace();
log.info("解析令牌失败");
Result error = Result.error("NOT_LOGIN");
//手动转化JSON对象,记得导入fast-json包
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//放行
chain.doFilter( request , response );
}
}
6-2使用拦截器拦截请求,检测是否登录
@Xslf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
public boolean preHandler(HttpServletrequest req ,HttpServletResponse resp ,Object handler){
//1获取请求url
String url = req.getRequestURL().toString();
//2判断url中是否包含login,是则是登录,放行
if(url.contains("login")){
log.info("登录放行");
return 1;
}
//3获取请求头中的令牌
String jwt = req.getHeader("token");
//4判断令牌是否存在
if(StrinUtils.haslength(jwt)){
log.info("token为空");
Result error = Result.error("NOT_LOGIN");
//手动转化JSON对象,记得导入fast-json包
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return 0;
}
//解析token
try{
JwtUtils.parseJwt(jwt);
}catch(Exception e){
e.printStackTrace();
log.info("解析令牌失败");
Result error = Result.error("NOT_LOGIN");
//手动转化JSON对象,记得导入fast-json包
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return 0;
}
//放行
return 1;
}
}
若令牌没有修改、过期则可以正常解析,否则报错。
统一拦截
在业务前设置拦截器拦截请求。
过滤器Filter
将对资源的请求拦截下来,实现一些特殊的功能。一般完成一些通用的操作比如:登录校验、统一编码处理、敏感字符处理。
定义Filter:定义一个类,实现Filter接口,重写所有方法
配置:@WebFilter(urlPatterns = “/*”)拦截哪些请求。同时在启动类上添加注解 @ServletComponentScan 开启Servlet支持。
@WebFilter(urlPatterns = "/*")//拦截哪些请求
class DemoFilter implements Filter{
public void init(Filter filterConfig){//初始化方法、web服务器启动,创建Filter的时候调用,只调用一次
Filter.super.init(filterConfig);
}
public void doFilter(Servlet request request,ServletResponse response ,FilterChain chain){
sout("拦截");
chain.doFilter(request,response);//往下放行,使得它接着做原有的请求
}
public void destroy(){
Filter.super.destroy();
}
}
执行流程
1、放行前逻辑
2、放行,正常执行操作
3、放行后逻辑
过滤器链
在一个web中可以配置多个过滤器,多个过滤器形成了一个过滤器链。
即doFilter中的参数FilterChain 使用chain.doFilter做的是放行到下一个过滤器当中,若是最后一个过滤器,则正常访问资源。
过滤器链的持续顺序由类名决定
拦截器
动态拦截方法条用的机制类似于过滤器。
它是Spring框架提供的动态拦截控制器方法的执行。
它用于拦截请求,在指定的方法调用的前后根据业务需要执行预先设定的代码。
使用
1、定义拦截器没实现HandlerInterceotor接口,并重写其所有方法。
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
public boolean preHandler(HttpServletrequest req ,HttpServletResponse resp ,Object handler){//目标方法执行前执行,放行则return 1,否则0
sout("pre");
return 1;
}
public void postHandler(HttpServletrequest req ,HttpServletResponse res,Object handler ,ModelAndView modelAndView ){//目标资源执行后执行
sout("post");
return ;
}
public void afterCompletion(HttpServletrequest req ,HttpServletResponse res,Object handler ,Exception ex){ // 视图渲染完后执行,最后执行
sout("afterCompletion");
return ;
}
}
2、注册拦截器
定义配置类如下:
@Configration
public class WebConfig implements WebMvcConfigurer{
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
public void addInterceptors(InterceptorRegistry registry){
//配置拦截路径
registry.addInterceptor(logincheckInterceptor)
.addPathPatterns("/**")//拦截的部分
.excludePathPatterns("/login");//拦截的当中被排除的
}
}
拦截路径
值得注意的是在拦截器中,通配符*的使用方法有所不同/*表示的是匹配下一级路径/**才是任意级路径
执行顺序
先执行服务器容器,所以先执行Filter过滤器;再到识别Servlet,执行前端控制器DispatcherServlet;再执行拦截器Interceptor ;最后访问Controller当中的方法。之后依次返回执行。
Filter于Interceptor对比
1接口规范不同
2拦截范围不同:拦截器会拦截所有的资源,而Interceptor仅仅拦截String环境中的资源。
异常处理
方案1:try catch
方案2:全局异常处理器
全局异常处理器
所有异常抛出到表现层处理,分类处理,使用AOP处理。使得在出现异常的时候也能保持返回给前端的数据的一致性,并使得在出现异常的时候服务器还能正常运行。
使用Spring中的@RestControllerAdvice注解表明这是异常处理器,@ExceptionHandler说明处理的异常种类。
@RestControllerAdvice
class GlobalExceptionHandler{
@ExceptionHandler( Exception.class)
public Result ex(Exception ex){
ex.printStackTrace();
return Result.error( "操作失败");
}
}
事务管理
一组操作的集合,是不可分割的工作单位,要么同时成功,要么同时失败。
注解:@Transactional
位置:业务层的方法上、类上、接口上
作用:将当前方法交给Spring进行事务管理,方法执行前开启事务;执行完毕提交事务;出现异常回滚事务。
一般加在经常进行的增删改操作上。
可以查询网页了解如何开启事务管理日志
回滚
默认情况下只有运算时异常才会进行回滚,可以对rollbackFor属性用于控制何种异常会进行事务的回滚。
@Transactional(rollbackFor = Exception.class)
事务的传播行为
一个事务方法被另一个事务方法调用的时候,这个事务方法应该如何进行事务控制。
可以配置propagation属性进行传播行为的设定。
常见属性值
REQUIRED : 【默认值】需要事务,有则加入,无则创建新事务。出现异常回滚的时候两个都回滚
REQUIRES_NEW : 需要新事务,无论有无,总是创建新事务,原有事务挂起。新事物和原事务是独立的。
SuPPORTS : 支持事务,有则加入,无则在无事务状态中运行
NOT_SUPPORTED : 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
MANDATORY : 必须有事务,否则抛异常
NEVER : 必须没事务,否则抛异常
AOP面向切面编程
基于动态代理,在不惊动原有代码的基础上做功能增强。
1、导入依赖
2、编写AOP程序:针对特定方法根据业务编程。
@Component
@Aspect //说明当前是AOP类
class TimeAspect{
@Around("execution(* com.mx.dao.*.*(..))")//切入点表达式,指定AOP加载在哪些方法上
private Object recordTime(Proceeding JoinPoint pJP){
//......
Object now= pJP.proceed);//调用原始方法运行
//......
return now;
}
}
核心概念
连接点JoinPoint:可以被AOP控制的方法,暗含方法执行的相关信息。
通知Advice:指那些重复的逻辑,也就是共性功能
切入点PointCut:匹配连接点的条件,通知仅会在切入点方法执行时被应用。即指定那些方法被影响到的条件。
切面Aspect:描述通知和切入点的对应关系(通知+切入点)
目标对象Target:通知所应用的对象。
运行过程
一旦使用AOP运行的就不再是原有的对象了,而是原有对象的代理对象。
通知类型
1.@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
2.@Before:前置通知,此注解标注的通知方法在目标方法前被执行
3.@After︰后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
4.@AfterReturning :返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
5.@AfterThrowing :异常后通知,此注解标注的通知方法发生异常后执行
通知顺序
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。
具体的执行顺序和类名有关,before字母排序靠前的先执行,after相反。
也可以使用 @Order(数字)加在切面类上来控制顺序。
目标方法前的通知方法:数字小的先执行目标方法后的通知方法:数字小的后执行
切入点表达式
切入点表达式:描述切入点方法的一种表达式
可以使用@Pointcut()定义表达式,之后再使用
@Pointcut ("execution (public void com.itheima.service .impl.DeptServiceImpl.delete (java.lang.Integer)")
void pt(){};
@Before("pt()")
public void before (){
log.info ("MyAspect6 ... before ... ");
}
作用:主要用来决定项目中的哪些方法需要加入通知常见形式:
- execution(…)︰根据方法的签名来匹配
- annotation(…)︰根据注解匹配
execution
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
其中带?的表示可以省略的部分
访问修饰符:可省略(比如:public、protected)
包名.类名:可省略
throws异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
可以使用通配符描述切入点
“*”︰单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
execution(* com .* .service.* .update*(*))
“…” :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
execution(* com.itheima. .Deptservice.* (…)
annotation
annotation切入点表达式,用于匹配标识有特定注解的方法。
需要自定义注解,
@Retention (RetentionPolicy.RUNTIME)
@Target (ElementType.METHOD)
public @interface MyLog{}
@annotation( com.itheima.anno.MyLog)
@Before("@annotation (com.itheima.anno.MyLog)"")
public void before () {
log.info ("before ...." );
}
连接点
在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint
对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型
joinPoint.getTarget().getclass( ).getName();//获取目标类名
joinPoint.getsignature();//获取目标方法签名
joinPoint.getsignature().getName();//获取目标方法名
joinPoint.getArgs();//获取目标方法运行参数
joinPoint.proceed();//执行原始方法,获取返回值(环绕通知)
案例:写入Log当前操作的信息
获取信息:
ID:先自动装配一个HttpServletRequest request,然后request.getHeader(“token”);获取JWT令牌,进一步解析后获得ID
@Autowired
HttpServletRequest request;
@Around("Cannotation (com.itheima.anno.MyLog)")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
//操作人ID-当前情录员工ID
//获取请求头中的jwt令牌,解析令牌
String jwt = request.getHeader("token");
claims clains = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer)claims.get("id" );
LocalDateTime operateTime = LocalDateTime.now();
string className = joinPoint.getTarget().getclass( ).getName();
private string methodName = joinPoint.getsignature().getName();
private string methodParams = joinPoint.getArgs();
Object result = joinPoint.proceed();//调用原方法,并返回结构
private string returnvalue = JSONObject.toJSONString(result);
private Long costTime = end - begin;
OperateLog op = new OperateLog(...);
operateLogMapper.insert(op);//shu'j'k
log.info("AOP操作日志",op);
}