目录
其实就是有人写好的代码,将其封装好了,形成一个比较统一的解决方案,称之为框架。我们直接拿来使用其他人已经写好的功能。
发明者:Rod Johnson -- 罗德·约翰逊
Spring是一个控制反转(IOC)和面向切面(AOP)的轻量级框架。
SpringIOC 依赖注入
SpringAop 面向切面
SpringTX 事物
Spring是一个框架集,内部集成了很多的功能,可以根据需求选择对应的子框架使用
综上所述,IOC控制反转的作用主要是为了解决层与层之间的高耦合关系,由直接依赖转换为间接依赖。
去官方进行Spring的下载:spring下载地址。libs-release-local -> org -> springframework -> spring -> 选择版本后右侧有个下载的url,进入后就能下载了
其中可以下载的文件:dist 为jar包等,docs为文档,schema为xsd约束文件
在项目中引入SpringIOC所必要的jar包
在src目录下添加一个配置文件,这里叫做:application.xml
编写测试类进行测试
package com.it.qxkj.test;
import com.it.qxkj.mapper.StudentMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringIoc_01 {
public static void main(String[] args) {
// 直接依赖
//StudentMapper studentMapper = new StudentMapper();
// 间接依赖
ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
StudentMapper studentMapper = (StudentMapper) ac.getBean("studentMapper");
// 这里是随便定义的一个方法,方法中就是一个System.out.println()语句
studentMapper.show();
}
}
问题引入:通过如上的小案例可以看出,虽然成功的将直接依赖变成了间接依赖,但是如果创建的对象中有属性需要初始化呢?
问题解决方案:通过如下三种方式
无参构造器创建对象
<!-- spring容器默认使用无参构造的方式创建对象
id : 从spring容器(Map)中获取到当前对象的key
class:需要创建的对象的全限定类名
scope:默认为singleton,在Spring容器加载时就创建(只有一个对象)
prototype :在使用getBean获取该对象时才创建(会创建多个对象)
-->
<bean id="stu1" class="com.it.qxkj.pojo.Student" scope="prototype"/>
有参构造器创建对象
<!-- 有参构造
index : 形式参数的下标位置,从0开始
name: 属性名
type:属性的全限定类名
value: 需要赋的值
ref:对当前spring容器的引用
-->
<bean id="stu2" class="com.it.qxkj.pojo.Student">
<constructor-arg index="0" name="name" type="java.lang.String" value="zhangsan"/>
<constructor-arg index="1" name="age" type="int" value="15"/>
</bean>
<bean id="stu3" class="com.it.qxkj.pojo.Student">
<property name="name" value="张三"/>
<property name="age" value="18"/>
</bean>
动态工厂(非静态方法创建对象)
<!-- 创建一个动态工厂对象 -->
<bean id="actFactory" class="com.it.qxkj.factory.ActStudentFactory"/>
<!-- 动态工厂创建对象 -->
<bean id="stu4" factory-bean="actFactory" factory-method="getStudent" />
静态工厂(静态方法创建对象)
如上,我们已经明白了Spring如何帮我们创建对象,但是有另一个问题:
创建出来的对象如果有其他对象的引用怎么办?这个时候使用构造器、set属性注入的方式的value属性就不能满足需求了,因为value是一个常量值。
解决方案:使用ref属性,引入一个Spring容器中的对象
1、创建一个Teacher类,里面可以什么都没有,也可以自定义一点属性
2、给Student对象增加一个引用类型为Teacher的属性(顺便加上set方法)
<bean id="teacher1" class="com.it.qxkj.pojo.Teacher" />
<bean id="stu1" class="com.it.qxkj.pojo.Student">
<property name="name" value="张三"/>
<property name="age" value="18"/>
<property name="teacher" ref="teacher1"/>
</bean>
create table t_user(
id int(11) primary key auto_increment,
uname varchar(32) not null,
pwd varchar(32) not null
);
insert into t_user values(default,'zhangsan','111111'),(default,'lisi','111111');
在项目中引入Mybatis的jar包、Springjar包、gson、连接数据库的驱动等…
创建一个login.jsp页面编写登录表单代码,并设置web.xml欢迎页为login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
${requestScope.errorMsg}
public class User implements Serializable {
private Integer id;
private String uname;
private String pwd;
// 省略get、set、toString等方法
}
package com.it.controller;
import com.it.pojo.User;
import com.it.service.LoginService;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author dengqixing
* @date 2021/7/22
*/
@WebServlet(urlPatterns = "/starlet/LoginServlet", loadOnStartup = 1)
public class LoginController extends HttpServlet {
private ApplicationContext ac;
private LoginService loginService;
@Override
public void init() throws ServletException {
// 由于web.xml中配置了监听器,在项目启动时就将Spring容器加载进ServletContext作用域中了,所以这里可以直接获取到
ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
loginService = (LoginService) ac.getBean("loginService");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1、处理编码
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
// 2、接收数据
String uname = req.getParameter("uname");
String pwd = req.getParameter("pwd");
// 3、处理业务
User user = loginService.login(uname, pwd);
// 4、返回结果
if(user != null) {
req.getSession().setAttribute("user",user);
resp.sendRedirect(req.getContextPath() + "/main.jsp");
}else {
req.setAttribute("errorMsg","登录失败,请重试");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}
}
package com.it.service;
import com.it.mapper.LoginMapper;
import com.it.pojo.User;
/**
* @author dengqixing
* @date 2021/7/22
*/
public class LoginService {
private LoginMapper loginMapper;
public User login(String uname, String pwd) {
User oldUser = new User(uname, pwd);
return loginMapper.selectUser(oldUser);
}
public void setLoginMapper(LoginMapper loginMapper) {
this.loginMapper = loginMapper;
}
}
package com.it.mapper;
import com.it.pojo.User;
import org.apache.ibatis.annotations.Select;
/**
* @author dengqixing
* @date 2021/7/22
*/
public interface LoginMapper {
@Select("select * from t_user where uname = #{uname} and pwd = #{pwd}")
User selectUser(User user);
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 引入配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置SqlSessionFactoryBean,放入数据源,其实就是一个SqlSessionFactory -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置Mapper扫描,加所有扫描到的Mapper都添加进Spring容器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.it.mapper"/>
<!-- 该属性被弃用,因为有时会出现加载MyBatis时,配置文件还没有被加载进来,导致数据源不存在。出现报错
<property name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
-->
<!-- 改用了sqlSessionFactoryBeanName,传入容器的值,然后在spring初始化结束后用该value值从Spring容器中获取对象-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
</bean>
<bean id="loginService" class="com.it.service.LoginService">
<property name="loginMapper" ref="loginMapper"/>
</bean>
</beans>
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/demo1?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置日志 -->
<settings>
<!-- 指定哪个日志实现,具体取值可查看帮助文档,会自动在类路径下寻找log4j.properties配置文件 -->
<setting name="logImpl" value="LOG4J"></setting>
</settings>
<typeAliases>
<package name="com.it.pojo"/>
</typeAliases>
</configuration>
## 全局日志输出,输出error级别的,输出 stdout,logfile(这2个名字是自己定义的)
log4j.rootLogger = error,stdout,logfile
# 指定单个类输出日志,这里指定通过xml文件动态生成的代理类,这样可以打印sql语句
# 可以理解为 com.it.mapper.*,会打印该命名空间下所有类的日志
log4j.logger.com.it.mapper=debug
### 输出信息到控制台
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %m%n
### 文件日志,只记录error日志到 D盘下的error.log文件
log4j.appender.logfile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.File =D://error.log
log4j.appender.logfile.Append = true
# 指定日志消息输出的最低层次
log4j.appender.logfile.Threshold = ERROR
log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<!-- 这个name属性值会被ContextLoaderListener监听器读取,必须固定 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.xml</param-value>
</context-param>
<!--配置该监听器,将Spring容器存入到Application作用域中,也就是ServletContext-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>
问题引出:虽然通过DI依赖注入可以完成引用类型的注入,但是一旦需要依赖注入的属性不断增加,男刀每一次都需要手动进行依赖注入吗?
解决方案:Spring自动注入
<bean id="stu1" class="com.it.qxkj.pojo.Student" autowire="byName" />
<bean id="stu1" class="com.it.qxkj.pojo.Student" autowire="byType" />
<bean id="stu1" class="com.it.qxkj.pojo.Student" autowire="default">
<bean id="stu1" class="com.it.qxkj.pojo.Student" autowire="no">
真实对象:要进行功能扩展的对象,相当于A对象
代理对象:完成了扩能扩展的对象,相当于B对象
切点:需要被扩展的方法称之为切点
前置通知方法:在切点之前执行的方法
后置通知方法:在切点后执行的方法
环绕通知:环绕着切点进行执行的方法,在前置方法之后,在后置方法之前
异常通知:在切点方法出现了异常时执行的方法
切面:由前置通知 + 切点 + 后置通知形成的横向执行的面
织入:由前置通知 + 切点 + 后置通知形成一个切面的过程成为织入
定义一个B类,来实现C接口,在B的实现方法中,创建A的对象
class B implements C {
@Override
public String login() {
// 扩展前的代码
A a = new A();
a.login();
// 扩展后的代码
}
}
(重要),在获取对象的时候不直接获取A对象,而是获取C接口,通过多态的特性来获取到B
这个时候C与B的依赖关系可以使用SpringIOC来完成解耦。
package com.it.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyBefore implements MethodBeforeAdvice {
/**
*
* @param method 当前切点的方法对象
* @param objects 当前执行方法的参数
* @param o 真实对象,相当于new A();
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置通知执行了呢");
}
}
package com.it.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class MyAfter implements AfterReturningAdvice {
/**
*
* @param o 切点方法的返回值
* @param method 切点方法对象
* @param objects 方法参数
* @param o1 真实对象
* @throws Throwable
*/
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("后置通知执行了");
}
}
package com.it.advice;
import org.springframework.aop.ThrowsAdvice;
public class MyThrow implements ThrowsAdvice {
/**
* 该方法的格式固定,请务必这样书写
* @param ex 异常对象
* @throws Throwable
*/
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("我是异常通知");
}
}
package com.it.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
public class MyRound implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
// 参数获取
// 真实对象
Object aThis = methodInvocation.getThis();
// 切点方法
Method method = methodInvocation.getMethod();
// 方法参数
Object[] arguments = methodInvocation.getArguments();
// 执行切点方法
method.invoke(aThis,arguments);
// 1、前置通知
System.out.println("我是环绕通知切点方法执行之前");
// 2、放行
Object proceed = null;
try {
//该方法会返回切点方法的返回值
proceed = methodInvocation.proceed();
}catch (Throwable e) {
// 3、异常通知
System.out.println("切点方法异常了:" + e.getMessage());
}
// 4、后置通知
System.out.println("我是环绕通知切点方法执行之后");
return proceed;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--需要被代理的对象-->
<bean id="a" class="com.it.utils.A"/>
<!-- 前置通知 -->
<bean id="myBefore" class="com.it.advice.MyBefore"/>
<!-- 后置通知 -->
<bean id="myAfter" class="com.it.advice.MyAfter"/>
<!-- 异常通知 -->
<bean id="myThrow" class="com.it.advice.MyThrow"/>
<!-- 环绕通知 -->
<bean id="myRound" class="com.it.advice.MyRound"/>
<aop:config>
<!-- 切入点表达式 -->
<aop:pointcut id="p1" expression="execution(* com.it.utils.A.*(..))"/>
<!-- 配置通知,会根据通知bean实现的接口来判断是何种通知 -->
<aop:advisor advice-ref="myBefore" pointcut-ref="p1"/>
<aop:advisor advice-ref="myAfter" pointcut-ref="p1"/>
<aop:advisor advice-ref="myThrow" pointcut-ref="p1"/>
<aop:advisor advice-ref="myRound" pointcut-ref="p1"/>
</aop:config>
</beans>
package com.it.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAdvice {
public void myBefore(JoinPoint joinPoint){
joinPoint.getTarget();// 真实对象
joinPoint.getThis(); // 代理对象
joinPoint.getSignature(); // 切点方法的数据
System.out.println("前置通知");
}
public void myAfter(JoinPoint joinPoint, Object returnVal) {
System.out.println("切点方法的返回值:" + returnVal);
System.out.println("后置通知");
}
public void myRound(ProceedingJoinPoint pjp) throws Throwable {
// 放行,该方法会返回切点方法的返回值
pjp.proceed();
}
public void myThrow(Exception ex) {
System.out.println("异常通知:" + ex.getMessage());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--需要被代理的对象-->
<bean id="a" class="com.it.utils.A"/>
<!-- 通知类的Bean -->
<bean id="myAdvice" class="com.it.advice.MyAdvice"/>
<aop:config>
<!-- 切入点表达式 -->
<aop:pointcut id="p1" expression="execution(* com.it.utils.A.*(..))"/>
<!-- 配置通知 -->
<aop:aspect ref="myAdvice">
<aop:before method="myBefore" pointcut-ref="p1"/>
<!-- 相当于finally,异常后仍然会执行 -->
<aop:after method="myAfter" pointcut-ref="p1"/>
<!-- 当切点方法正常执行结束后才会执行,returning必须与通知方法的形式参数相同,该参数为切点方法的返回值 -->
<aop:after-returning method="myAfter" pointcut-ref="p1" returning="returnVal" />
<aop:around method="myRound" pointcut-ref="p1"/>
<aop:after-throwing throwing="ex" method="myThrow" pointcut-ref="p1"/>
</aop:aspect>
</aop:config>
</beans>
区分通知的方式不同
切点表达式的作用范围不同
获取方法参数的方式不同
package com.it.service.impl;
import org.springframework.stereotype.Component;
@Component("testBean")
public class Demo {
public void test() {
System.out.println("我是一个测试注解的test方法 ===");
}
}
package com.it.advice;
import com.it.service.impl.Demo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AnnAdvice {
// 定义一个切入点表达式
@Pointcut("execution(* com.it.service.impl.Demo.test(..))")
private void p1() {
}
;
@Before(value = "p1()")
public void before(JoinPoint joinPoint) {
System.out.println("注解的前置通知");
}
@AfterReturning(value = "p1()", returning = "returnVal")
public void afterReturning(JoinPoint joinPoint, Object returnVal) {
System.out.println("注解的后置通知");
}
@Around(value = "p1()")
public void around(ProceedingJoinPoint pp) throws Throwable {
System.out.println("注解的环绕通知");
pp.proceed();
}
@AfterThrowing(value = "p1()", throwing = "e")
public void afterThrowing(Exception e) {
System.out.println("注解的异常通知" + e);
}
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
Demo demo = (Demo) ac.getBean("testBean");
demo.test();
}
}
<!--扫描注解,将其加入到spring容器中-->
<context:component-scan base-package="com.it"/>
<!--
expose-proxy : 解决切点自调用时通知不生效
proxy-target-class:如果被代理的目标对象至少实现了一个接口,则会使用JDK动态代理,所有实现该目标类实现的接口都将被代理
如果该目标对象没有实现任何接口,则创建CGLIB动态代理。但是可以通过proxy-target-class属性强制指定使用CGLIB代理。
如果指定了proxy-target-class="true"则将强制开启CGLIB动态代理
-->
<aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true" />
将某个功能的调用以及使用的代码封装起来,避免调用方式及使用代码的直接暴露
指的是代理对象、代理方法都由开发者编写
例如:A实现了C接口,现在需要扩展A的test()方法。
我们需要干的事:编写一个代理对象B,在代理对象中编写代理方法(前置、后置等)
指代理对象、代理方法都是通过动态生成的。
返回的代理对象:是实现的接口的子类,需要用接口来接收
编写一个接口
package jdk;
public interface MyInterface {
void show();
}
编写一个需要实现类
package jdk;
public class Student implements MyInterface{
@Override
public void show() {
System.out.println("Student:学生");
}
}
编写一个测试类生成代理对象
package jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
// 创建一个真实对象
Student student = new Student();
/**
* ClassLoader loader 类加载器
* Class<?>[] interfaces 代理对象需要实现的接口
* InvocationHandler h 代理方法的处理
* 必须使用接口来接收,因为生成的代理对象是接口的子类
*/
MyInterface myInterface =
(MyInterface) Proxy.newProxyInstance(student.getClass().getClassLoader()
,student.getClass().getInterfaces()
,new MyHandler(student));myInterface.show();
}
}
class MyHandler implements InvocationHandler{
private Object realObject;
public MyHandler(Object realObject) {
this.realObject = realObject;
}
/**
*
* @param proxy 代理对象
* @param method 代理对象的方法
* @param args 方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk代理 扩展前");// 使用一个真实对象来调用真实方法
Object invoke = method.invoke(realObject, args);
System.out.println("jdk代理 扩展后");
return invoke;
}
}
返回的代理对象:与需要被扩展的类是同一个类型的对象
引入cglib所需的jar包
创建一个需要被扩展的功能类
package cglib;
public class Dog {
public void play() {
System.out.println("Dog 只会跑来跑去,不会玩游戏");
}
}
创建一个Test类使用Cglib生成代理对象
package cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
// Cglib生成代理对象需要用到的类
Enhancer enhancer = new Enhancer();
// 指定生成的代理对象的父类
enhancer.setSuperclass(Dog.class);
// 指定代理方法如何处理
enhancer.setCallback(new MyHandle());
// 生成代理对象
Dog proxyDog = (Dog)enhancer.create();
// 执行代理方法
proxyDog.play();
}
}
class MyHandle implements MethodInterceptor{
/**
*
* @param o 代理对象
* @param method 真实对象的方法
* @param objects 方法参数
* @param methodProxy 代理对象的方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib 扩展前");
// 调用真实方法
Object invoke = methodProxy.invokeSuper(o, objects);
System.out.println("cglib 扩展后");
return invoke;
}
}
问题引出:在同一个业务中进行多个数据库操作。例如在银行转账的例子中,要减少转账人的余额,并且需要增加收款人的余额。这2个操作必须在同一个事物之内(也就是要么一起成功,要么一起失败)
问题解决:在业务方法的执行之前开启事物,如果方法正确执行,则提交事物,否则回滚事物。
未使用mybatais前的事物解决: 获得数据库的连接对象,对事物进行控制
使用mybatis后的事物解决:使用sqlSession对事物进行控制(需要用到ThreadLocal保证一个线程中只有一个SqlSession)
使用Spring后的事物解决:在学了SpringAop后,理解到了代理模式,而事物是否也可以使用代理设计模式实现呢? 答案是可以的,Spring为我们提供了事物管理器,使用SpringAop面向切面编程的技术完成了对事物的控制,我们只需要使用就可以了
事物管理的代码,例如开启事务,事物提交、事物回滚,等等的代码都需要由程序员自行编写。
事物管理由第三方直接提供,由程序员完成组装操作(配置)后直接使用
引入SpringIOC的jar包
SpringAOP的jar包
SpringTx的jar包
mybaits的jar包
数据库连接的jar包
mybaits与spring整合jar
在web.xml中配置ContextLoaderListener监听器和contextConfigLocation全局属性
搭建项目的基本文件夹结构(按照自己的要求)
在src或者已经指定好的类路径下编写spring的配置文件(我这里名字叫application.xml)
配置包扫描的路径
开启Spring对AOP注解的支持
配置DataSource数据源
配置SqlSessionFactoryBean(指定dataSource和、mapper.xml扫描路径、mybatis全局配置文件路径)。作用是为了生产SqlSession
配置MapperScannerConfigurer(指定mapper的路径、指定sqlSessionFactory)作用是为了将Mapper对象扫描并添加到Spring容器
配置事物bean(指定dataSource),DataSourceTransactionManager
配置事物管理的方法,tx:advice,传入事物bean
配置AOP切面aop:config
在类路径下配置一个log4j的日志文件
在类路径下配置mybaits全局配置文件
在类路径下编写jdbc.properties数据库连接配置文件
<!-- 配置spring提供的事物管理bean -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事物管理的方法,指定事物的规则-->
<tx:advice transaction-manager="transactionManager" id="advice">
<!-- 只对该标签配置的方法生效 -->
<tx:attributes>
<!--
propagation:事物的传播行为
isolation:事物的隔离级别
rollback-for:在遇到这些异常时回滚
no-rollback-for:在遇到异常时不回滚
timeout:事物的超时时间,单位为秒,-1为永不超时
read-only:该事物是否只读,查询操作建议设置为true,会增加效率
name:需要进行匹配的方法,*代表通配符
-->
<tx:method
propagation="REQUIRED"
isolation="DEFAULT"
rollback-for="java.lang.ClassNotFoundException,"
no-rollback-for="java.lang.NullPointerException"
timeout="-1"
read-only="true"
name="transfer*"/>
</tx:attributes>
</tx:advice>
<!-- 配置一个aop切面 -->
<aop:config>
<aop:pointcut id="mp" expression="execution( * com.it.service.impl.*.*(..))"/>
<aop:advisor advice-ref="advice" pointcut-ref="mp" />
</aop:config>
<!-- 配置spring提供的事物管理bean -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启注解事务控制 -->
<tx:annotation-driven transaction-manager="transactionManager" />
在需要进行事务控制的类上添加@Transactional注解(该类中的所有方法都会进行事物控制)
在需要进行事务控制的类方法上添加@Transactional注解
由于Spring事务是基于AOP实现的,而对真实对象创建代理对象时,需要拥有真实对象。所以进行事物控制的类必须交给Spring管理(配置bean或者使用对应的注解)
事务的传播行为发生在:一个业务方法中调用另外一个业务方法
REQUIRED (默认值): 如果当前有事务,就在事务中执行,如果当前没有事务,新建一个事务
SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执
MANDATORY:必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务,报错.
REQUIRES_NEW:必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务挂起
NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前事务挂起
NEVER:必须在非事务状态下执行,如果当前没有事务,正常执行,如果当前有事务,报错.
NESTED:必须在事务状态下执行.如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务
作用:在多线程中保证数据库数据完整性
脏读
一个事务(A)读取到另一个事务(B)中未提交的数据,另一个事务中数据可能进行了改变,此时A事务读取的数据可能和数据库中数据是不一致的,此时认为数据是脏数据,读取脏数据过程叫做脏读。
不可重复读
主要针对某行数据的修改操作、两次读取在同一个事务
当事务A第一次读取事务后,事务B对事务A读取的数据进行修改,事务A中再次读取的数据和之前读取的数据不一致,过程不可重复读
幻读
针对新增或者删除操作
事务A按照特定条件查询出结果,事务B新增了一条符合条件的数据.事务A中查询的数据和数据库中的数据不一致的,事务A好像出现了幻觉,这种情况称为幻读
DEFAULT: 默认值,由底层数据库自动判断应该使用什么隔离级别
READ_UNCOMMITTED:可以读取未提交数据,可能出现脏读,不重复读,幻读。(效率最高、不安全)
READ_COMMITTED:只能读取其他事务已提交数据,可以防止脏读,可能出现不可重复读和幻读
REPEATABLE_READ: 读取的数据被添加锁,防止其他事务修改此数据,可以防止不可重复读,脏读,可能出现幻读
SERIALIZABLE: 排队操作,对整个表添加锁.一个事务在操作数据时,另一个事务等待事务操作完成后才能操作这个表(最安全、效率最低)
目前现阶段未使用SpringMVC的开发弊端:
解决方案:
几个小问题
通过Spring的容器的子容器,在子容器中存储所有的逻辑类Controller的逻辑对象,然后Servlet在init初始化的时候一次性从子容器中获取所有的逻辑对象。
如何动态的通过url调用指定的逻辑对象的逻辑方法
通过注解 + 反射的技术
创建一个Web项目
引入SpringMVC的jar包
创建web项目的包结构
在类路径下增加一个spring子容器配置文件
<context:component-scan base-package="com.it" />
<!-- 其实是因为spring也有一些需要加入容器的bean,但是包名太长,所以提供了这个标签-->
<mvc:annotation-driven />
在web.xml中配置SpringMVC这个Servlet
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
编写单元方法
@Controller
public class MyController {
@RequestMapping("aa") // 相当于Servlet的urlPatterns
public String method1(){
System.out.println("我是第一个springMVC处理的逻辑方法");
return "aa";
}
}
问题:在学习了SpringMVC的请求流程后,我们知道SpringMVC所提供的DispatcherServlet是根据请求的url来动态使用反射和注解技术调用的对应的逻辑方法,那么逻辑方法如何获取到请求的参数呢?
解决方案:DispatcherServlet在service方法中处理逻辑时能够获取到Request对象,只要DispatcherServlet将Request对象传入到逻辑单元方法中进行反射执行,那么就可以获取到了。单元方法必须声明形参来进行参数的接收。
@RequestMapping("reqParam1")
public String method2(HttpServletRequest req) {
String name = req.getParameter("name");
System.out.println(name);
return null;
}
本质:
@RequestMapping("reqParam2")
public String method2(String uname, int age) {
System.out.println(uname);
System.out.println(age);
return null;
}
@RequestMapping("reqParam3")
/**
* @RequestParam 有如下几个参数
* name : 指定传递过来的键名
* value:效果同上
* required:是否为必须的,true(默认值)为必须(如果未赋值则会抛出异常),false为非必须
* defaultValue : 如果为传递,给其的默认值
* 注意:required与defaultValue 不要共同使用
*/
public String method3(@RequestParam("name") String uname, int age) {
System.out.println(uname);
System.out.println(age);
return null;
}
实体类
package com.it.entity;
public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
提供单元方法
@RequestMapping("reqParam4")
public String method4(Student student) {
System.out.println(student);
return null;
}
本质:通过获取Student类中的属性,通过属性作为键来进行getParameter获取到数据后存入该Student对象中。
@RequestMapping("reqParam5")
public String method5(String[] hobbys) {
for (String hobby : hobbys) {
System.out.println(hobby);
}
return null;
}
什么是restFul风格?
传统url: localhost:8080/project/req1?name=zhangsan&age=18
restFul:localhost:8080/project/req1/zhangsan/age
为什么要使用restFul风格
原因:因为前台和后台的代码不一定是同一个人写的,所以可能出现形式参数与键名的不一致性。
例如:前台传递参数的键名一旦修改,后台也需要做对应的修改,麻烦。
而使用restFul风格则不用指定键名,只是将数据放入到url中
那么如果只是一个url,那么dispatcherServlet也只会查找一个@RequestMapping对应的url地址作为逻辑方法的入口,所以SpringMVC提供了对RestFul风格的请求方式的支持
实现代码
@RequestMapping("reqParam6/{name}/{age}")
/**
* @PathVariable 有如下属性
* name:指定url中占位符的名称
* value:同上
* required: 是否必须(默认为true)
*/
public String method6(@PathVariable String name,@PathVariable("age") Integer myAge) {
System.out.println(name);
System.out.println(myAge);
return null;
}
在使用SpringMVC来进行参数的接收时,最好使用包装类
本质原因:例如单元方法一个形式参数 int age,但是在访问该单元方法时,没有传递该age。
DispatcherServlet在调用逻辑方法时,会通过getParameter("age")来获取值,为该形式参数age赋值。
但是如果没有传递age,那么getParameter就会获取到一个null,然后强转给一个int类型的age,就会报错
get请求
request.setCharacterEncoding();
配置Tomcat服务器配置文件server.xml
或者
post请求
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--设置编码格式-->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<!--设置编码格式的生效范围-->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
问题:由于配置的DispatcherServlet的拦截路径是拦截除jsp结尾的所有请求,所以也会将js、css等等静态资源也给拦截,因此我们需要指定放行规则
解决:在springmvc的配置文件中进行资源放行的规则配置
<!-- 放行静态资源
mapping:相当于浏览器在访问静态资源时的路径地址匹配
location:代表从服务器的部署目录的哪个文件夹中获取资源
-->
<mvc:resources mapping="/js/**" location="/WEB-INF/js/" />
<mvc:resources mapping="/css/**" location="/css/" />
<!-- 放行一整个static文件夹,文件夹中可以放css,js等等,这样就不用配置多个路径了 -->
<mvc:resources mapping="/static/**" location="/static/"/>
注意:
DispatcherServlet处理一个请求的时候,会先查找其对应的单元方法进行处理。如果找不到单元方法才会根据SpringMVC配置的静态资源放行规则查找静态资源。
所以说不要设置某逻辑单元方法的路径与任何一个静态资源的访问url相同。
@RequestMapping("resp1")
public String method7(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 转发
request.getRequestDispatcher("index.jsp").forward(request, response);
// 重定向
response.sendRedirect(request.getContextPath() + "/index.jsp");
// 返回数据
response.getWriter().write("你好呀");
return null;
}
@RequestMapping("resp2")
public String method7(){
System.out.println("即将转发到index.jsp");
// 如果不写forward: 默认就是转发
// return "index.jsp";
return "forward:index.jsp";
}
@RequestMapping("resp3")
public String method8(){
System.out.println("即将重定向到index.jsp");
return "redirect:index.jsp";
}
@RequestMapping("dataPass1")
public String method10(HttpServletRequest request) {
// 存入request作用域
request.setAttribute("myName","codeStar");
// 存入session作用域
request.getSession().setAttribute("myName2","codeStar2");
return "forward:myDataPass.jsp";
}
@RequestMapping("dataPass2")
public String method11(Model model) {
// 存入request作用域
model.addAttribute("myName","codeStar");
return "forward:myDataPass.jsp";
}
我们知道,HttpServletRequest存储的requset作用域中的数据,一旦通过重定向,那么数据就会消失。
可是Model的数据如果重定向,则会将本次请求中的存储的reqeust作用域的对象作为下次请求携带的参数
@RequestMapping("dataPass3")
public String method12(Model model) {
// 存入request作用域
model.addAttribute("myName","codeStar");
return "redirect:myDataPass.jsp";
}
本质:就是也就是用于处理逻辑单元方法返回值的类
该接口拥有两个实现类
使用示例
@RequestMapping("view1")
public View method13(HttpServletRequest request) {
// 请求转发
// return new InternalResourceView("/myView.jsp");
// 重定向
return new RedirectView(request.getContextPath() + "/myView.jsp");
}
出现的原因:我们既需要使用View完成视图的请求转发或者重定向,又需要Model来帮助我们完成数据的传递,所以ModelAndView就出现了。(其实主要还是因为以前直接使用View对象的时候需要new对象比较麻烦)
使用示例
@RequestMapping("view2")
public ModelAndView method14(HttpServletRequest request) {
// 创建ModelAndView对象
ModelAndView mv = new ModelAndView();
// 将数据存储到request作用域
mv.addObject("myName","codeStar");
// 设置请求转发
// mv.setViewName("/myView.jsp");// 默认就是转发
// 设置重定向
mv.setViewName("redirect:/myView.jsp");// 重定向(注意了,会自动加上项目名,不需要request.getContextPath());
return mv;
}
其实只是写法变得简便了,底层使用的还是ModelAndView。
DispatcherServlet在接收到方法的String字符串返回值之后,传递给ModelAndView来帮助进行解析,通过判断是字符串中的forward(不写就是默认)还是redirect,来决定是请求转发还是重定向。
@RequestMapping("view3")
public String method15() {
// 请求转发
// return "forward:/myView.jsp";
// return "/myView.jsp";
// 重定向
return "redirect:/myView.jsp";
}
问题引出:可以看到,Spring为我们提供的视图解析器虽然可以做到请求转发与重定向功能的实现。
但是有点死板,必须要我们写完整的资源路径,如果该资源在层层迭代的一个文件夹中,难道我们每次做资源跳转的时候都要写完整的路径吗,麻烦。
解决:所以Spring给我们提供了一个自定义视图解析器,我们可以通过配置前缀与后缀等参数完成我们自定义视图解析器的配置。
在Springmvc的配置文件中添加如下代码
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 你返回的String字符串的前缀,例如 return ”aa“,那么则会变成/WEB-INF/jsp/aa -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 你返回的String字符串的后缀 -->
<property name="suffix" value=".jsp" />
</bean>
使用示例
@RequestMapping("view4")
public String method16() {
return "view4";
}
问题引出:我们的资源目录下有许许多多的资源,难道我们访问每个资源都需要一个对应的单元方法吗?麻烦。
解决:使用restFul风格的单元方法模糊匹配资源
@RequestMapping("{url}")
public String resourceUrl(@PathVariable String url) {
return url;
}
注意:(以下都是基于浏览器发起的请求能够匹配该公共方法时)
如果配置了该公共方法。浏览器发起的请求会先匹配是否有对应的单元方法,如果没有。则会匹配到该公共方法。
还有一个重要的点: 如果对应的单元方法没有@ResponseBody注解,也没有返回对应的视图资源路径,则还是会走一次该公共方法。
因为WEB-INF目录相当于一个对外不开放的文件夹。只能通过服务器内部的请求转发才能获取到其中的资源。为了安全起见。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<base href="<%=request.getContextPath() + "/"%>">
<script type="text/javascript" src="js/jquery-1.12.3.min.js"></script>
<script type="text/javascript">
function uploadPhoto() {
var file = $("#photo")[0].files[0];
var data = new FormData();
// 后台接收时候,需要以该key:photo为形参名接收
data.append("photo",file);
$.ajax({
type:"post",
url: "regUpload",
//在 ajax 中 contentType 设置为 false 是为了避免 JQuery 对其操作,从而失去分界符,而使服务器不能正常解析文件。
contentType: false,
//默认情况下会将发送的数据序列化以适应默认的内容类型application/x-www-form-urlencoded
//如果想发送不想转换的的信息的时候需要手动将其设置为false
processData: false,
data: data,
dataType: 'json',
success: function (result) {
if (result.state){
alert("上传成功")
// 上传图片的回显
$("#pageImg").attr("src","upload/" + result.url).css("display","");
} else {
alert("上传失败:" + result.msg)
}
}
})
}
</script>
</head>
<body>
<form action="">
<p>选择头像:<input id="photo" type="file">
<input type="button" onclick="uploadPhoto()" value="点击上传">
<img id="pageImg" src="" width="200px" style="display: none">
</p>
<input type="hidden" name="img" id="img">
</form>
</body>
</html>
@RequestMapping("regUpload")
@ResponseBody
public MyResult regUpload(MultipartFile photo, HttpServletRequest request) throws IOException {
// 1、获取文件名
String oldName = photo.getOriginalFilename();
// 2、获得文件后缀
String suffix = oldName.substring(oldName.lastIndexOf("."));
String newName = UUID.randomUUID() + suffix;
// 3、获得文件存储路径
File path = new File(request.getServletContext().getRealPath("/upload"));
// 4、判断文件是否存在
if(!path.exists()){
path.mkdirs();
}
// 5、将文件存在服务器中
photo.transferTo(new File(path, newName));
// 返回友好提示
MyResult myResult = new MyResult();
myResult.setState(true);
myResult.setUrl(newName);
return myResult;
}
<a href="download?fileName=a38733de-2226-426b-8e0c-acef9200d541.png">啦啦啦</a>
@RequestMapping("download")
public void downloadImage(String fileName, HttpServletResponse response, HttpServletRequest request) throws IOException {
// 告诉浏览器,下载该文件,并指定了文件名
response.setHeader("Content-Disposition", "attachment;filename="+fileName);
// 从服务器中读取该文件
File file = new File(request.getServletContext().getRealPath("/upload") + "/" + fileName);
// 将该文件转换为byte数组
byte[] bytes = FileUtils.readFileToByteArray(file);
// 相应给浏览器
response.getOutputStream().write(bytes);
}
之前在介绍过滤器的时候,可以知道过滤器是保护请求的服务器资源,在请求资源之前,如果符合过滤器的拦截范围,则会先执行过滤器。过滤器的执行时机:在Servlet之前。
但是,现在我们使用了SpringMVC,只有一个Servlet,那就是DispatcherServlet,这个时候过滤器虽然能用,但是过滤器的拦截范围不管多小,都会拦截到DispatcherServlet,也就是会拦截所有的DispatcherServlet的请求。
解决方案:
使用过滤器
过滤器的执行时机:Servlet之后,单元方法之前
package com.it.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class InterceptorTestController {
@RequestMapping("inter1")
public void inter1() {
System.out.println("我是inter1单元方法");
}
@RequestMapping("inter2")
public void inter2() {
System.out.println("我是inter2单元方法");
}
@RequestMapping("demo")
public void demo() {
System.out.println("我是demo单元方法");
}
}
package com.it.config;
import com.it.controller.InterceptorTestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
public class MyInterceptor implements HandlerInterceptor {
@Autowired
private InterceptorTestController obj;
/**
* 触发时机:执行单元方法执行
* @param request 请求对象
* @param response 响应对象
* @param handler 实参的类型是HandlerMethod,也就是对应的单元方法,所以可以在这里调用一次单元方法
* 因为单元方法平时都是由DispatcherServlet调用的,在这里自己调用就可以使单元方法调用变得可控
* @return 返回true表示放行, flase表示不放行
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 调用了一次单元方法
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
method.invoke(obj,null);
System.out.println("执行单元方法之前调用");
// 放行
return true;
}
/**
* 执行时机: 在执行单元方法之后, 相应给浏览器页面之前,也就是视图解析器之前
* @param request 请求对象
* @param response 响应对象
* @param handler 同preHandle一致
* @param modelAndView 视图解析器,可以变更传递的数据,也可以变更需要跳转的资源
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("在执行单元方法之后, 资源相应给浏览器之前");
// 可以对数据进行过滤后再重新存入作用域,例如去除敏感词
// 可以重新设置页面的资源跳转地址
}
/**
* 触发时机: 在视图解析器执行完毕,响应给用户资源后执行
* @param request
* @param response
* @param handler
* @param ex 接收此次请求处理过程中出现的异常
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (ex != null) {
System.out.println("本次请求出现的异常: " + ex.getMessage());
}
// 也可以做一些关闭资源的操作
// 一些记录用户信息的操作(在这里进行操作时,不会影响到用户)
}
}
<!-- 配置拦截器 -->
<mvc:interceptors>
<!--声明全局拦截器,表示拦截所有单元方法-->
<!--<bean id="all" class=""></bean>-->
<!-- 配置局部拦截器, 注意,bean标签需要放在最后-->
<mvc:interceptor>
<!-- 拦截范围 -->
<mvc:mapping path="/inter*"/>
<!-- 不拦截的范围 -->
<mvc:exclude-mapping path="/inter2"/>
<bean id="interceptor" class="com.it.config.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
配置在前的拦截器设置变量为 A,
配置在后的拦截器设置为:B
执行顺序为:
之前我们使用传统Servlet处理ajax请求时,将需要转换为json的实体类对象或者List对象使用Gson进行转换,并使用response对象getWriter获得打印流对象来转换后的json字符串响应给前端。
提示:这个时候如果返回一个实体类对象或List等java类对象,会直接将该实体对象转换为json数据
@RequestMapping("regUpload")
@ResponseBody
public MyResult regUpload(MultipartFile photo, HttpServletRequest request) throws IOException {}
@Component 一般用于entity类上,相当于bean标签
@Controller 一般用于控制器上(SpringMVC),功能如上
@Service 一般用于service层上,功能如上
@Repository 一般用于dao层上,功能如上
@Resource 相当于依赖注入,默认使用byName方式,找不到则会使用beType,是jdk提供的注解(可以不用提供set方法)
@Autowired 作用如上,默认使用byType方式依赖注入,是spring提供的注解。(可以不用提供set方法)
@RequestParam 作用在单元方法形参上,用于指定传递参数的键名
@PathVariable 作用在单元方法形参上,用于获取url中的占位符{占位符名}
@RequestMapping("demo/{name}/{age}")
public String demo(@PathVariable String name,@PathVariable("age") String myAge){
return "";
}
@Value 作用于成员属性上,用于进行普通属性注入
手机扫一扫
移动阅读更方便
你可能感兴趣的文章