Spring:是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架。
Spring Boot:快速开发的脚手架,在Spring的基础上进一步简化配置和开发。
Spring Boot是一个在Spring的基础上搭建的一个全新微框架,目的是简化Spring的搭建和开发过程,是原有Spring的一个扩展。
Spring MVC是Spring整合的框架之一,而Spring相当于中央引擎。
spring-boot-starter-包名,一个包顶原有的spring很多个包,简化了包的收集和导入。
启动注解@SpringBootApplication点进去,有个自动配置注解@EnableAutoConfiguration,再点进去有个@Import注解。主启动类被这个注解修饰,那么SpringApplication.run(…)的内部就会执行selectImports()方法,寻找 META-INF/spring.factories文件,里面写有所有配置类的路径,根据路径将自动配置类加载到Spring IOC容器中。
a、application.properties:
按照约定的写法在全局配置文件application.properties中配置参数或开关。(全局配置文件application.properties与@ConfigurationProperties修饰的类绑定。)
b、yml映射文件:
更方便,修改SpringBoot默认参数,如端口号。
JavaConfig 自定义配置类,纯注解,无需xml文件。
最方便的是用IDEA创建:
1.创建一个新项目,选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现。
2.选择初始化的组件,初学者选择WEB即可
3.选择项目路径,创建一个空文件夹springboot-01-helloworld,选择这个文件夹。
4.运行启动类(@SpringBootApplication注解下的类)
IOC就是控制反转的意思,是一种设计思想,意思是把创建对象的控制权交给外部环境(IOC容器/spring框架),达到解耦的目的。
比如,Service层A接口有个A类,Dao层B接口有个B1类,A类想使用B1类的功能。
(1)那么在不使用IOC编程的情况下:
必须由程序员在A类内部主动new一个B1类的对象来使用,当业务有了新的逻辑,Dao层的B1类需要更换为B2类,那么还需要在A类当中重新new一个B2类对象。在这种情况下,Dao层代码变更影响了Service层的代码,这个就叫做程序间的耦合。
(2)有了IoC容器后:
不需要new,只需要A类中声明一个B接口类型的对象,通过IOC容器注入的方式创建B1对象(比如用@Repository注解修饰B1类+用@autoWired注解修饰A类)。新业务逻辑产生后,只需要把新B2对象注入即可(比如用@Repository+@primary注解修饰B2类)。此时Dao层代码变动没有改变Service层的代码,所以达到了解耦的目的。
依赖注入:先装配Bean到IOC容器中,然后从IOC容器中获取Bean。有以下的组合搭配:
(1)注解声明Bean
@Component
(2)配置类
@Configuration注解+@Bean注解+配置类,替代xml文件。
每写一个@Bean,就相当于之前我们写的一个
(3)Bean的获取
a、使用AnnotationConfigApplicationContext,传入配置类的class对象获取上下文,然后getBean(方法名)。
b、或者@AutoWired自动注入
Spring 中的 IoC 的实现原理就是工厂模式加反射机制。(通过class对象(反射对象)来获取Bean)
当我们要写一个类时,这个类中很多方法的相同位置出现了重复的代码,比如“打印日志、事务控制、权限验证”等等,这部分重复的代码,统称为 “横切冗余代码”,横切冗余代码的存在,造成了程序代码臃肿,不变维护。
AOP叫做面向切面编程,就是解决项目中切面代码冗余的。AOP编程就将切面逻辑代码和业务逻辑代码分离(比如SpringBoot使用@Aspect声明切面类,并用@Before、@After注解声明要注入哪些类的哪些方法,一个切面类织入多个目标类的方式),这样就降低横切逻辑代码的重复编写、也易于维护。
SpringBoot使用注解实现AOP:
(1)目标类:
(2)编写一个注解实现的切面类,注解@Aspect声明切面,然后用@Before、@After等注解注入到类的所有方法前。
(3)注册切面类的bean,并增加注解支持(或者用配置类也可以)
Spring AOP是基于动态代理的原理来实现的。
默认使用jdk动态代理织入增强,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
目标类(业务),代理类(业务+切面)
(静态代理的时候,代理类是写好的,不是动态生成的。)
相对于静态来说,动态代理的代理类是动态生成的,不是直接写好的。动态代理类可以生成一类业务相同的代理实例,不用再去一个个的手写。
a、JDK动态代理(基于接口)(基于反射实现):
步骤:
1.创建一个实现接口InvocationHandler的类,这个类时用于生成代理类的类。在这个类中private target; setTarget(); 用于传入目标类。
2.它必须重写invoke()方法,在invoke方法中插入横切逻辑代码,再运用反射method.invoke一下。
3.用Proxy类的静态方法newProxyInstance()获取代理类,写在getProxy()方法里。
4.最后使用的时候,用这个类的对象,setTarget()方法传入目标类,通过getProxy()动态获取代理对象。当代理对象调用目标对象方法比如rent()时,会自动跳转到的关联的invoke方法进行调用。从而动态地将横切代码和业务代码编织在一起。
代理角色类:
//用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object rent;
public void setRent(Host rent){
this.rent=rent;
}
//代理类
public Object getProxy(){
//1.代理类加载器 2.被代理类的接口 3.代理类
//生成代理类实例
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
@Override
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
Object result=method.invoke(rent,args);
return result;
}
public void seeHouse(){
System.out.println("代理类带你看房子!");
}
}
调用:
public class Client {
public static void main(String[] args) {
//真实角色
Host host=new Host();
//代理角色:现在没有
ProxyInvocationHandler pih=new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象!
pih.setRent(host);
Rent proxy= (Rent) pih.getProxy();
proxy.rent();
}
}
b、cglib动态代理(基于类,继承)。
原理:继承;
适用于:没有实现接口的类;
不适用于:final修饰的类。
单例模式:从bean的设定角度是singleton,只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。
假设有两个Bean,当程序调用Bean A时,Bean A中依赖Bean B,在Bean A中调用Bean B时,Bean B中又依赖了Bean A,这样就形成了循环依赖。
三级缓存解决了Bean之间的循环依赖。
实例化对象时,Spring一级一级向下寻找,找出了前面提到的三级缓存,也就是三个Map集合类:
找到后,一个Bean创建成功,另一个也就成功了。
ps:基于构造函数的注入,如果有循环依赖,Spring是不能够解决的。
主启动注解@SpringBootApplication下面的一个主启动类:SpringbootApplication:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。
我们这里来写个注解让我们的name只能支持Email格式;
使用数据校验,可以保证数据的正确性;
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
安全性的东西。市面上存在比较有名的:Shiro,Spring Security !
@ReponseBody注解:
在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,它的效果等同于通过response对象输出指定格式的数据。
不用的话只会返回ModelAndView对象,前台解析不到。
@Controller
@RequestMapping("/alpha")
public class AlphaController {
@Autowired
private AlphaService alphaService;
//(1)POST请求提交表单,需要写一个提交表单的静态页面student.html,放在static/html目录下
@RequestMapping(path = "/student", method = RequestMethod.POST)
@ResponseBody
public String saveStudent(String name, int age) {
System.out.println(name);
System.out.println(age);
return "success";
}
// (2)响应HTML数据,/teacher是真实URL,/demo/view是thymeleaf的位置
@RequestMapping(path = "/teacher", method = RequestMethod.GET)
public ModelAndView getTeacher() {
ModelAndView mav = new ModelAndView();
mav.addObject("name", "张三");
mav.addObject("age", 30);
mav.setViewName("/demo/view");
return mav;
}
//(3)响应JSON数据(异步请求:当前网页不刷新,但悄悄的访问了服务器)
// Java对象 -> JSON字符串 -> JS对象
@RequestMapping(path = "/emp", method = RequestMethod.GET)
@ResponseBody
public Map getEmp() {
Map emp = new HashMap<>();
emp.put("name", "张三");
emp.put("age", 23);
emp.put("salary", 8000.00);
return emp;
}
}
自己写一个controller就执行了,绿色部分都是spring MVC框架帮我们做的。
1、SpringMVC有个中央处理器DispatcherServlet。
2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
3、 DispatcherServlet调用HandlerAdapter处理器适配器。HandlerAdapter经过适配调用具体的处理器(Controller)。Controller执行完成返回ModelAndView。HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
4、DispatcherServlet将ModelAndView传给ViewReslover视图解析器。ViewReslover解析后返回具体View。DispatcherServlet根据View进行渲染视图,最终实现响应用户。
@RequestMapping注解用于映射url到控制器类或一个特定的处理程序方法。可用于类或方法上。 用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
(1)只注解方法
(2)同时注解类和方法
Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
在spring boot中,一般不用@Repository,通常用@Mapper。
public class User {
private int id;
private String username;
private String password;
private String salt;
private String email;
private int type;
private int status;
private String activationCode;
private String headerUrl;
private Date createTime;
//外加get、set、toString等
}
@Mapper
public interface UserMapper {
User selectById(int id);
User selectByName(String username);
User selectByEmail(String email);
int insertUser(User user);
int updateStatus(int id, int status);
int updateHeader(int id, String headerUrl);
int updatePassword(int id, String password);
}
namespace对应接口名,id对应接口内方法名。
username, password, salt, email, type, status, activation_code, header_url, create_time
id, username, password, salt, email, type, status, activation_code, header_url, create_time
insert into user ( )
values(#{username}, #{password}, #{salt}, #{email}, #{type}, #{status}, #{activationCode}, #{headerUrl}, #{createTime})
update user set status = #{status} where id = #{id}
update user set header_url = #{headerUrl} where id = #{id}
update user set password = #{password} where id = #{id}
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MapperTests {
@Autowired
private UserMapper userMapper;
@Autowired
private DiscussPostMapper discussPostMapper;
@Test
public void testSelectUser() {
User user = userMapper.selectById(101);
System.out.println(user);
user = userMapper.selectByName("liubei");
System.out.println(user);
user = userMapper.selectByEmail("[email protected]");
System.out.println(user);
}
@Test
public void testInsertUser() {
User user = new User();
user.setUsername("test");
user.setPassword("123456");
user.setSalt("abc");
user.setEmail("[email protected]");
user.setHeaderUrl("http://www.nowcoder.com/101.png");
user.setCreateTime(new Date());
int rows = userMapper.insertUser(user);
System.out.println(rows);
System.out.println(user.getId());
}
@Test
public void updateUser() {
int rows = userMapper.updateStatus(150, 1);
System.out.println(rows);
rows = userMapper.updateHeader(150, "http://www.nowcoder.com/102.png");
System.out.println(rows);
rows = userMapper.updatePassword(150, "hello");
System.out.println(rows);
}
@Test
public void testSelectPosts() {
List list = discussPostMapper.selectDiscussPosts(149, 0, 10);
for(DiscussPost post : list) {
System.out.println(post);
}
int rows = discussPostMapper.selectDiscussPostRows(149);
System.out.println(rows);
}
}
接口方法里传入一个参数blog,然后使用blog的属性来在mapper.xml中生成动态SQL。
Spring操作事务有如下两种方法:
我在项目中使用的是声明式事务管理(底层是aop实现的)。
声明式事务有两种(我在项目中用的是基于注解的形式):
(1)基于xml文件:事务标签
(2)基于注解:@Transactional,并设置“隔离级别+事务的传播机制(事务1内调用了事务2时)”
以下示例为Service层调用dao层的mapper,并使用事务:
// REQUIRED: 支持当前事务(外部事务),如果不存在则创建新事务.
// REQUIRES_NEW: 创建一个新事务,并且暂停当前事务(外部事务).
// NESTED: 如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),否则就会和REQUIRED一样.
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public Object save1() {
// 新增用户
User user = new User();
user.setUsername("alpha");
user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
user.setPassword(CommunityUtil.md5("123" + user.getSalt()));
user.setEmail("[email protected]");
user.setHeaderUrl("http://image.nowcoder.com/head/99t.png");
user.setCreateTime(new Date());
userMapper.insertUser(user);
// 新增帖子
DiscussPost post = new DiscussPost();
post.setUserId(user.getId());
post.setTitle("Hello");
post.setContent("新人报道!");
post.setCreateTime(new Date());
discussPostMapper.insertDiscussPost(post);
Integer.valueOf("abc");
return "ok";
}
不改变mapper.xml里的sql语句,而是在service层获取数据并且进行分页实现
RowBounds分页和通过数组方式分页原理差不多,都是一次获取所有符合条件的数据,然后在内存中对大数据进行操作,实现分页效果。只是数组分页需要我们自己去实现分页逻辑,这里更加简化而已。
存在问题:一次性从数据库获取的数据可能会很多,对内存的消耗很大,可能导师性能变差,甚至引发内存溢出。
适用场景:在数据量很大的情况下,建议还是适用拦截器实现分页效果。RowBounds建议在数据量相对较小的情况下使用。
举例: select _ from student ,拦截 sql 后重写为: select t._ from (select \*from student)t limit 0,10
一级缓存:
默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!一级缓存就是一个Map。
二级缓存:
也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。只要开启了二级缓存,在同一个Mapper下就有效。
所有的数据都会先放在一级缓存中,只有当会话提交,或者关闭的时候,才会提交到二级缓冲中!
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存。
要在程序中使用ehcache,先要导包!