本文注解总结:
注解收录集
Spring框架
一.Spring介绍
Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。
Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于JEE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。
知识补充:
1.J2SE java基础 J2EE java企业级开发 J2ME 移动端开发
2. bean: spring容器管理的对象称之为bean
3. 生命周期: 对象创建/对象的初始化/对象执行业务逻辑/对象销毁
4. 轻量级容器: Spring在内存中申请了一块内存空间,用来存储其它对象.
二.Spring-IOC
2.1 IOC调用原理图
Ioc全称Inversion of Control,即“控制反转”,这是一种设计思想。对象创建的权利由Spring框架完成.由容器管理对象的生命周期,需要的时候进行依赖注入(DI)即可.
2.2 Spring-IOC 配置文件方式
说明: spring早期都使用配置文件的方式来管理对象.但是随着软件的升级当下注解的方式已经成为主流. 所以先完成xml配置文件的方式,之后完成注解的方式.
内容说明: xml文件 一般都会有固定的头标签
2.2.1 准备Dog类
package com.jt.demo2;
public class Dog {
public Dog() {
System.out.println("我说Dog的无参构造");
}
public void hello(){
System.out.println("小狗交给 Spring容器管理");
}
}
2.2.2 准备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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--将Dog对象交给Spring容器管理
1.属性id是bean的唯一标识符.一般类名首字母小写
2.属性class 表示被管理的类
-->
<bean id="dog" class="com.jt.demo2.Dog"></bean>
<!--<bean id="cat" class="com.jt.demo2.Cat"></bean>-->
</beans>
2.2.3 准备SpringGetDog类
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringGetDog {
//该类表示我们要从spring容器中,动态获取Dog对象
public static void main(String[] args) {
//1.指定spring配置文件的路径
String resource = "spring.xml";
//2.启动spring容器
ApplicationContext context = new ClassPathXmlApplicationContext(resource);
//3.从容器中获取对象
Dog dog1 = (Dog) context.getBean("dog");//通过id获取
Dog dog2 = context.getBean(Dog.class);//通过class获取
System.out.println(dog1);//输出的是地址
System.out.println(dog2);
//4.对象调用方法
dog1.hello();
dog2.hello();
getDog();
}
}
2.2.4 Spring核心原理
说明: 默认条件下,Spring容器启动时,就会创建对象,如果创建对象的过程中,出现问题.则容器启动失败.
反射机制
/*
* Spring实例化对象的核心原理-反射机制
* 注意事项:反射代码 必然会调用对象身上的无参构造
* */
public static void getDog(){
try {
/*获取反射的字节码对象*/
Class<?> aClass = Class.forName("com.jt.demo2.Dog");
Dog dog=(Dog) aClass.newInstance();
dog.hello();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
2.2.5 Spring创建对象的步骤
- 定义spring的配置文件的地址.
- Spring容器启动时加载指定的配置文件.
- 当Spring扫描到bean标签时.加载属性id和class
- 根据反射机制 根据class的路径反射.实例化对象.
- Spring在内部维护了一个大型的Map<k,v>集合(容器),bean中的Id充当Map中的key. 实例化的对象充当Map中的value. 形式: Map<Id,实例化对象> 到此为止 spring容器启动成功
- 从spring容器中通过Id或者class类型 获取对象.
- 根据对象调用业务方法.
2.3 Spring-IOC注解方式
2.3.1 说明
传统Spring框架采用xml配置文件的方式进行维护.但是随着springboot框架的崛起,注解开发渐渐的成为主流.所以将来以注解开发为准.
组成部分:
- 实体类: Spring容器管理的类(对象)
- 配置类: 相当于早期的xml配置文件
- 测试代码: 利用注解的方式启动spring容器
2.3.2 Cat类
package com.jt.demo3;
public class Cat {
public Cat() {
System.out.println("我是demo3的无参构造");
}
public void hello(){
System.out.println("小花猫,喵喵喵~~~~");
}
}
2.3.3 配置类–Spring中的单例/多例模式–懒加载策略
@Scope
注解
单例模式: 内存中的对象就一份.
多例模式: 内存中的对象有多份.
概念说明: Spring中的对象默认是单例的.
@Lazy
注解
默认条件下,Spring容器启动,则会创建对象.(类比:饿汉式),如果开启了懒加载.则用户什么时候使用.则对象什么时候创建(类比:懒汉式).
关于多例模式和懒加载说明:
@Lazy 只能控制单例模式, 多例模式都是懒加载.
package com.jt.demo3;
import org.springframework.context.annotation.*;
@ComponentScan("com.jt.demo3") //根据指定的包路径扫描注解,扫描当前包及其子孙包
@Configuration //标记当前类是配置类,其实就是配置文件
public class SpringCatConfig {
/*
* 注解管理对象--自定义对象:
* 1.方法必须为公有的
* 2.方法必须添加返回值,返回值的对象,就是容器管理的对象
* 3.方法的名称就是bean的id
* 3.方法必须使用@Bean的注解标识,spring才会执行该方法,标识该对象要交给Spring容器
*/
@Bean
@Scope("prototype")//表示多例对象
//@Lazy //开启懒加载
//@Scope("singleton")//表示单例对象 默认
public Cat cat(){
return new Cat();
}
@Bean
public Snake snake(){
return new Snake();
}
}
测试类
package com.jt.demo3;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringAnno {
public static void main(String[] args) {
//利用注解启动spring容器
ApplicationContext context=new AnnotationConfigApplicationContext(SpringCatConfig.class);
//根据类型获取对象
Cat cat = context.getBean(Cat.class);
Cat cat2 = context.getBean(Cat.class);
Cat cat3 = context.getBean(Cat.class);
System.out.println(cat);
System.out.println(cat2);
System.out.println(cat3);
cat.hello();
}
}
2.4 Spring的生命周期
对象创建阶段
–初始化阶段
–业务调用阶段
–对象销毁阶段
2.4.1 创建Snake类
package com.jt.demo3;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class Snake {
//1.无参构造
public Snake() {
System.out.println("蛇蛋诞生");
}
//2.初始化方法
@PostConstruct //构造方法之后
public void init() {
System.out.println("蛇破壳了,四处溜达");
}
//3.业务方法
public void eat() {
System.out.println("蛇四处觅食");
}
//4.销毁方法
@PreDestroy //销毁方法之前
public void destroy(){
System.out.println("打蛇七寸,一命呜呼");
}
}
2.4.2 将Snake对象交给Spring容器管理
@Bean
public Snake snake(){
return new Snake();
}
2.4.3 生命周期方法测试
//利用注解启动spring容器
AnnotationConfigApplicationContext context1=new AnnotationConfigApplicationContext(Snake.class);
//根据类型获取对象
Snake snake=context1.getBean(Snake.class);
//业务调用
snake.eat();
//关闭容器即可
context1.close();
三.Spring的依赖注入-DI
3.1 概述
平常的java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提出了依赖注入的思想,即依赖类不由程序员实例化,而是通过spring容器帮我们new指定实例并且将实例注入到需要该对象的类中。依赖注入的另一种说法是“控制反转”,通俗的理解是:平常我们new一个实例,这个实例的控制权是我们程序员,而控制反转是指new实例工作不由我们程序员来做而是交给spring容器来做。
3.2 @Autowired:自动装配
注意事项:一般Spring框架内部的接口都是单实现,特殊条件下可以多实现
3.2.1 准备接口
package com.jt.demo4;
public interface Pet {
void hello();
}
3.2.2 准备Dog类和Cat类
package com.jt.demo4;
import org.springframework.stereotype.Component;
//@Component("abc") key:abc
@Component //将该类交给Spring容器管理 key:dog,value:反射机制创建对象
public class Dog implements Pet {
public Dog() {
System.out.println("Dog的无参构造");
}
@Override
public void hello() {
System.out.println("快到圣诞节啦");
}
}
package com.jt.demo4;
import org.springframework.stereotype.Component;
@Component
public class Cat implements Pet{
@Override
public void hello() {
System.out.println("小猫爱吃鱼");
}
}
3.2.3 准备User类
注入:将Spring容器中的对象进行引用
@Autowired
:可以将容器中的对象进行注入
1.按照类型注入 如果注入的类型是接口,则自动的查找其实现类对象进行注入
注意事项:一般Spring框架内部的接口都是单实现,特殊条件下可以多实现
2.按照名称注入 @Autowired + @Qualifier("dog")
package com.jt.demo4;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component //将User对象交给Spring容器管理
public class User {
public User() {
System.out.println("User的无参构造");
}
/*
* 注入:将Spring容器中的对象进行引用
* @Autowired:可以将容器中的对象进行注入
* 1.按照类型注入 如果注入的类型是接口,则自动的查找其实现类对象进行注入
* 注意事项:一般Spring框架内部的接口都是单实现,特殊条件下可以多实现
* 2.按照名称注入 @Autowired + @Qualifier("dog")
* */
@Autowired
@Qualifier(value="cat")
private Pet pet;
public void hello(){
System.out.println("我是user的hello");
pet.hello();
}
}
3.3.4设置配置类
package com.jt.demo4;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan("com.jt.demo4") //根据指定的包路径扫描注解,扫描当前包及其子孙包
@Configuration //标记当前类是配置类,其实就是配置文件
public class SpringConfig {
}
3.3.5 测试
package com.jt.demo4;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringDI {
public static void main(String[] args) {
//利用注解启动spring容器
ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
//根据类型获取对象
User user=context.getBean(User.class);
//业务调用
user.hello();
}
}
3.3.6 接口的多实现
当接口多实现时,DI注入需要使用名称注入
@Autowired @Qualifier(value="cat")
四.MVC设计思想
4.1 传统代码结构
说明: 如果将所有的业务代码都写到一个方法中,则导致后期维护耦合性高,为了提高程序的扩展性.将程序按照MVC设计思想 进行管理.
4.2 MVC设计思想说明
M: Model 数据层
V: View 视图层
C: Control 控制层
总结: MVC 主要的目的降低代码的耦合性,提高扩展性.方便后续开发.
4.2 三层代码结构
说明: 基于MVC设计思想的启发,在后端为了提高代码的扩展性,一般将后端代码分为三层.
分层:
- Controller层 主要与页面进行交互
@Controller
- Service层 主要实现后端的业务逻辑
@Service
- Dao层/Mapper层 主要与数据库进行交互 也把该层称之为 “持久层”
@Repository
/@Mapper
4.3 实现
4.3.1 Mapper层
- UserMapper接口
package com.jt.demo5.mapper;
public interface UserMapper {
void addUser();
}
- UserMapperImpl实现类
package com.jt.demo5.mapper;
import org.springframework.stereotype.Repository;
@Repository //标识持久层 该类交给Spring容器管理 key:userMapperImpl value:对象
public class UserMapperImpl implements UserMapper{
@Override
public void addUser() {
System.out.println("新增用户成功!!!!");
}
}
4.3.2 Service层
- UserService接口
package com.jt.demo5.service;
public interface UserService {
void addUser();
}
- UserServiceImpl实现类
package com.jt.demo5.service;
import com.jt.demo5.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper; //IOC+DI 解耦!!!!!
@Override
public void addUser() {
userMapper.addUser();
}
}
4.3.3 Controller层
- UserController类
package com.jt.demo5.controller;
import com.jt.demo5.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller //key=userController
public class UserController {
@Autowired
private UserService userService;
public void addUser(){
userService.addUser();
}
}
4.3.4 配置类
SpringConfig类
package com.jt.demo5.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.jt.demo5")
public class SpringConfig {
}
4.3.5 测试
package com.jt.demo5;
import com.jt.demo5.config.SpringConfig;
import com.jt.demo5.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Spring_MVC {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserController userController = context.getBean(UserController.class);
userController.addUser();
}
}
4.4 @value注解/@PropertySource
注解的作用: 为属性赋值
从spring容器中动态获取数据
@PropertySource(value = "classpath:/addUser.properties",encoding = "utf-8")
:根据指定的路径,加载properties配置文件,数据添加到spring容器中
4.4.1 编辑properties文件
# 1.数据结构: key=value
# 2.无需添加多余的引号
# 3.注意多余的空格
# 4.程序读取properties文件时,默认采用ISO-8859-1编码! 中文必定乱码
name=张三
4.4.2 编辑Mapper层
- UserMapper接口
package com.jt.demo6.mapper;
public interface UserMapper {
void addUser();
}
- UserMapperImpl实现类
package com.jt.demo6.mapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Repository;
@Repository
//Spring根据指定的路径,加载properties配置文件,数据添加到spring容器中
@PropertySource(value = "classpath:/addUser.properties",encoding = "utf-8")
public class UserMapperImpl implements UserMapper {
/**
* @Value 注解的作用: 为属性赋值
* 需求: 从spring容器中动态获取数据
*/
@Value("${name}")
private String name;
@Override
public void addUser() {
System.out.println("新增用户:"+name);
}
}
4.4.3 配置类
package com.jt.demo6.controller;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.jt.demo6")
public class SpringConfig {
}
4.4.4 测试类
package com.jt.demo6;
import com.jt.demo6.controller.SpringConfig;
import com.jt.demo6.mapper.UserMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringValue {
public static void main(String[] args) {
ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
//获取的对象 实现类和接口都可
UserMapper userMapper = context.getBean(UserMapper.class);
userMapper.addUser();
}
}
五.Spring-AOP
5.1 概述
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
5.1.1 含义
Aspect,的确是“方面”的意思。不过,汉语传统语义中的“方面”,大多数情况下指的是一件事情的不同维度、或者说不同角度上的特性,比如我们常说:“这件事情要从几个方面来看待”,往往意思是:需要从不同的角度来看待同一个事物。这里的“方面”,指的是事物的外在特性在不同观察角度下的体现。而在AOP中,Aspect的含义,可能更多的理解为“切面”比较合适。
可以通过预编译方式
和运行其动态代理
实现在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。
在Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务
(例如审计(auditing)和事务(transaction)管理)进行内聚性
的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
总结: Spring中的AOP 利用代理对象在不修改源代码的条件下,对方法进行扩展.
5.1.2 主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
5.1.3 主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码
。
5.1.4 AOP/OOP区别
AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。
OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
这两种设计思想在目标上有着本质的差异。
上面的陈述可能过于理论化,举个简单的例子,对于“雇员”这样一个业务实体进行封装,自然是OOP/OOD的任务,我们可以为其建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用AOP设计思想对“雇员”进行封装将无从谈起。
同样,对于“权限检查”这一动作片断进行划分,则是AOP的目标领域。而通过OOD/OOP对一个动作进行封装,则有点不伦不类。
5.2 代理模式
5.2.1 业务说明
事务特性: 1. 原子性 2. 一致性 3.隔离性 4.持久性
业务说明: 在增/删除/修改的操作过程中添加事务控制.
@Override
public void addUser() {
try {
System.out.println("事务开始");
System.out.println("完成用户新增");
System.out.println("事务提交");
}catch (Exception e){
e.printStackTrace();
System.out.println("事务回滚");
}
}
@Override
public void deleteUser() {
try {
System.out.println("事务开始");
System.out.println("完成用户删除");
System.out.println("事务提交");
}catch (Exception e){
e.printStackTrace();
System.out.println("事务回滚");
}
}
结论:
- 如果按照上述的代码进行编辑,则所有增/删除/修改操作的代码都必须按照上述的规则.那么
代码冗余.
- UserService与事务控制代码紧紧的耦合在一起.不方便后期扩展. 以后尽可能
保证业务的纯粹性.
5.2.2 代理模式说明
说明: 在业务层不方便做,但是又不得不做的事情,可以放到代理对象中. 通过这样的设计就可以解决业务层耦合的问题. 代理对象看起来和真是的对象 一模一样.所以用户使用不会察觉.
类比:
- 外卖也是一种典型的代理思想
- 游戏代练
- 房屋中介
5.2.3 动态代理-JDK动态代理
代理类:Proxy
动态代理类 (以下简称为代理类 )是一个实现在类创建时在运行时指定的接口列表的类,具有如下所述的行为。 代理接口是由代理类实现的接口。 代理实例是代理类的一个实例。 每个代理实例都有一个关联的调用处理程序对象,它实现了接口InvocationHandler
。 通过其代理接口之一的代理实例上的方法调用将被分派到实例调用处理程序的invoke
方法,传递代理实例, java.lang.reflect.Method被调用方法的java.lang.reflect.Method对象以及包含参数的类型Object Object的数组。 调用处理程序适当地处理编码方法调用,并且返回的结果将作为方法在代理实例上调用的结果返回。
5.2.4 方法详细信息
5.2.5 InvocationHandler接口
public interface InvocationHandler
InvocationHandler是由代理实例的调用处理程序实现的接口 。
每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。
5.3 JDK动态代理
5.3.1 添加配置类
package com.jt.demo1.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.jt.demo1")
public class SpringConfig {
}
5.3.2 业务类
package com.jt.demo1.service;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("完成用户新增");
}
@Override
public void deleteUser() {
System.out.println("完成用户删除");
}
}
5.3.3 动态代理类-JDK
package com.jt.demo1.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxy {
/*获取代理对象
参数说明:
1.ClassLoader loader 类加载器 读取真实的数据
2.Class<?>[] interfaces 要求传递接口信息
3.InvocationHandler h 当代理对象执行方法时 执行
注意事项:JDK代理必须要求 被代理者有接口(本身就是接口)或者实现接口(实现类)
*/
public static Object getProxy(Object target){
//1.获取类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//2.获取接口
Class<?>[] interfaces = target.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader,interfaces,getInvocationHandler(target));
}
//代理对象执行方法时调用
public static InvocationHandler getInvocationHandler(Object target){
//这些代码都是写死的
return new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("事务开始");
//执行目标方法(执行真实对象的方法-被代理者)
Object invoke = method.invoke(target, args);
System.out.println("事务提交");
return invoke;
}
};
}
}
5.3.4 业务测试
package com.jt.demo1;
import com.jt.demo1.config.SpringConfig;
import com.jt.demo1.proxy.JDKProxy;
import com.jt.demo1.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTx {
public static void main(String[] args) {
ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean(UserService.class);
//userService.addUser();
//userService.deleteUser();
System.out.println(userService.getClass());
//获取代理对象
UserService proxy =(UserService) JDKProxy.getProxy(userService);
System.out.println(proxy.getClass());
//基于代理对象,执行业务操作 实现方法扩展
proxy.addUser();
proxy.deleteUser();
}
}
运行结果:
5.4 JDK动态代理特点
- 类型名称:class com.sun.proxy.$Proxy9
- 要求:被代理者必须是接口或者是接口的实现类
- JDK代理是java原生提供的API 无需导包
- JDK动态代理在框架的源码中经常使用
5.5 CGlib动态代理
5.5.1 CGBLib动态代理特点
历史原因: JDK动态代理要求必须"有接口",但是某些类它没有接口,则无法使用JDK代理生成代理对象. 所以为了填补知识的空缺,则引入cglib代理.
问题说明: cglib动态代理 要求有无接口都可以创建代理对象. 问题? 如何保证和被代理者"相同"
答案(特点): 要求cglib动态代理继承被代理者.代理对象是被代理者的子类.
5.6 Spring AOP入门
5.6.1 AOP中专业术语
1).连接点: 用户可以被扩展的方法
2).切入点: 用户实际扩展的方法
3).通知: 扩展方法的具体实现
4).切面: 将通知应用到切入点的过程
5.6.2 通知类型
定义通知方法:
-
@Before
前置通知 在目标方法执行之后执行 -
@AfterReturning
后置通知 在目标方法执行之后执行 -
@AfterThrowing
异常通知 在目标方法抛出异常时执行
后置通知与异常通知是互斥的,只能有一个 -
@After
最终通知 都要执行的通知
说明:上述的四大通知一般用于记录程序的运行状态,只做记录 -
@Around
环绕通知 在目标方法执行前后都要执行的通知记录程序的状态:
1. 目标对象的class/类路径
2. 目标对象的方法名
3. 目标对象的方法的参数
4. 获取目标对象方法的返回值
5. 获取目标对象执行报错的异常信息
环绕通知:
- 特点:
1. 方法执行前后,通知都要执行
2. 环绕通知可以控制目标方法是否执行
3. 环绕通知必须添加返回值 - proceed()
作用1:如果有下一个通知,则执行下一个通知
作用2:如果没有下一个通知,则执行目标方法
5.6.3 切入点表达式
1). bean(bean的Id号)
按照bean匹配
2). within(包名.类名)
可以使用通配符
3). execution(返回值类型 包名.类名.方法名(参数列表))
4). @annotation(包名.注解名称)
切面 = 切入点表达式 + 通知方法
-
切入点:
理解:可以理解为就是一个if的判断
判断的条件:切入点表达式
规则: 如果满足表达式 则判断为true,则执行通知方法
如果不满足表达式 则判断为false, 则不执行通知方法 -
切入点表达式
2.1 bean(对象的id) 每次拦截,只拦截1个
2.2 within(包名.类名)
2.3 execution(返回值类型 包名.类名.方法名(参数列表))
2.4 @annotation(注解的路径) -
切入点表达式案例练习
3.1@Pointcut("bean(userServiceImpl)")
只匹配ID为userServiceImpl的对象
3.2@Pointcut("within(com.jt.demo2.service.*)")
匹配xx.xx.service包下的所有对象
说明:上诉操作 匹配的粒度是粗粒度的,按类匹配
3.3@Pointcut("execution(* com.jt.demo2.service..*.*(..))")
拦截任意返回值类型 xx.xx.service包下所有子孙包的所有类的任意方法
@Pointcut("execution(* com.jt.demo2.service..*.add*(..))")
拦截返回值类型任意 xx.xx.service包下所有子孙包的所有类的 add开头的方法
3.4@Pointcut("@annotation(com.jt.demo2.anno.CGB2110)")
拦截xx包下的CGB2110的注解
5.6.4 引入AOPjar包文件
<!--引入AOP jar包文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
5.6.5 定义切面类@Aspect
package com.jt.demo2.aop;
import com.jt.demo2.anno.Find;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component //将当前类交给Spring容器管理
@Aspect //我是一个切面类
public class SpringAop {
/* 切面 = 切入点表达式 + 通知方法
* 1. 切入点:
* 理解:可以理解为就是一个if的判断
* 判断的条件:切入点表达式
* 规则: 如果满足表达式 则判断为true,则执行通知方法
* 如果不满足表达式 则判断为false, 则不执行通知方法
* 2.切入点表达式
* 2.1 bean(对象的id) 每次拦截,只拦截1个
* 2.2 within(包名.类名)
* 2.3 execution(返回值类型 包名.类名.方法名(参数列表))
* 2.4 @annotation(注解的路径)
*
* 3.切入点表达式案例练习
* 3.1 @Pointcut("bean(userServiceImpl)") 只匹配ID为userServiceImpl的对象
* 3.2 @Pointcut("within(com.jt.demo2.service.*)") 匹配xx.xx.service包下的所有对象
* 说明:上诉操作 匹配的粒度是粗粒度的,按类匹配
* 3.3 @Pointcut("execution(* com.jt.demo2.service..*.*(..))")
* * 拦截任意返回值类型 xx.xx.service包下所有子孙包的所有类的任意方法
* @Pointcut("execution(* com.jt.demo2.service..*.add*(..))")
* 拦截返回值类型任意 xx.xx.service包下所有子孙包的所有类的 add开头的方法
* 3.4 @Pointcut("@annotation(com.jt.demo2.anno.CGB2110)")
* 拦截xx包下的CGB2110的注解
*
* */
//@Pointcut("bean(userServiceImpl)")
//@Pointcut("within(com.jt.demo2.service.*)")
//@Pointcut("execution(* com.jt.demo2.service..*.add*(..))")
@Pointcut("@annotation(com.jt.demo2.anno.CGB2110)")
public void pointcut() {
}
/*
* 定义通知方法:
* 1.前置通知 在目标方法执行之后执行
* 2.后置通知 在目标方法执行之后执行
* 3.异常通知 在目标方法抛出异常时执行
* 4.最终通知 都要执行的通知
* 说明:上述的四大通知一般用于记录程序的运行状态,只做记录
* 5.环绕通知 在目标方法执行前后都要执行的通知
*
* 记录程序的状态:
* 1.目标对象的class/类路径
* 2.目标对象的方法名
* 3.目标对象的方法的参数
* 4.获取目标对象方法的返回值
* 5.获取目标对象执行报错的异常信息
* */
//@Before("pointcut()")
public void before(JoinPoint joinPoint) {
//1.获取目标对象的类型
Class<?> targetClass = joinPoint.getTarget().getClass();
//2.获取目标对象的路径
String path = joinPoint.getSignature().getDeclaringTypeName();
//3.获取目标对象的方法名称
String methodName = joinPoint.getSignature().getName();
//4.获取方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("类型:" + targetClass);
System.out.println("类路径:" + path);
System.out.println("方法名:" + methodName);
System.out.println("参数:" + Arrays.toString(args));
}
//注意事项:如果有多个参数,joinPoint必须位于第一位
//@AfterReturning(value = "pointcut()",returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
//如果需要获取当前的方法信息,则可以通过joinPoint获取
System.out.println("我是后置通知,获取方法的返回值:" + result);
}
/*
后置通知与异常通知是互斥的,只能有一个
*/
//@AfterThrowing(value = "pointcut()",throwing="exception")
public void afterThrow(JoinPoint joinPoint, Exception exception) {
//打印异常
//exception.printStackTrace();
System.out.println("我说异常通知:" + exception.getMessage());
}
//最终通知
//@After("pointcut()")
public void after() {
System.out.println("我是最终通知");
}
/*
环绕通知:
1.特点:
1.方法执行前后,通知都要执行
2.环绕通知可以控制目标方法是否执行
3.环绕通知必须添加返回值
2.proceed()
作用1:如果有下一个通知,则执行下一个通知
作用2:如果没有下一个通知,则执行目标方法
*/
@Around(value = "pointcut()")
@Order(3)
public Object around(ProceedingJoinPoint JoinPoint) throws Throwable {
System.out.println("环绕通知开始");
long startTime = System.currentTimeMillis();
Object result = JoinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("方法执行时间:" + (endTime - startTime) + "毫秒");
System.out.println("环绕通知结束");
return result;
}
/*
* 知识点:
* 1.如果切入点表达式只对当前通知有效,则可以按照如下方式编译
* 要求:动态的拦截Find注解,并且要获取Find注解中的参数id
* 难点:动态获取注解对象
* 代码解释:
* 1.@annotation(find) 拦截find变量名称对应类型的注解
* 2.当匹配该注解之后,将注解对象当做参数传递给find
* 优势:可以一步到位获取注解的内容,避免了反射的代码
*
* */
//@Before("@annotation(find)")
public void before2(Find find) {
System.out.println("ID的值为:" + find.id());
}
}
5.6.6 定义配置类@EnableAspectJAutoProxy
package com.jt.demo2.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.jt.demo2")
@EnableAspectJAutoProxy //让spring中的AOP生效
public class SpringConfig {
}
5.6.7 测试类
package com.jt.demo2;
import com.jt.demo2.config.SpringConfig;
import com.jt.demo2.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Spring_AOP {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
//理论值:根据接口获取实现类对象 但是与切入点表达式匹配,为了后续扩展方便,为其创建代理对象
UserService userService = context.getBean(UserService.class);
//如果是实现类对象,则方法没有被扩展
//如果是代理对象,则方法被扩展 aop有效
System.out.println(userService.getClass());
userService.addUser();
//userService.findCount();//测试带返回值的方法
}
}
六.SpringMVC
Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring MVC框架或集成其他MVC开发框架,如Struts1(现在一般不用),Struts 2(一般老项目使用)等等。
总结: SpringMVC是Spring基于MVC思想,专门针对于前后端交互的开发的框架
6.1 Axios
6.1.1 Ajax
-
Ajax特点: 局部刷新,异步访问
-
什么是同步: 当用户刷新页面时,必须等到所有界面数据加载回来之后,统一刷新显示
-
什么是异步: 当用户刷新页面时,在内部发起多个请求,页面数据先回来的,则局部页面先刷新展示
-
Ajax为什么可以异步: Ajax引擎
-
常见Ajax API jQuery/axios 让Ajax调用变得简洁
6.1.2 axios入门案例
web前段
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Axios入门案例</title>
</head>
<body>
<!-- 导入就是文件 -->
<script src="./js/axios.js"></script>
<!-- 2.编辑就是代码 -->
<script type="text/javascript">
//url地址:http://loclhost:8080/findUser get/post
let url1="http://loclhost:8080/findUser"
//语法:axios.get(url地址,传递的参数)
axios.get(url1).then(function(promise){
console.log(promise.data)
})
/*
axios.get(url1).then(res)=>{
})
*/
</script>
</body>
</html>
6.2 SpringMVC概述
Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的SpringMVC框架或集成其他MVC开发框架,如Struts1(现在一 般不用),Struts2(一般老项目使用)等。
SpringMVC就是基于MVC设计模式来实现的。
我们的POJO就是Model层,我们的JSP就是视图层,我们的Controller就是控制层。
现在主流基于SSM三大框架开发都是在MVC上继续演化,又分为持久层DAO,业务层Service,控制层Controller。持久层用来和数据库读写ORM,业务层用来处理复杂的业务逻辑,控制层用来处理MVC的控制。
6.2.1 Spring入门案例
通过浏览器的地址栏只能发起GET请求
package com.jt.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@ResponseBody //返回值统一的JSON串
public class UserController {
@RequestMapping("/hello") //该路径必须与用户的访问路径一致,
public String hello(){
return "您好,SpringMVC";
}
/**
* 需求:拦截/say的请求
* 要求返回值:哈哈哈 元旦快乐
* */
@RequestMapping("/say")
public String say (){
return "哈哈哈 元旦快乐";
}
}
6.2.2 传统Servlet的弊端
- 传统的Servlet 一个业务方法,需要编辑一个Servlet. 如果业务复杂则导致Servlet数量增多.
- Servlet的请求的方式 只能支持 get/post请求.
- 通过Servlet获取的参数的类型 都是String,需要手动的进行类型转化.
- 如果编辑servlet 则需要编辑大量的web.xml(8行配置) 文件.
上述的知识都是历史产物. 后期SpringMVC框架将Servlet进行封装
6.2.3 框架间的调用关系
6.3 SpringMVC–参数传递
6.3.1 SpringMVC–简单参数传递
需求: 根据ID查询user数据
URL地址:http://localhost:8080/findUserById?id=1
参数: id=1
返回值:
1. 返回String类型, 查询成功
2. 返回User对象
设计UserController类
/*
* 需求: 根据ID查询user数据
* URL地址:http://localhost:8080/findUserById?id=1
* 参数: id=1
* 返回值:
* 1.返回String类型, 查询成功
* 2.返回User对象
*
* 知识点:
* 1.springMVC为了简化取值过程 可以直接添加参数
* 2.SpringMVC中路径的地址是不能重复的
* 3.如果遇到多个参数,则可以使用对象的方式接收,对象的属性必须添加get/set方法
* SpringMVC如果发现参数的名称与对象的属性名称一致时,则自动调用set方法完成赋值
* */
@RequestMapping("/findUserById")
public String findUserById(int id){
System.out.println("获取用户id值:" + id);
return "查询成功!!"+id;
}
6.3.2 SpringMVC–对象方式接收参数
需求:
多个参数传递问题
URL地址:http://localhost:8080/findUser?id=1&name=张三&age=18
参数:id=1&name=张三&age=18
返回值:String类型 查询成功
封装实体对象(User类)
package com.jt.pojo;
import java.io.Serializable;
//注意事项:1.必须使用包装类型 2.必须添加get/set方法 3.必须序列化
public class User implements Serializable {
private Integer id; //默认值为null 基本数据类型int 默认值0
private String name;
private Integer age;
private String sex;
//补get和set的方法
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
//public String getXly(){
// return "小鲤鱼";
//}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
UserController 接收数据
/*
* 多个参数传递问题
* URL地址:http://localhost:8080/findUser?id=1&name=张三&age=18
* 参数:id=1&name=张三&age=18
* 返回值:String类型 查询成功
* */
@RequestMapping("/findUser")
public String finUser(User user){
System.out.println(user.toString());
return "查询成功";
}
6.4 JSON
6.4.1 概述
JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)
JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。是xml的终结者,成为主流开发方式(ajax异步请求,json返回)。
JSON基于两种结构:
“名称/值”对的集合
(A collection of name/value pairs)。不同的编程语言中,它被理解为对象(object),纪录(record),结构
(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。值的有序列表
(An ordered list of values)。在大部分语言中,它被实现为数组(array),矢量(vector),列表(list),序列(sequence)。
6.4.2 JSON格式
6.4.2.1 JSON数据
var a =' "firstName" : "John" '
6.4.2.2 JSON对象
对象(object) 是一个无序的“‘名称/值’对”集合。一个对象以“{”(左括号)开始,“}”(右括号)结束。每个“名称”后跟一个“:”(冒号);“‘名称/值’ 对”之间使用“,”(逗号)分隔。
{ "firstName":"John" , "lastName":"Doe" }
6.4.2.3 JSON数组
数组(array) 是值(value)的有序集合。一个数组以“[”(左中括号)开始,“]”(右中括号)结束。值之间使用“,”(逗号)分隔。
[{ "firstName":"Bill" , "lastName":"Gates" },{ "firstName":"George" , "lastName":"Bush" }]
6.4.2.4 嵌套格式
值(value) 可以是双引号括起来的字符串(string)、数值(number)、true、false、 null、对象(object)或者数组(array)。这些结构可以嵌套。
[1,2,true,false,{"id":100,"name":"tomcat"},[{arrays: [[],[],[]]},5,6]]
6.5 SpringMVC 用法学习
6.5.1 关于SpringMVC 对象返回说明
/*
要求:返回User对象
URL:http://localhost:8080/getUserById?id=1002
返回值:User对象
知识点:对象转化为JSON串时,调用对象的get方法(getXly)获取属性(xly)和属性值(返回值)
获取getXxx(),之后去除get 然后首字母小写,当做key 返回值当做value
*/
@RequestMapping("/getUserById")
public User getUserById(int id){
User user = new User();
user.setId(id);
user.setName("测试,滴滴滴滴");
user.setAge(18);
user.setSex("男");
return user;
}
6.5.2 关于SpringMVC 同名提交问题
/*
SpringMVC动态参数接收 同名提交问题 多个参数使用逗号(,)分割
爱好:□ 打篮球 □踢足球 □乒乓球
URL:http://localhost:8080/hobby?names=打篮球,踢足球,乒乓球
返回值:返回爱好的list集合
知识点:如果你SpringMVC遇到了同名提交参数,并且中间用逗号分割,则可以自动转化为数组
*/
@RequestMapping("/hobby")
public List<String> hobby(String[] names){
//数组转化为list集合
return Arrays.asList(names);
}
6.6 RestFul结构(难点)
6.6.1 传入GET请求业务说明
需求说明: 向后端服务器传递多个参数, name=tomcat, age=18,sex=男
URL地址: http://localhost:8080/getUser?name=tomcat&age=18&sex=男
该方法能否优化? 能否降低字节数量
6.6.2 RestFul参数拼接
语法: 省略key,将value使用/的方式进行分隔.并且位置固定.
URL地址: http://localhost:8080/getUser/tomcat/18/男
6.6.3 RestFul参数接收
/**
* 需求: 使用restFul结构接收参数
* URL: http://localhost:8080/user/tomcat/18/男
* 参数: name/age/sex
* 返回值: User对象
* 难点知识: 如果获取url路径中的参数!!!
* restFul参数接收的语法:
* 1.参数使用{}进行包裹,并且指定属性名称
* 2.使用注解@PathVariable进行参数接收
* 3.restFul要求请求的路径不能出现动词
* 优化: 如果restFul的属性名称与对象的属性名称一致,则可以利用对象接收
*/
@RequestMapping("/user/{name}/{age}/{sex}")
public User getUser(User user){
return user;
}
/* public User getUser(@PathVariable String name,
@PathVariable int age,
@PathVariable String sex){
User user = new User();
user.setName(name);
user.setAge(age);
user.setSex(sex);
return user;
}*/
6.6.4 RestFul请求路径的通用写法
例子:
- http://localhost:8080/getUserById?id=100
- http://localhost:8080/deleteUserById?id=100
- http://localhost:8080/updateUserById?id=100&name=tomcat
- http://localhost:8080/insertUser POST 提交
问题: 上述的操作 意图特别的明显. 对后期操作会带来不安全的风险.
优化: restFul风格的请求名称的要求,不能出现动词
- http://localhost:8080/userById?id=100 类型:
GET 查询业务逻辑
- http://localhost:8080/userById?id=100 类型:
Delete 删除业务逻辑
- http://localhost:8080/userById?id=100&name=tomcat 类型:
PUT 更新业务逻辑
- http://localhost:8080/user 类型:
POST 新增业务逻辑
//@RequestMapping(value = "/user/{name}/{age}/{sex}",method = RequestMethod.GET)
//@GetMapping("/user/{name}/{age}/{sex}") //方法只支持请求类型
//@DeleteMapping("/user/{name}/{age}/{sex}") //方法只支持删除请求
//@PutMapping("/user/{name}/{age}/{sex}") //方法只支持更新请求
@PostMapping("/user/{name}/{age}/{sex}") //方法只支持新增请求
public User getUser(User user){
return user;
}
6.6.5 请求的类型
请求类型一共8种, 常用的4种 GET/POST/PUT/DELETE
七. 前后端调用
7.1 Axios-post请求_入门案例
7.1.1 前端JS
//1.指定请求的前缀
axios.defaults.baseURL="http://localhost:8080/axios"
/**
* 2. 需求: 一般业务新增时,采用post提交
* URL: http://localhost:8080/axios/saveUser
* axios.post(url地址,js对象)
* .then(回调函数!!!)
*/
let user1 = {id: 100,name:"tomcat猫",age:18,sex:"女"}
axios.post("/saveUser",user1)
.then( promise => {
console.log(promise.data)
})
7.1.2 post请求参数说明
说明: axios中的post请求,参数传递的是JSON串
7.1.3 AxiosController 参数接收
/*
业务说明:接收post请求
url地址:http://localhost:8080/axios/saveUser
参数:JSON串
返回值:User对象
知识点:
1.将JSON串转化为对象 @RequestBody
2.将对象转化为JSON串 @ResponseBody
*/
@PostMapping("/saveUser")
public User saveUser(@RequestBody User user) {
return user;
}
7.2 关于常见请求类型语法说明
GET
/DELETE
语法相同
POST
/PUT
语法相同
7.3 put请求
7.3.1 前端JS
/**
* 业务需求: 实现数据的更新操作
* 修改id=3的数据 name="张三" age=18
* URL:http://localhost:8080/axios/updateUser
* 类型: put
* 写法1: 利用对象传参提交数据
* 写法2: 利用restFul结构实现
*/
let user2 = {id:3, name:"张三", age:18}
axios.put("/updateUser",user2)
.then(promise => {
console.log(promise.data)
})
let url3=`http://localhost:8080/axios/user/${user2.name}/${user2.age}/${user2.id}`
axios.put(url3)
.then(promise => {
console.log(promise.data)
})
7.3.2 AxiosController类接收参数
/*
业务:完成修改操作
类型:put
url地址:http://localhost:8080/axios/updateUser
参数:JSON串
返回值:User对象
*/
@PutMapping("/updateUser")
public User updataUser(@RequestBody User user) {
return user;
}
/*
需求:restFul结构
url地址:http://localhost:8080/axios/user/${user2.name}/${user2.age}/${user2.id}
请求类型:put
返回值:User对象
* */
@PutMapping("/user/{name}/{age}/{id}")
public User user(User user) {
return user;
}
7.4 关于跨域说明
http的默认端口是80端口
https的默认端口号 443
7.4.1 同源策略
要素1: 浏览器的访问地址
要素2: Ajax的请求的网址
策略说明: 如果上述的要素同时满足协议://域名:端口号
都相同的情况,则称之为满足同源策略.可以进行正确的调用. 也称之为 “同域访问”.
7.4.2 什么是跨域
违反了同源策略就叫跨域访问
7.4.3 案例
案例1:
- 浏览器地址: http://www.jd.com/xx/xx
- ajax地址: https://www.jd.com/yy/yy 跨域: 协议不同
案例2:
- 浏览器地址: http://www.jd.com/xx/xx
- ajax地址: http://www.jd.com/yy/yy 同域请求
案例3:
- 浏览器地址: http://www.jd.com:80/xx/xx
- ajax地址: http://www.jd.com/yy/yy 同域请求!!!
案例4:
前提: 域名与IP对应
- 浏览器地址: http://www.jd.com/xx/xx
- ajax地址: http://8.8.8.8/yy/yy 跨域请求! 域名不一致
案例5:
- 浏览器地址: https://www.jd.com/xx/xx
- ajax地址: https://www.jd.com:443/yy/yy 同域请求
https的默认端口号 443
案例6:
- 浏览器地址: http://www.jd.com/xx/yy/xx
- ajax地址: http://miaoshi.jd.com/yy/yy 跨域请求 域名不同