Spring
Spring的核心组件包含Bean
、Core
、Context
、Expression
而Spring的核心功能主要有两个:IOC
(控制反转)、AOP
(面向切面编程)
IOC
IOC的作用
IOC也叫做控制反转
,是一种设计原则,可以很好的降低代码之间的耦合,常见的使用方式称作依赖注入(DI)
。Spring提供了一个IOC容器,将创建的Java Bean对象交给Spring管理,可以自动所需依赖进行注入。
IOC容器的引入极大的方便了开发时Bean的管理,注册在IOC容器里的Bean可以很方便的拿到Bean对象,只需要使用@Autowrite
注解即可把所依赖的对象注入。
包括第三方的jar包的对象也可以使用IOC容器进行管理,只需要在配置文件,或者配置类中配置即可。
IOC容器的使用
我们在以往写DAO层以及Service层到Web层通常的写法是这样的:
1 2 3 4 5 6 7 8
| public class Service{ Dao dao = new DaoImpl(); public void service(){ dao.method(); } }
|
以上这种通过创建Dao对象的方式叫做正转
,这会使得代码之间的耦合性很高,而通过IOC容器管理Bean对象则能很好的松耦合
配置
我们可以通过xml配置文件来创建Bean(个人并不是很喜欢xml的方式)
bean.xml
1 2 3 4 5 6 7 8 9 10 11
| <context:component-scan base-package="work.wucheng.spring5.aopanno"></context:component-scan>
<bean id="dao" class="work.wucheng.spring.dao.Dao"/>
<bean calss="work.wucheng.spring.service.Service">
<property name="dao" ref="dao"/> </bean>
|
@Configuration
通过注解来进行配置IOC容器则方便的多,不过我们需要将xml以对象的形式来配置
首先需要创建config类来进行Bean管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Configuration @ComponentScan(basePackages = {"work.wucheng.spring"}) public class SpringConfig { @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } }
|
使用
Spring提供了四个注解进行标识Bean对象以在IOC容器中进行注册,四个注解除了名字不一样实际并没有任何区别,只是为了方便阅读。这四个注解分别为:
@Compent
:一般使用在普通类中
@Repository
:一般使用在持久层Dao
@Service
:一般用在Service层
@Controller
:一般使用在控制器层中,在MVC前也可以说是web层
1 2 3 4 5
| @Repository public class Dao{ @Autowired JdbcTemplate jdbcTemplate; }
|
@Autowired
是根据类型进行自动装配,当我们有多个实现同一接口的Bean对象的时候Spring则不知道我们需要哪个对象,这个时候我们则需要另一个注解@Qualifier
来指定对象了
1 2 3 4 5 6
| @Service public class Service{ @Autowired @Qualifier("daoImpl") Dao dao; }
|
还有Java官方提供的一个注解@Resource
,当没有值的时候用法同@Autowired
,当有值的时候等同于@Autowired
+@Qualifier
,但是Spring更推荐我们使用Spring提供的注解
除了可以对所需的依赖进行注入,同样对于普通属性也可以使用@Value(数据)
进行赋值
获取IOC容器的Bean对象
在使用Spring的时候如果我们需要直接获取到IOC容器中的对象可以使用如下方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class TestSpring { @Test public void testService(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); Service service = context.getBean("service", Service.class); } @Test public void testService2(){ ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); Service service = context.getBean("service", Service.class); }
|
简单的IOC的原理
IOC容器的原理本质上是工厂模式
+反射
进行实现的
将需要需要进行控制反转的对象和需要注入的Bean对象放入一个工厂类中
通过反射获取到类对象,再将Bean对象赋值给类并创建对象,此时就完成了控制反转
以下为粗略的的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Factory{ public <T> T factory(Class<T> clazz,Object object,String fieldName) throws NoSuchFieldException, IllegalAccessException, InstantiationException { T t = clazz.newInstance(); Field declaredField = clazz.getDeclaredField(fieldName); declaredField.setAccessible(true); declaredField.set(t,object); return t; } }
|
Spring中有一个BeanFactory
类是IOC容器的基本实现接口,而上面获取IOC容器中Bean对象的ApplicationContext
是BeanFactory的子接口
以下是通过BeanFactory接口的简单实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class MyBean implements FactoryBean<Course> { @Override public Bean getObject() throws Exception { Bean bean = new Bean(); bean.setCname("abc"); return bean; } @Override public Class<?> getObjectType() { return null; } @Override public boolean isSingleton() { return FactoryBean.super.isSingleton(); } }
|
AOP
AOP的作用
AOP称作面向切面编程
,和面向对象一样是一种编程思想,它可以不改变原有代码的情况下进行方法增强。Spring提供了注解@AspectJ
进行AOP的实现,被增强的类和增强类需要IOC容器中进行注册。
AOP的最大好处就是,不用修改原有的代码也不用担心其修改会导致奇奇怪怪的错误,它只作增强,其效果相当于继承,如下:
1 2 3 4 5 6 7 8 9 10
| public class Object2 extends Object1{ @Override public String method(String str){ StringBuffer buffer = new StringBuffer("before"); String origin = super(str); buffer.append("after"); return buffer.toString(); } }
|
如上演示,我们此时就获得了一个Object1的增强方法Object2,但是如此来进行方法的增强那么意味着依赖Object1的方法全部都要替换成Object2。
但是AOP则更为强大,它并不需要改变Object1被依赖的代码就能达到这样的效果。
AOP的一些概念
- 连接点:指哪些类可以被增强
- 切入点:被增强的方法
- 通知:实际被增强的部分被称作通知,而通知也分为多种类型
- 切面:把通知应用到切入点的过程
AspectJ的使用
AspectJ并不是Spring核心的一部分,它是独立于AOP框架 的,但是一般都会一起使用
它的配置同样分为配置类和配置文件两种方式
xml
1 2 3 4 5
| <context:component-scan base-package="work.wucheng.spring"/>
<aop:aspectj-autoproxy/>
|
@Configuration
1 2 3 4 5
| @Configuration @ComponentScan(basePackages = {"work.wucheng.spring"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class ConfigAop { }
|
当配置完成后我们就可以在增强类中使用@AspectJ
注解了
但是使用前需要了解切入点表达式
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名] ([参数列表]))
权限修饰符可以省略
可以使用【*】通配符表示任意返回值类型
也可以使用【*..】表示当前包以及子包
参数列表可以使用【..】表示任意类型参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @Component @Aspect public class UserProxy { @Before("execution(* work.wucheng.spring.aopanno.User.add(..))") public void befor(){ System.out.println("before......"); } @After("execution(* work.wucheng.spring.aopanno.User.add(..))") public void after(){ System.out.println("after......"); } @AfterReturning("execution(* work.wucheng.spring.aopanno.User.add(..))") public void afterReturning(){ System.out.println("afterReturning......"); } @AfterThrowing("execution(* work.wucheng.spring.aopanno.User.add(..))") public void afterThrowing(){ System.out.println("afterThrowing......"); } @Around("execution(* work.wucheng.spring.aopanno.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕之前"); proceedingJoinPoint.proceed(); System.out.println("环绕之后"); } }
|
显然如果我们需要对一个方法进行增强多次,那么这样写就会显得非常麻烦了
我们可以抽取相同切入点进行简化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Component @Aspect public class UserProxy {
@Pointcut(value = "execution(* work.wucheng.spring5.aopanno.User.add(..))") public void pointdemo(){ }
@Before(value = "pointdemo()") public void befor(){ System.out.println("before......"); } }
|
但是如果我们需要多次增强,不止一个增强对象并且对运行顺序有要求,则需要用到@Order
注解了
@Order
注解标识在类的上方,数值为数字来表示优先级,数字越小优先级越高,@Before越先执行,@After越后执行
简单的AOP原理
AOP的本质其实是动态代理,而代理分为有接口代理和无接口的代理
以下是有接口的动态代理实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class JDKProxy { public static void main(String[] args) { Class[] interfaces = {UserDao.class}; UserDaoImpl userDao = new UserDaoImpl(); UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); int result = dao.add(1, 2); System.out.println("result:"+result); } }
class UserDaoProxy implements InvocationHandler{
private Object object; public UserDaoProxy(Object obj){ this.object = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法之前执行。。。"+method.getName()+":传递的参数"+ Arrays.toString(args)); Object res = method.invoke(object, args); System.out.println("方法之后执行。。。"+object); return res; } }
|
Spring MVC
@RequestMapping
作用
RequestMapping也叫做请求映射,当类使用注解@Controller
标记为控制器方法后,SpringMVC扫描到该类会将方法使用了注解@RequestMapping
的方法作为请求映射,当在游览器访问此方法时会调用此方法
@RequestMapping的使用
@RequsetMapping
可以使用在类上,也可以使用在方法上
当使用在类上的时候,请求该类中方法的路径前缀都需要带上该类上@RequestMapping的值
例如:
1 2 3 4 5 6 7 8 9 10 11
| @Controller @RequestMapping("/test") public class RequestMappingController {
@RequestMapping("/testRequestMapping") public String testRequestMapping(){ return "success"; }
}
|
在访问该方法需要在游览器中访问本机地址/工程名/test/testRequestMapping
才能调用此方法
在该注解中同样拥有其他参数
例如在参数中指定method即可限定访问的方法
1 2 3 4 5 6 7
| @RequestMapping( value = {"/testRequestMapping", "/test"}, method = {RequestMethod.GET, RequestMethod.POST} ) public String testRequestMapping(){ return "success"; }
|
例如上方的method限定了只有GET请求和POST的请求才能够调用此方法,当然SpringMVC也提供了例如@GetMapping
和@PostMapping
注解来省略掉method参数来让我们快速的使用
其他参数
params参数
当在RequestMapping中使用了params参数和method类似,也是限定了请求,但是这个参数要求的是请求中必须携带参数或者要求请求携带的参数必须为指定的值
例如:
1 2 3 4 5 6 7
| @RequestMapping( value = "/testRequestMapping" ,param = {"username","password!=123456"} ) public String testRequestMapping(){ return "success"; }
|
这个方法就要求了参数中必须携带username
参数且password
参数不能为123456
其他参数一般更为少见,需要时可以查阅资料
在SpringMVC中的参数获取
在以往的Servlet中,我们获取参数往往都需要调用方法request.getParameter("")
来获取携带的请求参数,而SpringMVC则帮助我们做了简化,我们只需要在方法中传入同名字的参数即可
例如:
1 2 3 4 5
| @RequestMapping("/testParam") public String testParam(String username,String password){ System.out.println("username:"+username+",password:"+password); return "success"; }
|
通过以上方法即可方便的获取到游览器传过来的参数,当然,我们也可以将传入的参数放入集合中
例如:
1 2 3 4 5
| @RequestMapping("/testParam") public String testParam(@RequestParam Map<String,String> map){ String usernem = map.get("username"); return "success"; }
|
在参数中我们不仅可以获取Parameter,同样也可以直接获取到Servlet的request以及response以及session,但是一般并不会去这样使用。
1 2 3 4 5
| @RequestMapping("/testParam") public String testParam(HttpServletRequest reqeust){ String usernem = reqeust.getParameter("username"); return "success"; }
|
当然如果我们获取到的参数刚好是要放入Java Bean对象中,我们也可以直接在传入的参数中直接将对象作为参数,SpringMVC会自动装配值(当然名字得一样)
其他参数的获取
在方法的传参中我们能够获取Request对象,那么同样的我们也可以在里面获取其他的参数,以下是常用的获取参数
@PathVariable
路径变量(Resful风格)
@RequestHeader
获取请求头
@RequestParam
获取请求参数
@CookieValue
获取Cookie值
@RequestAttribute
获取request域属性
使用Cookie举例:
1 2 3 4 5
| @RequestMapping("/testParam") public String testParam(@CookieValue("GUID") Cookie cookie){ String value = cookie.getValue(); return "success"; }
|
向域对象中存放数据
在SpringMVC中可以轻易的获取值,那么除了先获取Request对象再放入值这种原始的方法之外,SpringMVC也提供了几种方法让我们能够轻易的向域对象中存放数据,不过这些方法之间差别不大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @RequestMapping("/testRequestByServletAPI") public String testRequestByServletAPI(HttpServletRequest request){ request.setAttribute("testRequestScope","hello,servletAPI"); return "success"; }
@RequestMapping("/testModelAndView") public ModelAndView testModelAndView(){ ModelAndView mav = new ModelAndView(); mav.addObject("testRequestScope","hello,ModelAndView"); mav.setViewName("success"); return mav; }
@RequestMapping("/testModel") public String testModel(Model model){ model.addAttribute("testRequestScope","hello,Model"); System.out.println(model.getClass().getName()); return "success"; }
@RequestMapping("/testMap") public String testMap(Map<String,Object> map){ map.put("testRequestScope","hello,Map"); System.out.println(map.getClass().getName()); return "success"; }
@RequestMapping("/testModelMap") public String testModelMap(ModelMap modelMap){ modelMap.addAttribute("testRequestScope", "hello,ModelMap"); System.out.println(modelMap.getClass().getName()); return "success"; }
@RequestMapping("/testSession") public String testSession(HttpSession session){ session.setAttribute("testSessionScope","hello,session"); return "success"; }
@RequestMapping("/testApplication") public String testApplication(HttpSession session){ ServletContext context = session.getServletContext(); context.setAttribute("testApplicationScope","hello,application"); return "success"; }
|
视图的转发与重定向
在视图的配置中,我们通常都会配置视图前缀和视图后缀,因此只需要在方法中返回字符串SpringMVC会自动将拼接成完整的路径,在这里MVC做的是视图转发。
而如果我们想要把请求转发到其他方法,而不是让SpringMVC给我们做视图的转发,那么我们就可以这样写
代码如下:
1 2 3 4 5 6 7 8 9 10
| @RequestMapping("/test") public String test(){ return "forward:/success"; }
@RequestMapping("/success") public String success(){ return "success"; }
|
那么如果我们不希望SpringMVC给我们做转发到视图而是重定向,那么只需要将forward
改成redirect
即可
1 2 3 4
| @RequestMapping("/test") public String test(){ return "redirect:/success"; }
|
RESTful风格
SpringMVC同样支持Restful风格的写法,即为将参数包含在路径中发送请求
请求地址/book/page/1
如上所示的分页请求,就是一个Restful风格的请求
那么在SpringMVC中的请求处理中如下所示
1 2 3
| @RequestMapping("/book/page/{num}") public String test(@PathVariable("num")String page){ return "book";
|
page的页数加载请求地址中传入,在SpringMVC使用方法参数注解@PathVariable
指定参数的名称即可获取到参数
而Result也规定了四种请求
操作 |
传统方式 |
REST风格 |
增 |
saveUser |
user—>POST |
删 |
deleteUser?id=1 |
user/1—>DELETE |
改 |
updateUser |
user—>PUT |
查 |
getUserById?id=1 |
user/1—>GET |
在游览器中实际上是无法直接发出四种请求的,我们可以借助ajax发送请求,或者使用SpringMVC提供的亲求转换过滤器HiddenHttpMethodFilter
可以在前面文章的SSM配置整合中找到配置方法,这里不再复述
在游览器中使用属性为hidden
的input
标签,HiddenHttpMethodFilter
会自动将请求转化为对应的请求
1 2 3
| <form action="user/1" method="post"> <input type="hidden" name="_method" value="DELETE"/> </form>
|
需要注意的是,游览器发送的请求必须是POST请求过滤器才能够进行转换
@ResponseBody
如果我们要向游览器返回数据,那么我们需要在方法上加上此注解,当然通常使用此注解的时候返回的都为JSON数据,那么此时我们需要依赖Jackson
进行对象的转换