一、注解方式配置
在此之前我们都是在xml中手动配置bean,这样的方式如果量大的话会带来一些问题,于是我们可以使用spring提供的注解方式来指定需要管理的对象。
1.使用注解可以简化xml的配置,开发效率高,可读性比较强。
2.使用注解配置时是类型安全了,一般不会出现配置错误的问题,在开发的时候就能发现,而用xml配置在运行的时候才能发现。
1.1 添加相关约束
xmlns:context="http://www.springframework.org/schema/context"
对xsi:schemaLocation 中添加如下两条
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
整体配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd"
>
<context:component-scan base-package="cn.itcast.bean" />
</beans>
其中 <context:component-scan base-package="cn.itcast.bean" /> 指定spring要去扫描的包,我们可以在包下面的类中配置注解,这样spring就可以扫描的到,就可以根据我们的注解去管理生成对象了
1.2 @Component注解
在User.java类上用Component注解,说明这是一个需要管理的bean,
另外还有@Service 业务层,@Respository dao层,@Controller web层,这些的效果是一样的,只是语义不一样。
package cn.itcast.bean;
import org.springframework.stereotype.Component;
@Component("user")
public class User {
private String name;
private Integer age;
private Car car;
public User() {
}
public User(Integer age, Car car) {
System.out.println("构造函数注入。。");
this.age = age;
this.car = car;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
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 void initUser() {
System.out.println("user init ...");
}
public void destroyUser() {
System.out.println("user destroy ...");
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", car=" + car + "]";
}
}
相当于
<bean name="user" class="cn.itcast.bean.User" ></bean>
测试代码
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user);
}
和以前的运行结果完全一样。
1.3 @Scope注解
@Scope注解可以指定对象的scope作用范围,和之前的scope属性是一样的
如@Scope("prototype")
1.4 属性值类型注入
使用@Value注解可以对属性值进行值类型的注入,我们在字段name上用@Value("zhangsan") 注入
@Component("user")
@Scope("prototype")
public class User {
@Value("zhangsan")
private String name;
private Integer age;
private Car car;
public User() {
}
}
或者可以在set方法上写
@Value("zhangsan")
public void setName(String name) {
this.name = name;
}
1.5 引用类型注入
使用@Autowired 可以进行应用类型的自动装配
在car标记为注入组件
@Component("car")
public class Car {
private String name;
private String color;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Car [name=" + name + ", color=" + color + "]";
}
}
在User.java中标记car字段
@Component("user")
@Scope("prototype")
public class User {
private String name;
private Integer age;
@Autowired
private Car car;
public User() {
}
}
注意:@Autowired 自动装配相同类型的对象,如果有多个相同类型的对象,则将无法选择注入哪一个对象(当有多个同一个接口的实现类时),我们需要使用@Qualifier来指定自动装配哪个对象
使用@Resource(name="car") 来手动注入,指定注入哪一个对象,这里我们指定一个car1,并且手动使用car1注入
@Component("car1")
public class Car {
private String name;
private String color;
public String getName() {
return name;
}
}
@Component("user")
@Scope("prototype")
public class User {
private String name;
private Integer age;
@Resource(name = "car1")
private Car car;
public User() {
}
}
1.6 初始化和销毁
使用@PostConstruct注解来标记init-method方法,使用@PreDestroy注解来标记destroy-method方法
@PostConstruct
public void initUser() {
System.out.println("user init ...");
}
@PreDestroy
public void destroyUser() {
System.out.println("user destroy ...");
}
二、spring整合junit测试
整合之后可以使我们非常方便的进行测试,省去了许多多余代码的麻烦
2.1 导包 4+2+aop+test
2.2 添加一个测试类
package cn.itcast.test;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.itcast.bean.User;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestDemo {
@Resource(name = "user")
private User user;
@Test
public void testScopeSingleton() {
System.out.println(user);
}
}
添加上述注解,直接运行,如果成功可以打印出我们要的对象。
注意:可能出现报错的情况,这是因为junit的版本过低的问题,解决办法https://blog.youkuaiyun.com/java_web_android/article/details/77482418
三、spring中的aop
3.1 aop的思想介绍
aop思想的核心就是横向重复,纵向抽取。这八个字说起来简单,理解起来估计不容易。
先上一张图
比如我们filter 拦截请求的过程,其实我们不抽取的话,每个servlet都得写一遍,也就是请求来处理的这个纵向过程中,横向代码有重复的部分,就抽取出来,相当于一个切面一样插入了这个纵向的请求管道,相当于把所有的横向重复的部分放到了管道上边一样,这就是纵向抽取出来了。这也是面向切面编程思想的一个展现。
像上面拦截器啊,都是这个道理。再说的简单一样,就是我们要在执行一些方法前统一执行一些代码,或者之后统一执行一些代码,方法的执行就是纵向的,如果不抽取就是横向重复了,把横向的重复的部分抽取出了,也放到方法的纵向执行过程中,这就实现了aop的思想。
3.2 spring中aop思想的实现原理
要想使得执行一个方法之前或者之后执行一些代码,而这些代码又不能加入到方法里边,又不能破坏原来的对象的方法,我们就可以制造一个新的对象,新的对象和老的对象有同样的方法,在同一个方法里我们可以加入aop的代码,再次调用原来对象的同样的方法,我们把这个新的对象称为代理对象,相当于对原来对象进行了扩充,和装饰者模式有点像。
3.2.1 动态代理方式
使用java自带的动态代理方式的方法就可以帮我们产生一个代理对象。
注意:被代理对象一定要实现接口才能产生代理对象,没有实现接口不能使用动态代理技术
我们先编写一个接口和一个实现类 UserService和UserServiceImpl
package cn.itcast.service;
public interface UserService {
void save();
void update();
void delete();
void query();
}
public class UserServiceImpl implements UserService {
@Override
public void save() {
// TODO Auto-generated method stub
System.out.println("保存用户");
}
@Override
public void update() {
// TODO Auto-generated method stub
System.out.println("更新用户");
}
@Override
public void delete() {
// TODO Auto-generated method stub
System.out.println("删除用户");
}
@Override
public void query() {
// TODO Auto-generated method stub
System.out.println("查询用户");
}
}
接下来我们要产生UserServiceImpl的动态代理对象,来实现增强,可以在方法的前边和后边执行我们的aop代码。
代理类:
package cn.itcast.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import cn.itcast.service.UserService;
import cn.itcast.service.UserServiceImpl;
public class UserServiceProxy implements InvocationHandler {
public UserServiceProxy(UserService us) {
super();
this.us = us;
}
private UserService us;
public UserService getUserServiceProxy(){
UserService proxyInstance = (UserService)Proxy.newProxyInstance(UserServiceProxy.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), this);
return proxyInstance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("保存之前打开事务...");
Object invoke = method.invoke(us, args);
System.out.println("保存之后关闭事务...");
return invoke;
}
}
测试代码:
@Test
public void testProxy(){
UserServiceProxy proxy = new UserServiceProxy(new UserServiceImpl());
UserService us = proxy.getUserServiceProxy();
us.save();
}
输出
3.2.2 cglib代理
第三方代理技术,cglib代理.可以对任何类生成代理.代理的原理是对目标对象进行继承代理. 如果目标对象被final修饰.那么该类无法被cglib代理.
代理类
package cn.itcast.proxy;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import cn.itcast.service.UserService;
import cn.itcast.service.UserServiceImpl;
public class CglibUserServiceProxy implements MethodInterceptor {
public UserService getUserServiceProxy(){
Enhancer en = new Enhancer();
en.setSuperclass(UserServiceImpl.class);
en.setCallback(this);
UserService us = (UserService) en.create();
return us;
}
@Override
public Object intercept(Object prxoyobj, Method arg1, Object[] arg,
MethodProxy methodProxy) throws Throwable {
//打开事务
System.out.println("打开事务!");
//调用原有方法
Object returnValue = methodProxy.invokeSuper(prxoyobj, arg);
//提交事务
System.out.println("提交事务!");
return returnValue;
}
}
测试代码:
@Test
public void testCglibProxy(){
CglibUserServiceProxy proxy = new CglibUserServiceProxy();
UserService us = proxy.getUserServiceProxy();
us.save();
}
3.3 aop名词介绍
3.3.1 连接点(joinpoint)
aop中的连接点就是 目标对象中 所有可以被增强的方法,顾名思义,只有这些方法被调用的时候才可以把调用之前要执行的代码和调用之后要执行的代码连接起来。所以叫连接点
package cn.itcast.service;
public class UserServiceImpl implements UserService {
@Override
public void save() {
// TODO Auto-generated method stub
System.out.println("保存用户");
}
@Override
public void update() {
// TODO Auto-generated method stub
System.out.println("更新用户");
}
@Override
public void delete() {
// TODO Auto-generated method stub
System.out.println("删除用户");
}
@Override
public void query() {
// TODO Auto-generated method stub
System.out.println("查询用户");
}
}
我们要增强UserServiceImpl中的所有方法,所以save,update,delete,query都是连接点。
3.3.2 切入点(pointcut)
目标对象已经增强的方法,相当于我们已经切入了这个点,所以叫做切入点。上边所有方法都增强了,所以都是切入点。
3.3.3 通知/增强(advice)
就是增强的代码,新加入的代码,这个就很好理解了,你加入一些代码其实也是想通知这个方法调用前后要干些什么事情。
3.3.4 目标对象(target)
目标对象是要被代理的对象,这里是UserServiceImpl。
3.3.5 织入(weaving)
将通知应用到切入点的过程。
3.3.6 代理(Proxy)
将通知织入到目标对象之后,形成代理对象。
3.3.7 切面(aspect)
切面是一个组合,是切入点和通知的组合,相当于一个完整的功能的实现。
四、spring中的aop演示
4.1 导包
导入4+2包,和 spring-aspects-4.2.4.RELEASE.jar ,spring-aspects-4.2.4.RELEASE.jar 和依赖的第三方包
com.springsource.org.aopalliance-1.0.0.jar ,com.springsource.org.aopalliance-1.0.0.jar
4.2 准备目标对象
package cn.itcast.service;
public class UserServiceImpl implements UserService {
@Override
public void save() {
// TODO Auto-generated method stub
System.out.println("保存用户");
}
@Override
public void update() {
// TODO Auto-generated method stub
System.out.println("更新用户");
}
@Override
public void delete() {
// TODO Auto-generated method stub
System.out.println("删除用户");
}
@Override
public void query() {
// TODO Auto-generated method stub
System.out.println("查询用户");
}
}
4.3 准备通知
package cn.itcast.advice;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAdvice {
public void before(){
System.out.println("这是前置通知");
}
public void afterReturning(){
System.out.println("这是后置通知(如果出现异常则不会调用)");
}
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("这是环绕通知之前前前前前的部分");
Object obj = pjp.proceed();
System.out.println("这是环绕通知之后后后后后的部分");
return obj;
}
public void afterException(){
System.out.println("出现异常之后。。。");
}
public void after(){
System.out.println("出现异常之后仍然调用");
}
}
4.4 配置织入,将通知织入目标对象
首先要导入名称空间 xmlns:aop="http://www.springframework.org/schema/aop"
约束 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean name="userService" class="cn.itcast.service.UserServiceImpl"></bean>
<bean name="myAdvice" class="cn.itcast.advice.MyAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* cn.itcast.service.*ServiceImpl.*(..))" id="pc" />
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut-ref="pc"/>
<aop:after-returning method="afterReturning" pointcut-ref="pc"/>
<aop:around method="around" pointcut-ref="pc"/>
<aop:after-throwing method="afterException" pointcut-ref="pc"/>
<aop:after method="after" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
</beans>
输入结果
4.5 配置切入点对象表达式
expression="execution(* cn.itcast.service.*ServiceImpl.*(..))" 对应
public void cn.itcast.service.UserServiceImpl.save() 具体的方法
void cn.itcast.service.UserServiceImpl.save() 不限修饰符
* cn.itcast.service.UserServiceImpl.save() 不限返回值
* cn.itcast.service.UserServiceImpl.*() 不限返回值和方法名 但是必须是没有参数的
* cn.itcast.service.*ServiceImpl.*(..) 不限返回值 类名以ServiceImpl结尾的任何不限参数的方法 只找service 包下面
* cn.itcast.service..*ServiceImpl.*(..) 两个点 包括子包也找
4.6 注解配置方式
配置文件修改
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean name="userService" class="cn.itcast.service.UserServiceImpl"></bean>
<bean name="myAdvice" class="cn.itcast.advice.MyAdvice"></bean>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
通知类
package cn.itcast.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class MyAdvice {
@Pointcut("execution(* cn.itcast.service.*ServiceImpl.*(..))")
public void pc(){
}
@Before("MyAdvice.pc()")
public void before(){
System.out.println("这是前置通知");
}
@AfterReturning("execution(* cn.itcast.service.*ServiceImpl.*(..))")
public void afterReturning(){
System.out.println("这是后置通知(如果出现异常则不会调用)");
}
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("这是环绕通知之前前前前前的部分");
Object obj = pjp.proceed();
System.out.println("这是环绕通知之后后后后后的部分");
return obj;
}
@AfterThrowing("execution(* cn.itcast.service.*ServiceImpl.*(..))")
public void afterException(){
System.out.println("出现异常之后。。。");
}
public void after(){
System.out.println("出现异常之后仍然调用");
}
}
在类名上加上注解@Aspect 然后在响应方法上加上@Before @After 之类的注解表明环绕方式,参数为execution表达式
五、总结
这就是spring的aop功能,aop在一些框架中和一些业务中都非常的有用,我们可以利用这样的方法进行监控和记录日志,避免了很多重复的代码。